feat(form-builder): ApplyBindings listener chain with two-transaction pattern (WS-6)

ApplyBindingsOnFormSubmit (sync) wraps the applicator in DB::transaction
and writes apply_status post-commit. On exception: outer catch records
FormSubmissionActionFailure in a separate transaction (survives inner
rollback), marks apply_status=failed, swallows so siblings keep running
(RFC Q3, Q4). When ApplyBindings provisions a Person on a previously
no-subject submission, the listener also writes subject_type/subject_id
back so TriggerPersonIdentityMatchOnFormSubmit (next sync listener) can
find the freshly-provisioned subject.

ApplyBindingsOnFormSectionSubmitted (queued, feature-flagged) ready
for ARTIST_ADVANCE activation per RFC Q10.

Listener chain on FormSubmissionSubmitted explicitly registered in
AppServiceProvider::boot for deterministic ordering (RFC Q1):
ApplyBindings → IdentityMatch → queued siblings.

FormBindingApplicator dropped 'final readonly' to 'class' so listener
tests can subclass it for throw-path coverage; constructor properties
remain readonly individually.

Refs: RFC-WS-6.md §3 (Q1, Q3, Q4, Q10)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-26 13:18:30 +02:00
parent 9f98a4fe1b
commit 6b5111ce43
8 changed files with 541 additions and 7 deletions

View File

@@ -27,13 +27,17 @@ use Throwable;
* - Q10: optional sectionId for future section-level apply.
* - Q12: hierarchical activity log via BindingActivityLogger.
*/
final readonly class FormBindingApplicator
// Not final + not readonly: listener tests need to override `apply()` for
// throw-path coverage (Mockery can't mock final classes; PHP doesn't allow
// extending readonly with non-readonly child). Properties stay readonly
// individually to preserve immutability.
class FormBindingApplicator
{
public function __construct(
private PurposeRegistry $purposeRegistry,
private BindingConflictResolver $conflictResolver,
private BindingTypeRegistry $typeRegistry,
private BindingActivityLogger $activityLogger,
private readonly PurposeRegistry $purposeRegistry,
private readonly BindingConflictResolver $conflictResolver,
private readonly BindingTypeRegistry $typeRegistry,
private readonly BindingActivityLogger $activityLogger,
) {}
/**