feat(form-builder): FormFieldBindingService + library-to-field row copy + snapshot writer

WS-5a commit 2 of 4.

FormFieldBindingService owns all writes to the relational binding table.
Validation against config/form_binding.php entity-column registry lives here
(ARCH §6.2).

FormFieldService::insertFromLibrary now calls copyBindings instead of
hydrating JSON — the Q3 row-copy mandate. Library and field bindings share
the same table; insertion is a row-clone operation.

Snapshot writer (FormSubmissionService::buildSnapshot) serialises bindings
via toJsonShape so schema_snapshot JSON keeps its ARCH §4.6.1 / §6.3
contract. No snapshot format change.
API resources source binding output from the relational table via the same
serialiser — external shape preserved.

Tests: service transactional behaviour, copyBindings preservation,
snapshot parity, API resource parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 18:48:47 +02:00
parent af8a9da038
commit 6933e6d700
9 changed files with 712 additions and 5 deletions

View File

@@ -33,6 +33,7 @@ final class FormSubmissionService
public function __construct(
private readonly FormLocaleResolver $localeResolver,
private readonly FormValueService $valueService,
private readonly FormFieldBindingService $bindingService,
) {}
/**
@@ -199,7 +200,7 @@ final class FormSubmissionService
*/
private function buildSnapshot(FormSchema $schema): array
{
$schema->loadMissing(['fields', 'sections']);
$schema->loadMissing(['fields.bindings', 'sections']);
return [
'schema_version' => $schema->version,
@@ -235,7 +236,7 @@ final class FormSubmissionService
'is_required' => (bool) $f->is_required,
'is_filterable' => (bool) $f->is_filterable,
'is_pii' => (bool) $f->is_pii,
'binding' => $f->binding,
'binding' => $this->bindingService->toJsonShape($f->bindings->first()),
'conditional_logic' => $f->conditional_logic,
'translations' => $f->translations,
'value_storage_hint' => $f->value_storage_hint instanceof \BackedEnum ? $f->value_storage_hint->value : $f->value_storage_hint,