<p dir="auto">Hi all,</p>
<p dir="auto">If there's something that bothers us is to create form to fill out polymorphic objects. Normally, when you have a polymorphic object, you first need to know the type and then build the form. Currently, there is no an elegant way to do this trick. You may create multiple embedded forms, or use two actions and do it in two steps. You also may use events to dinamically "merge" the form, but all events and options of the other form will be lost (for instance, if the CarType is set to cascade validation, and the TruckType is not, how can you solve it using events?), besides others drawbacks. Later, when you want to edit the object, you have another "problem": check the type of object and then instantiate the correct form. Of course, you can create a service, but would be very nice if this job was made by the factory automatically.</p>
<p dir="auto">After a long time thinking about, I think that the following pseudocode may be a good approach:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class Vehicle
{
    public $model;
}
class Car extends Vehicle {}
class Truck extends Vehicle {}

abstract class VehicleType extends AbstractType {}
class CartType extends VehicleType {} 
// 'data_class' =&gt; 'Acme\Bundle\VehicleBundle\Entity\Car'
class TruckType extends VehicleType {}
// 'data_class' =&gt; 'Acme\Bundle\VehicleBundle\Entity\Truck'
class MotocycleType extends VehicleType {}
// 'data_class' =&gt; 'Acme\Bundle\VehicleBundle\Entity\Motocycle'

class VehicleBuilderType extends VehicleType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder-&gt;add('type', 'choice', array(
                'choices'  =&gt; $options['inheritance_map'],
        ));

        // The model field ($vehicle-&gt;model) will be displayed on the first stage of the form (type selection) and will fill the field of the inherited form on bind
        parent::buildForm($builder, $options);
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'inheritance' =&gt; true,
            'inheritance_discriminator' =&gt; 'type',
            'inheritance_map' =&gt; array(
                'car'       =&gt; 'cart_type',
                'truck'     =&gt; 'truck_type',
                'motocycle' =&gt; 'motocycle_type',
        ));
    }
}

public function actionCreate()
{
    $form = $factory-&gt;create('vehicle_type');

    if ($request-&gt;isMethod('POST')) {
        $form-&gt;bind($request); // array('type' =&gt; 'car',  'wheels' =&gt; 4);
        echo get_class($form-&gt;getData()); // prints Acme\Bundle\VehicleBundle\Entity\Car
    }
}

public function actionEdit($id)
{
    $vehicle = new Car(); // load from database...
    $form = $factory-&gt;create('vehicle_type', $vehicle);
    echo get_class($form); // prints Acme\Bundle\VehicleBundle\Form\CarType
}"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">Vehicle</span>
{
    <span class="pl-k">public</span> <span class="pl-c1"><span class="pl-c1">$</span>model</span>;
}
<span class="pl-k">class</span> <span class="pl-v">Car</span> <span class="pl-k">extends</span> <span class="pl-v">Vehicle</span> {}
<span class="pl-k">class</span> <span class="pl-v">Truck</span> <span class="pl-k">extends</span> <span class="pl-v">Vehicle</span> {}

<span class="pl-k">abstract</span> <span class="pl-k">class</span> <span class="pl-v">VehicleType</span> <span class="pl-k">extends</span> <span class="pl-v">AbstractType</span> {}
<span class="pl-k">class</span> <span class="pl-v">CartType</span> <span class="pl-k">extends</span> <span class="pl-v">VehicleType</span> {} 
<span class="pl-c">// 'data_class' =&gt; 'Acme\Bundle\VehicleBundle\Entity\Car'</span>
<span class="pl-k">class</span> <span class="pl-v">TruckType</span> <span class="pl-k">extends</span> <span class="pl-v">VehicleType</span> {}
<span class="pl-c">// 'data_class' =&gt; 'Acme\Bundle\VehicleBundle\Entity\Truck'</span>
<span class="pl-k">class</span> <span class="pl-v">MotocycleType</span> <span class="pl-k">extends</span> <span class="pl-v">VehicleType</span> {}
<span class="pl-c">// 'data_class' =&gt; 'Acme\Bundle\VehicleBundle\Entity\Motocycle'</span>

