Skip to content

Relations and nested forms

Small Forms contains Entity Manager-aware paths for nested AbstractEntity input. These paths rely on Core 3 relation metadata and the entity factory methods newToOne() / newToMany().

To-one relation

Define both ORM and form metadata explicitly:

use Small\Forms\Form\Field\Type\SubFormType;
use Small\SwooleEntityManager\Entity\Attribute\ToOne;

#[ToOne(AddressManager::class, ['addressId' => 'id'])]
#[SubFormType(AddressEntity::class)]
private ?AddressEntity $address = null;

For a new relation supplied as an input array:

$form = FormBuilder::createFromAttributes($user)
    ->fillFromArray([
        'address' => [
            'street' => '1 Example Street',
            'city' => 'Avignon',
        ],
    ], $user);

Because $user is passed as the parent entity, Small Forms can resolve the address relation manager and call newEntity() rather than constructing AddressEntity without its required manager.

During hydration, AbstractForm::hydrate() detects newToOne() on the destination entity, asks it for a related entity, recursively hydrates the nested form, then assigns that entity to the relation property.

Existing to-one relation

When the destination relation is already initialized, Small Forms can hydrate the nested form into the existing related object. This is useful for controlled updates where the relation identity is already established.

Be explicit about whether relation replacement is allowed by the operation. A form should validate input shape; application logic should still decide which related entity may be edited or attached.

Arrays of nested objects

For non-ORM object lists:

#[ArrayType(new SubFormType(LineInput::class))]
private array $lines = [];

Input arrays become nested forms and hydrate recursively into the destination array/object type.

To-many Entity Manager relations

Core 3 normally represents to-many relations with EntityCollection:

#[ToMany(ProjectManager::class, ['id' => 'userId'])]
private ?EntityCollection $projects = null;

Small Forms 2.3.0 can create relation entities while filling input when the parent AbstractEntity is supplied. However, automatic hydration of a non-array collection currently discovers the collection item class through Small Collection's #[CheckItemClass] class attribute.

Core 3's stock EntityCollection instead validates values through CheckValueInterface and does not declare #[CheckItemClass]. Therefore automatic ArrayType(SubFormType(...)) hydration into the stock EntityCollection is not a reliable integration path in the current versions.

Stock EntityCollection

Validate the to-many payload with Small Forms, but map accepted items to $entity->newToMany($aliar) and an EntityCollection explicitly until the collection-hydration contracts are aligned.

A safe application-layer pattern is:

$linesForm = FormBuilder::createInlineForm()
    ->addField(
        'projects',
        new ArrayType(new SubFormType(ProjectInput::class)),
    )
    ->fillFromArray($payload);

$messages = new StringCollection();
$linesForm->validate($messages);

if ($messages->count() !== 0) {
    return;
}

$projects = new EntityCollection();
foreach ($linesForm->getFieldValue('projects') as $projectForm) {
    $project = $user->newToMany('projects');
    $projectForm->hydrate($project);
    $projects[] = $project;
}

// Assign through your entity API/property workflow, then persist as required.

This keeps Entity Manager in control of related-entity construction and avoids instantiating entities without managers.

Relation persistence

Hydration only builds/assigns object state. Persistence semantics still follow Entity Manager rules: persist the root/related entities or use persistence threads/transactions according to the graph operation you are implementing.

Small Forms does not decide transaction boundaries or cascade policy.