collection Field Type

collection Field Type

This field type is used to render a "collection" of some field or form. In the easiest sense, it could be an array of text fields that populate an array emails field. In more complex examples, you can embed entire forms, which is useful when creating forms that expose one-to-many relationships (e.g. a product from where you can manage many related product photos).

Rendered as depends on the type option
Options
Inherited options
Parent type form
Class CollectionType

Note

If you are working with a collection of Doctrine entities, pay special attention to the allow_add, allow_delete and by_reference options. You can also see a complete example in the cookbook article How to Embed a Collection of Forms.

Basic Usage

This type is used when you want to manage a collection of similar items in a form. For example, suppose you have an emails field that corresponds to an array of email addresses. In the form, you want to expose each email address as its own input text box:

1
2
3
4
5
6
7
8
9
$builder->add('emails', 'collection', array(
    // each item in the array will be an "email" field
    'type'   => 'email',
    // these options are passed to each "email" type
    'options'  => array(
        'required'  => false,
        'attr'      => array('class' => 'email-box')
    ),
));

The simplest way to render this is all at once:

  • Twig
    1
    {{ form_row(form.emails) }}
    
  • PHP
    1
    <?php echo $view['form']->row($form['emails']) ?>
    

A much more flexible method would look like this:

  • Twig
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    {{ form_label(form.emails) }}
    {{ form_errors(form.emails) }}
    
    <ul>
    {% for emailField in form.emails %}
        <li>
            {{ form_errors(emailField) }}
            {{ form_widget(emailField) }}
        </li>
    {% endfor %}
    </ul>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <?php echo $view['form']->label($form['emails']) ?>
    <?php echo $view['form']->errors($form['emails']) ?>
    
    <ul>
    <?php foreach ($form['emails'] as $emailField): ?>
        <li>
            <?php echo $view['form']->errors($emailField) ?>
            <?php echo $view['form']->widget($emailField) ?>
        </li>
    <?php endforeach; ?>
    </ul>
    

In both cases, no input fields would render unless your emails data array already contained some emails.

In this simple example, it's still impossible to add new addresses or remove existing addresses. Adding new addresses is possible by using the allow_add option (and optionally the prototype option) (see example below). Removing emails from the emails array is possible with the allow_delete option.

Adding and Removing Items

If allow_add is set to true, then if any unrecognized items are submitted, they'll be added seamlessly to the array of items. This is great in theory, but takes a little bit more effort in practice to get the client-side JavaScript correct.

Following along with the previous example, suppose you start with two emails in the emails data array. In that case, two input fields will be rendered that will look something like this (depending on the name of your form):

1
2
<input type="email" id="form_emails_0" name="form[emails][0]" value="foo@foo.com" />
<input type="email" id="form_emails_1" name="form[emails][1]" value="bar@bar.com" />

To allow your user to add another email, just set allow_add to true and - via JavaScript - render another field with the name form[emails][2] (and so on for more and more fields).

To help make this easier, setting the prototype option to true allows you to render a "template" field, which you can then use in your JavaScript to help you dynamically create these new fields. A rendered prototype field will look like this:

1
<input type="email" id="form_emails___name__" name="form[emails][__name__]" value="" />

By replacing __name__ with some unique value (e.g. 2), you can build and insert new HTML fields into your form.

