Skip to content

[Form] Support dependent fields #5807

@webmozart

Description

@webmozart

We long have the problem of creating fields that depend on the value of other fields now. See also:

I want to propose a solution that seems feasible from my current point of view.

Currently, I can think of two different APIs:

API 1
<?php

$builder->addIf(function (FormInterface $form) {
    return $form->get('field1')->getData() >= 1 
        && !$form->get('field2')->getData();
}, 'myfield', 'text');

$builder->addUnless(function (FormInterface $form) {
    return $form->get('field1')->getData() < 1 
        || $form->get('field2')->getData();
}, 'myfield', 'text');
API 2
<?php

$builder
    ->_if(function (FormInterface $form) {
        return $form->get('field1')->getData() >= 1 
            && !$form->get('field2')->getData();
    })
        ->add('myfield', 'text')
        ->add('myotherfield', 'text')
    ->_endif()
;

$builder
    ->_switch(function (FormInterface $form) {
        return $form->get('field1')->getData();
    })
    ->_case('foo')
    ->_case('bar')
        ->add('myfield', 'text', array('foo' => 'bar'))
        ->add('myotherfield', 'text')
    ->_case('baz')
        ->add('myfield', 'text', array('foo' => 'baz'))
    ->_default()
        ->add('myfield', 'text')
    ->_endswitch()
;

The second API obviously is a lot more expressive, but also a bit more complicated than the first one.

Please give me your opinions on what API you prefer or whether you can think of further limitations in these APIs.

Implementation

The issue of creating dependencies between fields can be solved by a lazy dependency resolution graph like in the OptionsResolver.

During form prepopulation, the conditions are invoked with a FormPrepopulator object implementing FormInterface. When FormPrepopulator::get('field') is called, "field" is prepopulated. If "field" is also dependent on some condition, that condition will be evaluated now in order to construct "field". After evaluating the condition, fields are added or removed accordingly.

During form binding, the conditions are invoked with a FormBinder object, that also implements FormInterface. This object works like FormPrepopulator, only that it binds the fields instead of filling them with default data.

In both cases, circular dependencies can be detected and reported.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DXDX = Developer eXperience (anything that improves the experience of using Symfony)FeatureForm

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions