150 lines
4.8 KiB
PHP
150 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Support\Providers\Boundary;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
final class ProviderBoundarySeam
|
|
{
|
|
public const string FOLLOW_UP_NONE = 'none';
|
|
|
|
public const string FOLLOW_UP_DOCUMENT_IN_FEATURE = 'document-in-feature';
|
|
|
|
public const string FOLLOW_UP_SPEC = 'follow-up-spec';
|
|
|
|
/**
|
|
* @param list<string> $implementationPaths
|
|
* @param list<string> $neutralTerms
|
|
* @param list<string> $retainedProviderSemantics
|
|
*/
|
|
public function __construct(
|
|
public readonly string $key,
|
|
public readonly ProviderBoundaryOwner $owner,
|
|
public readonly string $description,
|
|
public readonly array $implementationPaths,
|
|
public readonly array $neutralTerms,
|
|
public readonly array $retainedProviderSemantics,
|
|
public readonly string $followUpAction,
|
|
) {
|
|
$this->validate();
|
|
}
|
|
|
|
/**
|
|
* @param array{
|
|
* owner?: string,
|
|
* description?: string,
|
|
* implementation_paths?: list<string>,
|
|
* neutral_terms?: list<string>,
|
|
* retained_provider_semantics?: list<string>,
|
|
* follow_up_action?: string
|
|
* } $attributes
|
|
*/
|
|
public static function fromConfig(string $key, array $attributes): self
|
|
{
|
|
$owner = ProviderBoundaryOwner::tryFrom((string) ($attributes['owner'] ?? ''));
|
|
|
|
if (! $owner instanceof ProviderBoundaryOwner) {
|
|
throw new InvalidArgumentException("Provider boundary seam [{$key}] has an invalid owner.");
|
|
}
|
|
|
|
return new self(
|
|
key: $key,
|
|
owner: $owner,
|
|
description: (string) ($attributes['description'] ?? ''),
|
|
implementationPaths: self::stringList($attributes['implementation_paths'] ?? []),
|
|
neutralTerms: self::stringList($attributes['neutral_terms'] ?? []),
|
|
retainedProviderSemantics: self::stringList($attributes['retained_provider_semantics'] ?? []),
|
|
followUpAction: (string) ($attributes['follow_up_action'] ?? self::FOLLOW_UP_NONE),
|
|
);
|
|
}
|
|
|
|
public function isProviderOwned(): bool
|
|
{
|
|
return $this->owner === ProviderBoundaryOwner::ProviderOwned;
|
|
}
|
|
|
|
public function isPlatformCore(): bool
|
|
{
|
|
return $this->owner === ProviderBoundaryOwner::PlatformCore;
|
|
}
|
|
|
|
public function retainsProviderSemantics(): bool
|
|
{
|
|
return $this->retainedProviderSemantics !== [];
|
|
}
|
|
|
|
public function documentsProviderSemantic(string $term): bool
|
|
{
|
|
return in_array($term, $this->retainedProviderSemantics, true);
|
|
}
|
|
|
|
public function coversPath(string $path): bool
|
|
{
|
|
$normalizedPath = $this->normalizePath($path);
|
|
|
|
foreach ($this->implementationPaths as $implementationPath) {
|
|
if ($normalizedPath === $this->normalizePath($implementationPath)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param array<mixed> $values
|
|
* @return list<string>
|
|
*/
|
|
private static function stringList(array $values): array
|
|
{
|
|
return array_values(array_filter(
|
|
array_map(static fn (mixed $value): string => trim((string) $value), $values),
|
|
static fn (string $value): bool => $value !== '',
|
|
));
|
|
}
|
|
|
|
private function validate(): void
|
|
{
|
|
if (trim($this->key) === '') {
|
|
throw new InvalidArgumentException('Provider boundary seam key cannot be empty.');
|
|
}
|
|
|
|
if (trim($this->description) === '') {
|
|
throw new InvalidArgumentException("Provider boundary seam [{$this->key}] must include a description.");
|
|
}
|
|
|
|
if ($this->implementationPaths === []) {
|
|
throw new InvalidArgumentException("Provider boundary seam [{$this->key}] must include implementation paths.");
|
|
}
|
|
|
|
if ($this->isPlatformCore() && $this->neutralTerms === []) {
|
|
throw new InvalidArgumentException("Platform-core provider boundary seam [{$this->key}] must include neutral terms.");
|
|
}
|
|
|
|
if ($this->retainsProviderSemantics() && $this->followUpAction === self::FOLLOW_UP_NONE) {
|
|
throw new InvalidArgumentException("Provider boundary seam [{$this->key}] retains provider semantics without a follow-up action.");
|
|
}
|
|
|
|
if (! in_array($this->followUpAction, $this->validFollowUpActions(), true)) {
|
|
throw new InvalidArgumentException("Provider boundary seam [{$this->key}] has an invalid follow-up action.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
private function validFollowUpActions(): array
|
|
{
|
|
return [
|
|
self::FOLLOW_UP_NONE,
|
|
self::FOLLOW_UP_DOCUMENT_IN_FEATURE,
|
|
self::FOLLOW_UP_SPEC,
|
|
];
|
|
}
|
|
|
|
private function normalizePath(string $path): string
|
|
{
|
|
return trim(str_replace('\\', '/', $path), '/');
|
|
}
|
|
}
|