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>
176 lines
6.7 KiB
PHP
176 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Repository\CustomField;
|
|
|
|
use MintyPHP\DB;
|
|
use MintyPHP\Repository\Support\RepoQuery;
|
|
|
|
/** Reads and writes tenant-scoped custom field definitions with type and pagination filtering. */
|
|
class TenantCustomFieldDefinitionRepository implements TenantCustomFieldDefinitionRepositoryInterface
|
|
{
|
|
private static function unwrap(?array $row): ?array
|
|
{
|
|
if (!$row) {
|
|
return null;
|
|
}
|
|
return $row['tenant_custom_field_definitions'] ?? null;
|
|
}
|
|
|
|
private static function unwrapList($rows): array
|
|
{
|
|
if (!is_array($rows)) {
|
|
return [];
|
|
}
|
|
$list = [];
|
|
foreach ($rows as $row) {
|
|
$item = $row['tenant_custom_field_definitions'] ?? null;
|
|
if (is_array($item)) {
|
|
$list[] = $item;
|
|
}
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
public function listByTenantId(int $tenantId, bool $onlyActive = true): array
|
|
{
|
|
if ($tenantId <= 0) {
|
|
return [];
|
|
}
|
|
$query = 'select id, uuid, tenant_id, field_key, label, type, is_required, is_filterable, active, sort_order, created_by, modified_by, created, modified ' .
|
|
'from tenant_custom_field_definitions where tenant_id = ?';
|
|
$params = [(string) $tenantId];
|
|
if ($onlyActive) {
|
|
$query .= ' and active = 1';
|
|
}
|
|
$query .= ' order by sort_order asc, label asc, id asc';
|
|
return self::unwrapList(DB::select($query, ...$params));
|
|
}
|
|
|
|
public function listByTenantIds(array $tenantIds, bool $onlyActive = true): array
|
|
{
|
|
$tenantIds = array_values(array_unique(array_map('intval', $tenantIds)));
|
|
$tenantIds = array_values(array_filter($tenantIds, static fn ($id) => $id > 0));
|
|
if (!$tenantIds) {
|
|
return [];
|
|
}
|
|
$query = 'select id, uuid, tenant_id, field_key, label, type, is_required, is_filterable, active, sort_order, created_by, modified_by, created, modified ' .
|
|
'from tenant_custom_field_definitions where tenant_id in (???)';
|
|
$params = [$tenantIds];
|
|
if ($onlyActive) {
|
|
$query .= ' and active = 1';
|
|
}
|
|
$query .= ' order by tenant_id asc, sort_order asc, label asc, id asc';
|
|
return self::unwrapList(call_user_func_array(['MintyPHP\\DB', 'select'], array_merge([$query], $params)));
|
|
}
|
|
|
|
public function listFilterableByTenantIds(array $tenantIds): array
|
|
{
|
|
$tenantIds = array_values(array_unique(array_map('intval', $tenantIds)));
|
|
$tenantIds = array_values(array_filter($tenantIds, static fn ($id) => $id > 0));
|
|
if (!$tenantIds) {
|
|
return [];
|
|
}
|
|
$query = 'select id, uuid, tenant_id, field_key, label, type, is_required, is_filterable, active, sort_order, created_by, modified_by, created, modified ' .
|
|
'from tenant_custom_field_definitions ' .
|
|
'where tenant_id in (???) and active = 1 and is_filterable = 1 ' .
|
|
"and type in ('select', 'multiselect', 'boolean', 'date') " .
|
|
'order by tenant_id asc, sort_order asc, label asc, id asc';
|
|
return self::unwrapList(call_user_func_array(['MintyPHP\\DB', 'select'], [$query, $tenantIds]));
|
|
}
|
|
|
|
public function findByUuid(string $uuid): ?array
|
|
{
|
|
$row = DB::selectOne(
|
|
'select id, uuid, tenant_id, field_key, label, type, is_required, is_filterable, active, sort_order, created_by, modified_by, created, modified ' .
|
|
'from tenant_custom_field_definitions where uuid = ? limit 1',
|
|
$uuid
|
|
);
|
|
return self::unwrap($row);
|
|
}
|
|
|
|
public function findById(int $id): ?array
|
|
{
|
|
$row = DB::selectOne(
|
|
'select id, uuid, tenant_id, field_key, label, type, is_required, is_filterable, active, sort_order, created_by, modified_by, created, modified ' .
|
|
'from tenant_custom_field_definitions where id = ? limit 1',
|
|
(string) $id
|
|
);
|
|
return self::unwrap($row);
|
|
}
|
|
|
|
public function findByTenantIdAndKey(int $tenantId, string $fieldKey): ?array
|
|
{
|
|
if ($tenantId <= 0 || $fieldKey === '') {
|
|
return null;
|
|
}
|
|
$row = DB::selectOne(
|
|
'select id, uuid, tenant_id, field_key, label, type, is_required, is_filterable, active, sort_order, created_by, modified_by, created, modified ' .
|
|
'from tenant_custom_field_definitions where tenant_id = ? and field_key = ? limit 1',
|
|
(string) $tenantId,
|
|
$fieldKey
|
|
);
|
|
return self::unwrap($row);
|
|
}
|
|
|
|
public function create(array $data): int|false
|
|
{
|
|
return DB::insert(
|
|
'insert into tenant_custom_field_definitions ' .
|
|
'(uuid, tenant_id, field_key, label, type, is_required, is_filterable, active, sort_order, created_by, created) ' .
|
|
'values (?,?,?,?,?,?,?,?,?,?,NOW())',
|
|
$data['uuid'] ?? RepoQuery::uuidV4(),
|
|
(string) ($data['tenant_id'] ?? 0),
|
|
(string) ($data['field_key'] ?? ''),
|
|
(string) ($data['label'] ?? ''),
|
|
(string) ($data['type'] ?? ''),
|
|
(string) ((int) ($data['is_required'] ?? 0)),
|
|
(string) ((int) ($data['is_filterable'] ?? 0)),
|
|
(string) ((int) ($data['active'] ?? 1)),
|
|
(string) ((int) ($data['sort_order'] ?? 100)),
|
|
$data['created_by'] ?? null
|
|
);
|
|
}
|
|
|
|
public function update(int $id, array $data): bool
|
|
{
|
|
if ($id <= 0) {
|
|
return false;
|
|
}
|
|
$fields = [
|
|
'field_key' => (string) ($data['field_key'] ?? ''),
|
|
'label' => (string) ($data['label'] ?? ''),
|
|
'type' => (string) ($data['type'] ?? ''),
|
|
'is_required' => (int) ($data['is_required'] ?? 0),
|
|
'is_filterable' => (int) ($data['is_filterable'] ?? 0),
|
|
'active' => (int) ($data['active'] ?? 1),
|
|
'sort_order' => (int) ($data['sort_order'] ?? 100),
|
|
];
|
|
if (array_key_exists('modified_by', $data)) {
|
|
$fields['modified_by'] = $data['modified_by'];
|
|
}
|
|
|
|
$set = [];
|
|
$params = [];
|
|
foreach ($fields as $field => $value) {
|
|
$set[] = sprintf('`%s` = ?', $field);
|
|
$params[] = (string) $value;
|
|
}
|
|
$params[] = (string) $id;
|
|
|
|
$result = DB::update(
|
|
'update tenant_custom_field_definitions set ' . implode(', ', $set) . ' where id = ?',
|
|
...$params
|
|
);
|
|
return $result !== false;
|
|
}
|
|
|
|
public function delete(int $id): bool
|
|
{
|
|
if ($id <= 0) {
|
|
return false;
|
|
}
|
|
$result = DB::delete('delete from tenant_custom_field_definitions where id = ?', (string) $id);
|
|
return $result !== false;
|
|
}
|
|
}
|