|null */ private ?array $cache = null; /** @var array>|null */ private ?array $guardClassCache = null; /** @var array */ private array $guardProviderCache = []; /** @var array>|null */ private ?array $resolverClassCache = null; /** @var array */ private array $resolverInstanceCache = []; public function __construct(private readonly ConfigRepository $config) {} /** @return array keyed by slug */ public function all(): array { if ($this->cache !== null) { return $this->cache; } /** @var array> $raw */ $raw = (array) $this->config->get('form_builder.purposes', []); $definitions = []; $guardClasses = []; $resolverClasses = []; foreach ($raw as $slug => $attrs) { $mode = $attrs['default_submission_mode'] ?? null; if (! $mode instanceof FormSubmissionMode) { throw new \InvalidArgumentException( "Purpose '{$slug}' has invalid default_submission_mode; expected FormSubmissionMode enum case." ); } $guardsClass = $attrs['guards_class'] ?? null; if (! is_string($guardsClass) || ! is_subclass_of($guardsClass, PurposeGuardProvider::class)) { throw new \InvalidArgumentException( "Purpose '{$slug}' has invalid guards_class; expected class-string implementing PurposeGuardProvider." ); } $resolverClass = $attrs['subject_resolver_class'] ?? null; if (! is_string($resolverClass) || ! is_subclass_of($resolverClass, PurposeSubjectResolver::class)) { throw new \InvalidArgumentException( "Purpose '{$slug}' has invalid subject_resolver_class; expected class-string implementing PurposeSubjectResolver." ); } $definitions[(string) $slug] = new PurposeDefinition( slug: (string) $slug, label: (string) ($attrs['label'] ?? ''), subjectType: (string) ($attrs['subject_type'] ?? ''), defaultSubmissionMode: $mode, allowsPublicAccess: (bool) ($attrs['allows_public_access'] ?? false), requiredBindings: array_values((array) ($attrs['required_bindings'] ?? [])), ); $guardClasses[(string) $slug] = $guardsClass; $resolverClasses[(string) $slug] = $resolverClass; } $this->guardClassCache = $guardClasses; $this->resolverClassCache = $resolverClasses; return $this->cache = $definitions; } public function guardProviderFor(string $slug): PurposeGuardProvider { if (! $this->has($slug)) { throw Exceptions\PurposeNotFoundException::forSlug($slug); } if (isset($this->guardProviderCache[$slug])) { return $this->guardProviderCache[$slug]; } /** @var array> $classes */ $classes = $this->guardClassCache ?? []; $class = $classes[$slug]; /** @var PurposeGuardProvider $instance */ $instance = resolve($class); return $this->guardProviderCache[$slug] = $instance; } public function subjectResolverFor(string $slug): PurposeSubjectResolver { if (! $this->has($slug)) { throw Exceptions\PurposeNotFoundException::forSlug($slug); } if (isset($this->resolverInstanceCache[$slug])) { return $this->resolverInstanceCache[$slug]; } /** @var array> $classes */ $classes = $this->resolverClassCache ?? []; $class = $classes[$slug]; /** @var PurposeSubjectResolver $instance */ $instance = resolve($class); return $this->resolverInstanceCache[$slug] = $instance; } public function get(string $slug): PurposeDefinition { $all = $this->all(); if (! isset($all[$slug])) { throw PurposeNotFoundException::forSlug($slug); } return $all[$slug]; } public function has(string $slug): bool { return isset($this->all()[$slug]); } /** @return list unique sorted list of subject_type aliases */ public function allSubjectTypes(): array { $types = array_values(array_unique(array_map( static fn (PurposeDefinition $p): string => $p->subjectType, $this->all(), ))); sort($types); return $types; } /** @return list slugs with allows_public_access === true */ public function publicAccessibleSlugs(): array { return array_values(array_keys(array_filter( $this->all(), static fn (PurposeDefinition $p): bool => $p->allowsPublicAccess, ))); } }