Symfony 3 Ajax (EntityType) CollectionType

Objective: Create an efficient way to use a Symfony CollectionType of Entities. I wanted to be able to provide a Dijit FilteringSelect (autocomplete) for a collection of entities.

Issues: Using a CollectionType of EntityTypes loaded all the entities into a selection statement. This is fine if there are only a few entities, but not if there are hundreds. Also, model transformers cannot be used with CollectionTypes.

Solution: Create a custom FieldType and apply a model transformer.

The custom field type is based off a TextType, which allows the id of the entity to be passed as text, then transformed by the transformer. Transformers are described here.

The Dijit FilteringSelect only needs an id to serve as an anchor within the page layout. One may argue it could all be dynamic, but I like to use the HTML as a foundation. A text input is used as the base element which is replaced by the Dijit widget. The widget is created and populated with the data from a store.

<?php

namespace AppBundle\Form\Admin\Asset\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use AppBundle\Form\Admin\Asset\DataTransformer\ModelToIdTransformer;

class ModelRelationshipType extends AbstractType
{

    private $modelToIdTransformer;

    public function __construct( ModelToIdTransformer $modelToIdTransformer )
    {
        $this->modelToIdTransformer = $modelToIdTransformer;
    }

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm( FormBuilderInterface $builder, array $options )
    {

        $builder
                ->addModelTransformer( $this->modelToIdTransformer );
    }

    public function configureOptions( OptionsResolver $resolver )
    {
        // Stub
    }

    public function getParent()
    {
        return TextType::class;
    }

}

The new custom field type was then added as a service, with the transformer injected in:

    app.admin.field.type.model_relationship:
        class: AppBundle\Form\Admin\Asset\Type\ModelRelationshipType
        arguments: ['@app.form.data_transformer.model']
        tags:
            - { name: form.type, alias: 'app_model_relationship_type' }

The Twig template code to display each row of the collection uses a macro, because there are four of these fields.

{% block _model_extends_entry_row %}
{% import 'form/macros.html.twig' as form_macros %}
{{ form_macros.model_relationship_entry_row('requires',form) }}
{% endblock %}

{%macro relation(title,type,form) %}{%spaceless%}
        <div id="{{type}}" class="{{type}}">
            <h3>{{title}}</h3>
            {{form_row(form)}}
            {% if form.vars.allow_add %}
                <div data-type="{{type}}" class="add-one-more-row">{{ 'common.add_one_more'|trans}}
            {% endif %}
        </div>
{%endspaceless%}{%endmacro %}

To use the new field type in the form, you add the CollectionType, and the entry_type is ModelRelationshipType.

->add( 'requires', CollectionType::class, [
                    'entry_type' => ModelRelationshipType::class,
                    'required' => false,
                    'label' => false,
                    'empty_data' => null,
                    'allow_add' => true,
                    'allow_delete' => true,
                    'delete_empty' => true
                ] )