feat(form-builder): add PurposeSubjectResolver per purpose (WS-6)
Parallel interface to PurposeGuardProvider for runtime subject resolution. Seven concrete resolvers, one per v1.0 purpose. Wired through purposes.php via subject_resolver_class key. EventRegistration uses PersonProvisioner (may create). Other purposes resolve from existing context (portal token, production request, auth). IncidentReport is the only purpose allowed to return null (anonymous- allowed configurations); the others return concrete model types (narrowed via PHP covariance) for caller convenience. Refs: RFC-WS-6.md §3 (Q9) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,12 @@ final class PurposeRegistry
|
||||
/** @var array<string, PurposeGuardProvider> */
|
||||
private array $guardProviderCache = [];
|
||||
|
||||
/** @var array<string, class-string<PurposeSubjectResolver>>|null */
|
||||
private ?array $resolverClassCache = null;
|
||||
|
||||
/** @var array<string, PurposeSubjectResolver> */
|
||||
private array $resolverInstanceCache = [];
|
||||
|
||||
public function __construct(private readonly ConfigRepository $config) {}
|
||||
|
||||
/** @return array<string, PurposeDefinition> keyed by slug */
|
||||
@@ -33,6 +39,7 @@ final class PurposeRegistry
|
||||
|
||||
$definitions = [];
|
||||
$guardClasses = [];
|
||||
$resolverClasses = [];
|
||||
foreach ($raw as $slug => $attrs) {
|
||||
$mode = $attrs['default_submission_mode'] ?? null;
|
||||
if (! $mode instanceof FormSubmissionMode) {
|
||||
@@ -48,6 +55,13 @@ final class PurposeRegistry
|
||||
);
|
||||
}
|
||||
|
||||
$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'] ?? ''),
|
||||
@@ -57,9 +71,11 @@ final class PurposeRegistry
|
||||
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;
|
||||
}
|
||||
@@ -84,6 +100,26 @@ final class PurposeRegistry
|
||||
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<string, class-string<PurposeSubjectResolver>> $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();
|
||||
|
||||
Reference in New Issue
Block a user