refactor(customfield): interface-based DI for repositories

Align CustomField-Schicht mit core/Repository/Access-Muster. Vier
Repositories (Tenant-Definition, Tenant-Option, User-Value, User-Value-Option)
bekommen je ein Interface, werden von static auf instance umgestellt und
ueber CustomFieldRepositoryFactory injiziert.

TenantCustomFieldService und UserCustomFieldValueService injizieren die
Repository-Interfaces statt statisch aufzurufen. CustomFieldServicesFactory
reicht Instanzen durch. AddressBookService (Modul) bekommt
TenantCustomFieldOptionRepositoryInterface per Container — Interface-Kopplung
verbessert Modul-Isolation gegenueber dem vorherigen statischen Aufruf.

Kein Verhaltenswechsel, keine SQL-Aenderung, keine Service-Signatur-Aenderung.
Alle tenant_id-Parameter erhalten (GR-SEC-009, Security-Review SR-002).

Oeffnet den Weg fuer Cluster B1 (TenantCustomFieldService-Tests) und B2
(UserCustomFieldValueService-Tests), die nun mit createMock() moeglich sind.

Nebenbei zwei PHPStan-Folge-Findings korrigiert:
- Veralteter Ignore-Eintrag fuer findById aus phpstan-baseline.neon entfernt
- Redundanter is_array-Check in TenantCustomFieldOptionRepository entfernt
  (durch typisierte Interface-Signatur abgedeckt)

Gates: QG-001 (1945 Tests, 28581 Assertions) / QG-002 / QG-003 / QG-006 pass.

Workflow: .agents/runs/CUSTOMFIELD-DI-REFACTOR-001/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 19:32:14 +02:00
parent 0da785f4b7
commit 6b05bbfe9c
18 changed files with 227 additions and 83 deletions

View File

@@ -5,7 +5,7 @@ namespace MintyPHP\Repository\CustomField;
use MintyPHP\DB;
/** Retrieves selectable options for tenant-scoped custom field definitions. */
class TenantCustomFieldOptionRepository
class TenantCustomFieldOptionRepository implements TenantCustomFieldOptionRepositoryInterface
{
private static function unwrapList($rows): array
{
@@ -22,7 +22,7 @@ class TenantCustomFieldOptionRepository
return $list;
}
public static function listByDefinitionIds(array $definitionIds, bool $onlyActive = true): array
public function listByDefinitionIds(array $definitionIds, bool $onlyActive = true): array
{
$definitionIds = array_values(array_unique(array_map('intval', $definitionIds)));
$definitionIds = array_values(array_filter($definitionIds, static fn ($id) => $id > 0));
@@ -39,12 +39,12 @@ class TenantCustomFieldOptionRepository
return self::unwrapList(call_user_func_array(['MintyPHP\\DB', 'select'], array_merge([$query], $params)));
}
public static function replaceForDefinition(int $definitionId, array $options): bool
public function replaceForDefinition(int $definitionId, array $options): bool
{
if ($definitionId <= 0) {
return false;
}
$existing = self::listByDefinitionIds([$definitionId], false);
$existing = $this->listByDefinitionIds([$definitionId], false);
$existingByKey = [];
foreach ($existing as $row) {
$key = (string) ($row['option_key'] ?? '');
@@ -55,9 +55,6 @@ class TenantCustomFieldOptionRepository
$keepIds = [];
foreach ($options as $option) {
if (!is_array($option)) {
continue;
}
$optionKey = trim((string) ($option['option_key'] ?? ''));
$label = trim((string) ($option['label'] ?? ''));
if ($optionKey === '' || $label === '') {
@@ -117,7 +114,7 @@ class TenantCustomFieldOptionRepository
return true;
}
public static function deleteByDefinitionId(int $definitionId): bool
public function deleteByDefinitionId(int $definitionId): bool
{
if ($definitionId <= 0) {
return false;