feat(form-builder): form_field_bindings table + polymorphic owner + cascade observer
WS-5a commit 1 of 4 per ARCH-CONSOLIDATION-ADDENDUM-2026-04-24 Q3. Creates the relational home for what was form_fields.binding JSON and form_field_library.default_binding JSON. Owner discriminator is polymorphic morph (owner_type/owner_id) — the pattern the rest of WS-5 (5b validation_rules, 5d options) will reuse. Migration backfills rows from both JSON sources in a single transaction and is genuinely reversible (rollback reconstructs the JSON). Old columns remain in place until commit 3 has switched all readers. Pattern B (binding=null) is represented by absence of row. mode enum covers entity_owned / mirrored only. Cascade on owner delete via observer — bindings are physical state, not historical audit. FormFieldBindingScope enforces multi-tenancy via UNION over both owner chains (form_field → schema → org OR form_field_library → org) — Q2's declarative tenantScopeStrategy() can't walk morph parents. Tests: migration forward/back, morph relation, cascade observer, scope isolation, enum coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Enums\FormBuilder;
|
||||
|
||||
use App\Enums\FormBuilder\FormFieldBindingMergeStrategy;
|
||||
use App\Enums\FormBuilder\FormFieldBindingMode;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class FormFieldBindingEnumsTest extends TestCase
|
||||
{
|
||||
public function test_mode_has_expected_cases(): void
|
||||
{
|
||||
$values = array_map(fn ($case) => $case->value, FormFieldBindingMode::cases());
|
||||
$this->assertSame(['entity_owned', 'mirrored'], $values);
|
||||
}
|
||||
|
||||
public function test_mode_from_string(): void
|
||||
{
|
||||
$this->assertSame(FormFieldBindingMode::EntityOwned, FormFieldBindingMode::from('entity_owned'));
|
||||
$this->assertSame(FormFieldBindingMode::Mirrored, FormFieldBindingMode::from('mirrored'));
|
||||
}
|
||||
|
||||
public function test_mode_rejects_legacy_form_owned(): void
|
||||
{
|
||||
$this->expectException(\ValueError::class);
|
||||
FormFieldBindingMode::from('form_owned');
|
||||
}
|
||||
|
||||
public function test_merge_strategy_has_expected_cases(): void
|
||||
{
|
||||
$values = array_map(fn ($case) => $case->value, FormFieldBindingMergeStrategy::cases());
|
||||
sort($values);
|
||||
$this->assertSame(['append', 'first_write_wins', 'overwrite', 'replace'], $values);
|
||||
}
|
||||
|
||||
public function test_merge_strategy_from_string(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
FormFieldBindingMergeStrategy::Overwrite,
|
||||
FormFieldBindingMergeStrategy::from('overwrite'),
|
||||
);
|
||||
$this->assertSame(
|
||||
FormFieldBindingMergeStrategy::FirstWriteWins,
|
||||
FormFieldBindingMergeStrategy::from('first_write_wins'),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user