Using jQuery, a simple example might look like this. If you're rendering your collection fields all at once (e.g. form_row(form.emails)), then things are even easier because the data-prototype attribute is rendered automatically for you (with a slight difference - see note below) and all you need is the JavaScript:

  • Twig
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    {{ form_start(form) }}
        {# ... #}
    
        {# store the prototype on the data-prototype attribute #}
        <ul id="email-fields-list" data-prototype="{{ form_widget(form.emails.vars.prototype)|e }}">
        {% for emailField in form.emails %}
            <li>
                {{ form_errors(emailField) }}
                {{ form_widget(emailField) }}
            </li>
        {% endfor %}
        </ul>
    
        <a href="#" id="add-another-email">Add another email</a>
    
        {# ... #}
    {{ form_end(form) }}
    
    <script type="text/javascript">
        // keep track of how many email fields have been rendered
        var emailCount = '{{ form.emails|length }}';
    
        jQuery(document).ready(function() {
            jQuery('#add-another-email').click(function() {
                var emailList = jQuery('#email-fields-list');
    
                // grab the prototype template
                var newWidget = emailList.attr('data-prototype');
                // replace the "__name__" used in the id and name of the prototype
                // with a number that's unique to your emails
                // end name attribute looks like name="contact[emails][2]"
                newWidget = newWidget.replace(/__name__/g, emailCount);
                emailCount++;
    
                // create a new list element and add it to the list
                var newLi = jQuery('<li></li>').html(newWidget);
                newLi.appendTo(jQuery('#email-fields-list'));
    
                return false;
            });
        })
    </script>
    

Tip

If you're rendering the entire collection at once, then the prototype is automatically available on the data-prototype attribute of the element (e.g. div or table) that surrounds your collection. The only difference is that the entire "form row" is rendered for you, meaning you wouldn't have to wrap it in any container element as it was done above.

Field Options

allow_add

type: Boolean default: false

If set to true, then if unrecognized items are submitted to the collection, they will be added as new items. The ending array will contain the existing items as well as the new item that was in the submitted data. See the above example for more details.

The prototype option can be used to help render a prototype item that can be used - with JavaScript - to create new form items dynamically on the client side. For more information, see the above example and Allowing "new" Tags with the "Prototype".

Caution

If you're embedding entire other forms to reflect a one-to-many database relationship, you may need to manually ensure that the foreign key of these new objects is set correctly. If you're using Doctrine, this won't happen automatically. See the above link for more details.

allow_delete

type: Boolean default: false

If set to true, then if an existing item is not contained in the submitted data, it will be correctly absent from the final array of items. This means that you can implement a "delete" button via JavaScript which simply removes a form element from the DOM. When the user submits the form, its absence from the submitted data will mean that it's removed from the final array.

For more information, see Allowing Tags to be Removed.

Caution

Be careful when using this option when you're embedding a collection of objects. In this case, if any embedded forms are removed, they will correctly be missing from the final array of objects. However, depending on your application logic, when one of those objects is removed, you may want to delete it or at least remove its foreign key reference to the main object. None of this is handled automatically. For more information, see Allowing Tags to be Removed.

delete_empty

2.5The delete_empty option was introduced in Symfony 2.5.

type: Boolean default: false

If you want to explicitly remove entirely empty collection entries from your form you have to set this option to true. However, existing collection entries will only be deleted if you have the allow_delete option enabled. Otherwise the empty values will be kept.

options

type: array default: array()

This is the array that's passed to the form type specified in the type option. For example, if you used the choice type as your type option (e.g. for a collection of drop-down menus), then you'd need to at least pass the choices option to the underlying type:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$builder->add('favorite_cities', 'collection', array(
    'type'   => 'choice',
    'options'  => array(
        'choices'  => array(
            'nashville' => 'Nashville',
            'paris'     => 'Paris',
            'berlin'    => 'Berlin',
            'london'    => 'London',
        ),
    ),
));

prototype

type: Boolean default: true

This option is useful when using the allow_add option. If true (and if allow_add is also true), a special "prototype" attribute will be available so that you can render a "template" example on your page of what a new element should look like. The name attribute given to this element is __name__. This allows you to add a "add another" button via JavaScript which reads the prototype, replaces __name__ with some unique name or number, and render it inside your form. When submitted, it will be added to your underlying array due to the allow_add option.

The prototype field can be rendered via the prototype variable in the collection field:

  • Twig
    1
    {{ form_row(form.emails.vars.prototype) }}
    
  • PHP
    1
    <?php echo $view['form']->row($form['emails']->vars['prototype']) ?>
    

Note that all you really need is the "widget", but depending on how you're rendering your form, having the entire "form row" may be easier for you.

Tip

If you're rendering the entire collection field at once, then the prototype form row is automatically available on the data-prototype attribute of the element (e.g. div or table) that surrounds your collection.

For details on how to actually use this option, see the above example as well as Allowing "new" Tags with the "Prototype".

prototype_name

type: String default: __name__

If you have several collections in your form, or worse, nested collections you may want to change the placeholder so that unrelated placeholders are not replaced with the same value.

type

type: string or FormTypeInterface required

This is the field type for each item in this collection (e.g. text, choice, etc). For example, if you have an array of email addresses, you'd use the email type. If you want to embed a collection of some other form, create a new instance of your form type and pass it as this option.

Inherited Options

These options inherit from the form type. Not all options are listed here - only the most applicable to this type:

by_reference

type: Boolean default: true

In most cases, if you have a name field, then you expect setName() to be called on the underlying object. In some cases, however, setName() may not be called. Setting by_reference ensures that the setter is called in all cases.

To explain this further, here's a simple example:

1
2
3
4
5
6
7
8
$builder = $this->createFormBuilder($article);
$builder
    ->add('title', 'text')
    ->add(
        $builder->create('author', 'form', array('by_reference' => ?))
            ->add('name', 'text')
            ->add('email', 'email')
    )

If by_reference is true, the following takes place behind the scenes when you call submit() (or handleRequest()) on the form:

1
2
3
$article->setTitle('...');
$article->getAuthor()->setName('...');
$article->getAuthor()->setEmail('...');

Notice that setAuthor() is not called. The author is modified by reference.

If you set by_reference to false, submitting looks like this:

1
2
3
4
5
$article->setTitle('...');
$author = $article->getAuthor();
$author->setName('...');
$author->setEmail('...');
$article->setAuthor($author);

So, all that by_reference=false really does is force the framework to call the setter on the parent object.

Similarly, if you're using the collection form type where your underlying collection data is an object (like with Doctrine's ArrayCollection), then by_reference must be set to false if you need the setter (e.g. setAuthors()) to be called.

cascade_validation

type: Boolean default: false

Set this option to true to force validation on embedded form types. For example, if you have a ProductType with an embedded CategoryType, setting cascade_validation to true on ProductType will cause the data from CategoryType to also be validated.

Instead of using this option, you can also use the Valid constraint in your model to force validation on a child object stored on a property.

Tip

By default the error_bubbling option is enabled for the collection Field Type, which passes the errors to the parent form. If you want to attach the errors to the locations where they actually occur you have to set error_bubbling to false.

empty_data

type: mixed

The default value is array() (empty array).

This option determines what value the field will return when the submitted value is empty.

But you can customize this to your needs. For example, if you want the gender choice field to be explicitly set to null when no value is selected, you can do it like this:

1
2
3
4
5
6
7
8
9
$builder->add('gender', 'choice', array(
    'choices' => array(
        'm' => 'Male',
        'f' => 'Female'
    ),
    'required'    => false,
    'empty_value' => 'Choose your gender',
    'empty_data'  => null
));

Note

If you want to set the empty_data option for your entire form class, see the cookbook article How to Configure empty Data for a Form Class.

error_bubbling

type: Boolean default: true

If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.

error_mapping

type: array default: empty

This option allows you to modify the target of a validation error.

Imagine you have a custom method named matchingCityAndZipCode that validates whether the city and zip code match. Unfortunately, there is no "matchingCityAndZipCode" field in your form, so all that Symfony can do is display the error on top of the form.

With customized error mapping, you can do better: map the error to the city field so that it displays above it:

1
2
3
4
5
6
7
8
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'error_mapping' => array(
            'matchingCityAndZipCode' => 'city',
        ),
    ));
}

