TenantAtlas/apps/platform/app/Support/Resources/ProviderResourceDescriptor.php
Ahmed Darrazi fb2642e941
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m9s
feat(resources): implement provider resource identity binding
Added ProviderResourceBinding model, migrations, policies, and supporting framework for canonical resource identity mapping as defined in Spec 381.
2026-06-15 17:37:06 +02:00

129 lines
4.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Resources;
use App\Support\Baselines\SubjectClass;
use Illuminate\Contracts\Support\Arrayable;
use JsonSerializable;
/**
* @implements Arrayable<string, mixed>
*/
final readonly class ProviderResourceDescriptor implements Arrayable, JsonSerializable
{
/**
* @param array<string, string|int|float|bool|null> $sourceReferences
*/
public function __construct(
public ResourceIdentity $identity,
public ?string $displayLabel,
public string $subjectDomain,
public SubjectClass|string $subjectClass,
public string $subjectTypeKey,
public array $sourceReferences = [],
public ?string $fingerprint = null,
public ?string $lastSeenAt = null,
) {}
/**
* @param array<string, string|int|float|bool|null> $sourceReferences
*/
public static function fromIdentity(
ResourceIdentity $identity,
string $subjectDomain,
SubjectClass|string $subjectClass,
string $subjectTypeKey,
?string $displayLabel = null,
array $sourceReferences = [],
?string $fingerprint = null,
?string $lastSeenAt = null,
): self {
return new self(
identity: $identity,
displayLabel: $displayLabel,
subjectDomain: $subjectDomain,
subjectClass: $subjectClass,
subjectTypeKey: $subjectTypeKey,
sourceReferences: self::safeSourceReferences($sourceReferences),
fingerprint: $fingerprint,
lastSeenAt: $lastSeenAt,
);
}
/**
* @param array<string, mixed> $payload
*/
public static function fromArray(array $payload): self
{
$identityPayload = $payload['identity'] ?? [];
return new self(
identity: ResourceIdentity::fromArray(is_array($identityPayload) ? $identityPayload : []),
displayLabel: self::nullableString($payload['display_label'] ?? null),
subjectDomain: (string) ($payload['subject_domain'] ?? ''),
subjectClass: (string) ($payload['subject_class'] ?? ''),
subjectTypeKey: (string) ($payload['subject_type_key'] ?? ''),
sourceReferences: self::safeSourceReferences(is_array($payload['source_references'] ?? null) ? $payload['source_references'] : []),
fingerprint: self::nullableString($payload['fingerprint'] ?? null),
lastSeenAt: self::nullableString($payload['last_seen_at'] ?? null),
);
}
/**
* @return array{
* identity: array<string, mixed>,
* display_label: ?string,
* subject_domain: string,
* subject_class: string,
* subject_type_key: string,
* source_references: array<string, string|int|float|bool|null>,
* fingerprint: ?string,
* last_seen_at: ?string
* }
*/
public function toArray(): array
{
return [
'identity' => $this->identity->toArray(),
'display_label' => $this->displayLabel,
'subject_domain' => $this->subjectDomain,
'subject_class' => $this->subjectClass instanceof SubjectClass ? $this->subjectClass->value : $this->subjectClass,
'subject_type_key' => $this->subjectTypeKey,
'source_references' => self::safeSourceReferences($this->sourceReferences),
'fingerprint' => $this->fingerprint,
'last_seen_at' => $this->lastSeenAt,
];
}
public function jsonSerialize(): array
{
return $this->toArray();
}
/**
* @param array<string, mixed> $sourceReferences
* @return array<string, string|int|float|bool|null>
*/
private static function safeSourceReferences(array $sourceReferences): array
{
$safe = [];
foreach ($sourceReferences as $key => $value) {
if (! is_string($key) || trim($key) === '' || (! is_scalar($value) && $value !== null)) {
continue;
}
$safe[$key] = $value;
}
return $safe;
}
private static function nullableString(mixed $value): ?string
{
return is_string($value) && trim($value) !== '' ? trim($value) : null;
}
}