<span class="pl-k">class</span> <span class="pl-v">VehicleBuilderType</span> <span class="pl-k">extends</span> <span class="pl-v">VehicleType</span>
{
    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">buildForm</span>(<span class="pl-smi"><span class="pl-smi">FormBuilderInterface</span></span> <span class="pl-s1"><span class="pl-c1">$</span>builder</span>, <span class="pl-smi">array</span> <span class="pl-s1"><span class="pl-c1">$</span>options</span>)
    {
        <span class="pl-s1"><span class="pl-c1">$</span>builder</span>-&gt;<span class="pl-en">add</span>(<span class="pl-s">'type'</span>, <span class="pl-s">'choice'</span>, <span class="pl-en">array</span>(
                <span class="pl-s">'choices'</span>  =&gt; <span class="pl-s1"><span class="pl-c1">$</span>options</span>[<span class="pl-s">'inheritance_map'</span>],
        ));

        <span class="pl-c">// The model field ($vehicle-&gt;model) will be displayed on the first stage of the form (type selection) and will fill the field of the inherited form on bind</span>
        <span class="pl-smi">parent</span>::<span class="pl-en">buildForm</span>(<span class="pl-s1"><span class="pl-c1">$</span>builder</span>, <span class="pl-s1"><span class="pl-c1">$</span>options</span>);
    }

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">getDefaultOptions</span>(<span class="pl-smi">array</span> <span class="pl-s1"><span class="pl-c1">$</span>options</span>)
    {
        <span class="pl-k">return</span> <span class="pl-en">array</span>(
            <span class="pl-s">'inheritance'</span> =&gt; <span class="pl-c1">true</span>,
            <span class="pl-s">'inheritance_discriminator'</span> =&gt; <span class="pl-s">'type'</span>,
            <span class="pl-s">'inheritance_map'</span> =&gt; <span class="pl-en">array</span>(
                <span class="pl-s">'car'</span>       =&gt; <span class="pl-s">'cart_type'</span>,
                <span class="pl-s">'truck'</span>     =&gt; <span class="pl-s">'truck_type'</span>,
                <span class="pl-s">'motocycle'</span> =&gt; <span class="pl-s">'motocycle_type'</span>,
        ));
    }
}