Here are the rules for the left and the right side of the mapping:

  • The left side contains property paths;
  • If the violation is generated on a property or method of a class, its path is simply propertyName;
  • If the violation is generated on an entry of an array or ArrayAccess object, the property path is [indexName];
  • You can construct nested property paths by concatenating them, separating properties by dots. For example: addresses[work].matchingCityAndZipCode;
  • The left side of the error mapping also accepts a dot ., which refers to the field itself. That means that any error added to the field is added to the given nested field instead;
  • The right side contains simply the names of fields in the form.

label

type: string default: The label is "guessed" from the field name

Sets the label that will be used when rendering the field. Setting to false will suppress the label. The label can also be directly set inside the template:

  • Twig
    1
    {{ form_label(form.name, 'Your name') }}
    
  • PHP
    1
    2
    3
    4
    echo $view['form']->label(
        $form['name'],
        'Your name'
    );
    

label_attr

type: array default: array()

Sets the HTML attributes for the <label> element, which will be used when rendering the label for the field. It's an associative array with HTML attribute as a key. This attributes can also be directly set inside the template:

  • Twig
    1
    {{ form_label(form.name, 'Your name', {'label_attr': {'class': 'CUSTOM_LABEL_CLASS'}}) }}
    
  • PHP
    1
    2
    3
    4
    5
    echo $view['form']->label(
        $form['name'],
        'Your name',
        array('label_attr' => array('class' => 'CUSTOM_LABEL_CLASS'))
    );
    

mapped

type: boolean default: true

If you wish the field to be ignored when reading or writing to the object, you can set the mapped option to false.

required

type: Boolean default: true

If true, an HTML5 required attribute will be rendered. The corresponding label will also render with a required class.

This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.

Note

The required option also affects how empty data for each field is handled. For more details, see the empty_data option.

Field Variables

Variable Type Usage
allow_add Boolean The value of the allow_add option.
allow_delete Boolean The value of the allow_delete option.

This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License .