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

@@ -26,6 +26,7 @@ final class FormFieldService
{
public function __construct(
private readonly FormSchemaService $schemaService,
private readonly FormFieldBindingService $bindingService,
) {}
public function create(FormSchema $schema, array $data): FormField
@@ -140,7 +141,6 @@ final class FormFieldService
'validation_rules' => $library->validation_rules,
'is_required' => (bool) $library->default_is_required,
'is_filterable' => (bool) $library->default_is_filterable,
'binding' => $library->default_binding,
'translations' => $library->translations,
'sort_order' => $this->nextSortOrder($schema),
], $overrides);
@@ -154,6 +154,8 @@ final class FormFieldService
/** @var FormField $field */
$field = FormField::create($data);
$this->bindingService->copyBindings($library, $field);
FormFieldLibrary::query()->whereKey($library->id)->increment('usage_count');
$this->schemaService->bumpVersion($schema);