<span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">actionCreate</span>()
{
    <span class="pl-s1"><span class="pl-c1">$</span>form</span> = <span class="pl-s1"><span class="pl-c1">$</span>factory</span>-&gt;<span class="pl-en">create</span>(<span class="pl-s">'vehicle_type'</span>);

    <span class="pl-k">if</span> (<span class="pl-s1"><span class="pl-c1">$</span>request</span>-&gt;<span class="pl-en">isMethod</span>(<span class="pl-s">'POST'</span>)) {
        <span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">bind</span>(<span class="pl-s1"><span class="pl-c1">$</span>request</span>); <span class="pl-c">// array('type' =&gt; 'car',  'wheels' =&gt; 4);</span>
        <span class="pl-k">echo</span> get_class(<span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">getData</span>()); <span class="pl-c">// prints Acme\Bundle\VehicleBundle\Entity\Car</span>
    }
}

<span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">actionEdit</span>(<span class="pl-s1"><span class="pl-c1">$</span>id</span>)
{
    <span class="pl-s1"><span class="pl-c1">$</span>vehicle</span> = <span class="pl-k">new</span> <span class="pl-v">Car</span>(); <span class="pl-c">// load from database...</span>
    <span class="pl-s1"><span class="pl-c1">$</span>form</span> = <span class="pl-s1"><span class="pl-c1">$</span>factory</span>-&gt;<span class="pl-en">create</span>(<span class="pl-s">'vehicle_type'</span>, <span class="pl-s1"><span class="pl-c1">$</span>vehicle</span>);
    <span class="pl-k">echo</span> get_class(<span class="pl-s1"><span class="pl-c1">$</span>form</span>); <span class="pl-c">// prints Acme\Bundle\VehicleBundle\Form\CarType</span>
}</pre></div>
<p dir="auto">For now, a quickly solution may be a data_class based class guessing in the FormFactory :</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="$factory-&gt;createForData(new Car()); // returns a CarType "><pre class="notranslate"><span class="pl-s1"><span class="pl-c1">$</span>factory</span>-&gt;<span class="pl-en">createForData</span>(<span class="pl-k">new</span> <span class="pl-v">Car</span>()); <span class="pl-c">// returns a CarType </span></pre></div>
<p dir="auto">What you think?</p>
<p dir="auto">We long have the problem of creating fields that depend on the value of other fields now. See also:</p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="3948579" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/3767" data-hovercard-type="issue" data-hovercard-url="/symfony/symfony/issues/3767/hovercard" href="https://github.com/symfony/symfony/issues/3767">#3767</a></li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="3954416" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/3768" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/3768/hovercard" href="https://github.com/symfony/symfony/pull/3768">#3768</a></li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="4993683" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/4548" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/4548/hovercard" href="https://github.com/symfony/symfony/pull/4548">#4548</a></li>
</ul>
<p dir="auto">I want to propose a solution that seems feasible from my current point of view.</p>
<p dir="auto">Currently, I can think of two different APIs:</p>
<h5 dir="auto">API 1</h5>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&lt;?php

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

$builder-&gt;addUnless(function (FormInterface $form) {
    return $form-&gt;get('field1')-&gt;getData() &lt; 1 
        || $form-&gt;get('field2')-&gt;getData();
}, 'myfield', 'text');"><pre class="notranslate"><span class="pl-ent">&lt;?php</span>

<span class="pl-s1"><span class="pl-c1">$</span>builder</span>-&gt;<span class="pl-en">addIf</span>(<span class="pl-k">function</span> (<span class="pl-smi"><span class="pl-smi">FormInterface</span></span> <span class="pl-s1"><span class="pl-c1">$</span>form</span>) {
    <span class="pl-k">return</span> <span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">get</span>(<span class="pl-s">'field1'</span>)-&gt;<span class="pl-en">getData</span>() &gt;= <span class="pl-c1">1</span> 
        &amp;&amp; !<span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">get</span>(<span class="pl-s">'field2'</span>)-&gt;<span class="pl-en">getData</span>();
}, <span class="pl-s">'myfield'</span>, <span class="pl-s">'text'</span>);

<span class="pl-s1"><span class="pl-c1">$</span>builder</span>-&gt;<span class="pl-en">addUnless</span>(<span class="pl-k">function</span> (<span class="pl-smi"><span class="pl-smi">FormInterface</span></span> <span class="pl-s1"><span class="pl-c1">$</span>form</span>) {
    <span class="pl-k">return</span> <span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">get</span>(<span class="pl-s">'field1'</span>)-&gt;<span class="pl-en">getData</span>() &lt; <span class="pl-c1">1</span> 
        || <span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">get</span>(<span class="pl-s">'field2'</span>)-&gt;<span class="pl-en">getData</span>();
}, <span class="pl-s">'myfield'</span>, <span class="pl-s">'text'</span>);</pre></div>
<h5 dir="auto">API 2</h5>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&lt;?php

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

$builder
    -&gt;_switch(function (FormInterface $form) {
        return $form-&gt;get('field1')-&gt;getData();
    })
    -&gt;_case('foo')
    -&gt;_case('bar')
        -&gt;add('myfield', 'text', array('foo' =&gt; 'bar'))
        -&gt;add('myotherfield', 'text')
    -&gt;_case('baz')
        -&gt;add('myfield', 'text', array('foo' =&gt; 'baz'))
    -&gt;_default()
        -&gt;add('myfield', 'text')
    -&gt;_endswitch()
;"><pre class="notranslate"><span class="pl-ent">&lt;?php</span>

<span class="pl-s1"><span class="pl-c1">$</span>builder</span>
    -&gt;<span class="pl-en">_if</span>(<span class="pl-k">function</span> (<span class="pl-smi"><span class="pl-smi">FormInterface</span></span> <span class="pl-s1"><span class="pl-c1">$</span>form</span>) {
        <span class="pl-k">return</span> <span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">get</span>(<span class="pl-s">'field1'</span>)-&gt;<span class="pl-en">getData</span>() &gt;= <span class="pl-c1">1</span> 
            &amp;&amp; !<span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">get</span>(<span class="pl-s">'field2'</span>)-&gt;<span class="pl-en">getData</span>();
    })
        -&gt;<span class="pl-en">add</span>(<span class="pl-s">'myfield'</span>, <span class="pl-s">'text'</span>)
        -&gt;<span class="pl-en">add</span>(<span class="pl-s">'myotherfield'</span>, <span class="pl-s">'text'</span>)
    -&gt;<span class="pl-en">_endif</span>()
;

<span class="pl-s1"><span class="pl-c1">$</span>builder</span>
    -&gt;<span class="pl-en">_switch</span>(<span class="pl-k">function</span> (<span class="pl-smi"><span class="pl-smi">FormInterface</span></span> <span class="pl-s1"><span class="pl-c1">$</span>form</span>) {
        <span class="pl-k">return</span> <span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">get</span>(<span class="pl-s">'field1'</span>)-&gt;<span class="pl-en">getData</span>();
    })
    -&gt;<span class="pl-en">_case</span>(<span class="pl-s">'foo'</span>)
    -&gt;<span class="pl-en">_case</span>(<span class="pl-s">'bar'</span>)
        -&gt;<span class="pl-en">add</span>(<span class="pl-s">'myfield'</span>, <span class="pl-s">'text'</span>, <span class="pl-en">array</span>(<span class="pl-s">'foo'</span> =&gt; <span class="pl-s">'bar'</span>))
        -&gt;<span class="pl-en">add</span>(<span class="pl-s">'myotherfield'</span>, <span class="pl-s">'text'</span>)
    -&gt;<span class="pl-en">_case</span>(<span class="pl-s">'baz'</span>)
        -&gt;<span class="pl-en">add</span>(<span class="pl-s">'myfield'</span>, <span class="pl-s">'text'</span>, <span class="pl-en">array</span>(<span class="pl-s">'foo'</span> =&gt; <span class="pl-s">'baz'</span>))
    -&gt;<span class="pl-en">_default</span>()
        -&gt;<span class="pl-en">add</span>(<span class="pl-s">'myfield'</span>, <span class="pl-s">'text'</span>)
    -&gt;<span class="pl-en">_endswitch</span>()
;</pre></div>
<p dir="auto">The second API obviously is a lot more expressive, but also a bit more complicated than the first one.</p>
<p dir="auto">Please give me your opinions on what API you prefer or whether you can think of further limitations in these APIs.</p>
<h5 dir="auto">Implementation</h5>
<p dir="auto">The issue of creating dependencies between fields can be solved by a lazy dependency resolution graph like in the OptionsResolver.</p>
<p dir="auto">During form prepopulation, the conditions are invoked with a <code class="notranslate">FormPrepopulator</code> object implementing <code class="notranslate">FormInterface</code>. When <code class="notranslate">FormPrepopulator::get('field')</code> 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.</p>
<p dir="auto">During form binding, the conditions are invoked with a <code class="notranslate">FormBinder</code> object, that also implements <code class="notranslate">FormInterface</code>. This object works like <code class="notranslate">FormPrepopulator</code>, only that it binds the fields instead of filling them with default data.</p>
<p dir="auto">In both cases, circular dependencies can be detected and reported.</p>