TenantAtlas/apps/platform/app/Support/Governance/PlatformVocabularyTerm.php
2026-04-14 08:07:40 +02:00

110 lines
3.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Governance;
use InvalidArgumentException;
final readonly class PlatformVocabularyTerm
{
/**
* @param list<string> $allowedContexts
* @param list<string> $legacyAliases
* @param list<string> $forbiddenPlatformAliases
*/
public function __construct(
public string $termKey,
public string $canonicalLabel,
public string $canonicalDescription,
public string $boundaryClassification,
public string $ownerLayer,
public array $allowedContexts = [],
public array $legacyAliases = [],
public ?string $aliasRetirementPath = null,
public array $forbiddenPlatformAliases = [],
) {
if (trim($this->termKey) === '' || trim($this->canonicalLabel) === '' || trim($this->canonicalDescription) === '') {
throw new InvalidArgumentException('Platform vocabulary terms require a key, label, and description.');
}
if ($this->legacyAliases !== [] && blank($this->aliasRetirementPath)) {
throw new InvalidArgumentException('Platform vocabulary terms with legacy aliases must declare an alias retirement path.');
}
}
/**
* @param array<string, mixed> $data
*/
public static function fromArray(array $data): self
{
return new self(
termKey: (string) ($data['term_key'] ?? ''),
canonicalLabel: (string) ($data['canonical_label'] ?? ''),
canonicalDescription: (string) ($data['canonical_description'] ?? ''),
boundaryClassification: (string) ($data['boundary_classification'] ?? ''),
ownerLayer: (string) ($data['owner_layer'] ?? ''),
allowedContexts: self::stringList($data['allowed_contexts'] ?? []),
legacyAliases: self::stringList($data['legacy_aliases'] ?? []),
aliasRetirementPath: is_string($data['alias_retirement_path'] ?? null)
? trim((string) $data['alias_retirement_path'])
: null,
forbiddenPlatformAliases: self::stringList($data['forbidden_platform_aliases'] ?? []),
);
}
public function matches(string $term): bool
{
$normalized = trim(mb_strtolower($term));
if ($normalized === '') {
return false;
}
return in_array($normalized, array_map(
static fn (string $candidate): string => trim(mb_strtolower($candidate)),
array_merge([$this->termKey], $this->legacyAliases),
), true);
}
/**
* @return array{
* term_key: string,
* canonical_label: string,
* canonical_description: string,
* boundary_classification: string,
* owner_layer: string,
* allowed_contexts: list<string>,
* legacy_aliases: list<string>,
* alias_retirement_path: ?string,
* forbidden_platform_aliases: list<string>
* }
*/
public function toArray(): array
{
return [
'term_key' => $this->termKey,
'canonical_label' => $this->canonicalLabel,
'canonical_description' => $this->canonicalDescription,
'boundary_classification' => $this->boundaryClassification,
'owner_layer' => $this->ownerLayer,
'allowed_contexts' => $this->allowedContexts,
'legacy_aliases' => $this->legacyAliases,
'alias_retirement_path' => $this->aliasRetirementPath,
'forbidden_platform_aliases' => $this->forbiddenPlatformAliases,
];
}
/**
* @param iterable<mixed> $values
* @return list<string>
*/
private static function stringList(iterable $values): array
{
return collect($values)
->filter(static fn (mixed $value): bool => is_string($value) && trim($value) !== '')
->map(static fn (string $value): string => trim($value))
->values()
->all();
}
}