Orchestrates per-purpose subject resolution + binding conflict resolution + per-binding writes per RFC Q4/Q7/Q9. Per-binding failures captured in BindingPassResult, not thrown — partial failures are expected and recoverable. Catastrophic failures (no transaction, unknown purpose, missing schema) throw FormBindingApplicatorException and bubble. Per-strategy null-winner matrix implemented via a NO_OP sentinel: overwrite=write null, append=noop, replace=conditional, first_write_wins= write only into null target. Append is collection-only with set-merge semantics (deduplicated array_merge). Identity-key bindings are skipped during apply — the subject resolver already used them for lookup/provisioning; re-writing is a no-op or a clobber. Activity log hierarchical: one bindings_pass_completed parent + N binding_applied children with parent_activity_id linkage (RFC Q12). Failed bindings get error_class/error_message in their activity entry in addition to their FormSubmissionActionFailure row (deliberate dual source of truth). Refs: RFC-WS-6.md §3 (Q4, Q7, Q9, Q12) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
24 lines
621 B
PHP
24 lines
621 B
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Exceptions\FormBuilder;
|
|
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* RFC-WS-6 §3 (Q3) — catastrophic applicator failure that bubbles to
|
|
* the caller. Per-binding failures are captured in BindingPassResult,
|
|
* not thrown.
|
|
*/
|
|
final class FormBindingApplicatorException extends RuntimeException
|
|
{
|
|
public function __construct(
|
|
public readonly string $reasonCode,
|
|
public readonly string $submissionId,
|
|
?string $message = null,
|
|
) {
|
|
parent::__construct($message ?? "FormBindingApplicator failed: {$reasonCode} (submission {$submissionId})");
|
|
}
|
|
}
|