Поле CollectionType

Этот тип поля исполльзуется для отображения "коллекции" некотрого поля или формы. В самом простом смысле, он может быть массивом полей TextType, наполняющих значения массива emails. В более сложных примерах, вы можете встраивать целые формы, что полезно при создании форм, которые представляют отношения один-ко-многим (например, продукт, откуда вы можете управлять многими фото, свзяанными с продуктом).

Отображается как зависит от опции entry_type
Опции
Наследуемые опции
Родительский тип FormType
Класс CollectionType

Note

Если вы работаете с коллекцией сущностей Doctrine, обратите особое внимание на опции allow_add, allow_delete и by_reference. Вы также можете увидеть полный пример в статье How to Embed a Collection of Forms.

Базовое применение

Этот тип используется, когда вы хотите управлять коллекцией похожих объектов в форме. Например, представьте, что у вас есть поле emails, которое соответствует массиву электронных адресов. В форме вам надо выразить каждый адрес в виде собственного поля ввода:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
// ...

$builder->add('emails', CollectionType::class, array(
    // каждая запись в массиве будет полем "email"
    'entry_type'   => EmailType::class,
    // эти опции передаются каждому типу "email"
    'entry_options'  => array(
        'attr'      => array('class' => 'email-box')
    ),
));

Самый простой способ отобразить всё одномоментно:

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

Намного более гибкий метод будет выглядеть так:

  • 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>
    

В обоих случаях, поля ввода не будут отображены, разве что ваши массив данных emails уже не содержал некоторые электронные адреса.

В этом простом примере, всё ещё возможно добавлять новые адреса или удалять существующие. Добавление новых адресов возможно с использованием опции allow_add (и факультативно опции prototype) (см. пример ниже). Удаление электронных адресов из массива emails возможно с опцией allow_delete.

Жобавление и удаление объеётов

Если allow_add установлен, как true, то при отправке любых неопознанных объектов, они будут незаметно добавлены к массиву объектов. В теории это отлично, но на практике требует немного больше усилий, чтобы получить правильный клиентский JavaScript.

Следуя предыдущему примеру, представьте, что вы начинаете с двух адресов в массиве данных emails. В этом случае, будут отображены два поля ввода, которые будут выглядеть как-то так (в зависимости от имени вашей формы):

1
2
<input type="email" id="form_emails_0" name="form[emails][0]" value="[email protected]" />
<input type="email" id="form_emails_1" name="form[emails][1]" value="[email protected]" />

Чтобы разрешить вашему пользователю добавить ещё один адрес, просто установите allow_add, как true и через JavaScript отобразите другое поле с именем form[emails][2] (и так далее для большего количества полей).

Чтобы облегчить это, установка опции prototype, как true позволяет вам отобразить поле "шаблона", которое вы потом можете использовать в вашем JavaScript, чтобы помочь вам динамично создавать эти новые поля. Отображённое поле прототипа будет выглядеть так:

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

Заменив __name__ некоторым уникальным значением (например, 2), вы можете построить и вставить новые HTML-поля в вашу форму.

Используя jQuery, простой пример может выглядеть так. Если вы отображаете все ваши поля коллекции одновременно (например, form_row(form.emails)), то всё ещё проще, так как атрибут data-prototype отображается автоматически для вас (с небольшим отличием - см. ниже), и всё, что вам нужно - это 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
    43
    {{ form_start(form) }}
        {# ... #}
    
        {# сохранить прототип в атрибуте прототипа данных #}
        <ul id="email-fields-list"
            data-prototype="{{ form_widget(form.emails.vars.prototype)|e }}">
        {% для emailField в 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">
        // следить за тем, сколько полей электронных адресов было отображено
        var emailCount = '{{ form.emails|length }}';
    
        jQuery(document).ready(function() {
            jQuery('#add-another-email').click(function(e) {
                e.preventDefault();
    
                var emailList = jQuery('#email-fields-list');
    
                // возьмите шаблон прототипа
                var newWidget = emailList.attr('data-prototype');
                // замените "__name__", используемый в id и имени прототипа
                // номером, уникальным для ваших электронных адресов
                // и атрибут имени выглядит так name="contact[emails][2]"
                newWidget = newWidget.replace(/__name__/g, emailCount);
                emailCount++;
    
                // создать новый элемент списка и добавить его в список
                var newLi = jQuery('<li></li>').html(newWidget);
                newLi.appendTo(emailList);
            });
        })
    </script>
    

Tip

Если вы отображаете сразу целую коллекцию, то прототип автоматически доступен в атрибуте data-prototype элемента (например, div или table), который окружает вашу коллекцию. Единственное отличие заключается в том, что весь "ряд формы" отображается для вас, что означает, что вам не нужно будет заключать его в какой-либо элемент контейнера, как было сделано выше.

Опции поля

allow_add

тип: boolean по умолчанию: false

Если установлена, как true, то при отправке в коллекцию неопознанных объектов, они будут добавлены в качестве новых. Окончательный массив будет содержать существующие объекты,а также новый объект, который был в отправленных данных. См. пример выше, чтобы узнать больше.

Опция prototype может быть использована, чтобы помочь отобразить объект прототипа, который может быть использован с JavaScript для динамичного создания новых объектов формы на клиентской стороне. Чтобы узнать больше, см. пример выше и Разрешение "новых" тегов с помощью "прототипа".

Caution

Если вы встраиваете целые другие формы, чтобы отобразить отношение БД один-ко-многим, то вам может понадобиться вручную убедиться в том, что сторонний ключ этих новых объектов установлен правильно. Если вы используете Doctrine, то это не произойдёт автоматически. См. ссылку выше, чтобы узнать больше деталей.

allow_delete

тип: boolean по умолчанию: false

Если установлена, как true, то если существующий объект не содержится в отправленных данных, он будет правильно отсутствовать в итоговом массиве объектов. Это означает, что вы можете реализовать кнопку "удалить" через JavaScript, который просто удалит элемент формы из DOM. Когда пользователь отправляет форму, её отсутствие в отправленных данных будет означать, что она удалена из итогового массива.

Чтобы получить больше информации, см. Разрешение удаления тегов.

Caution

Будьте осторожны используя эту опцию, когда вы встраиваете коллекцию объектов. В этом случае, если удаляются любые встроенные формы, они будут правильно отсутствовать в итоговом массиве объектов. Однако, в зависимости от логики вашего приложения, когда один из этих объектов удаляется, вы можете захоеть удалить его или по крайней мере ссылку его стороннего ключа на главный объект. Ничего из этого не происходит автоматически. Чтобы узнать больше, см. Разрешение удаления тегов.

delete_empty

тип: Boolean по умолчанию: false

Если вы хотите ясно удалить абсолютно пустые записи коллекций из вашей формы, то вам нужно установить эту опцию, как "true". Однако, существующие записи коллекции будут удалены только, если у вас включена опция allow_delete. Иначе пустые значения будут оставлены.

entry_options

тип: array по умолчанию: array()

Это массив, который передаётся типу формы, указанному в опции entry_type. Например, если мы использовали ChoiceType в качестве вашей опции entry_type (например, для коллекции выпадающих меню), то вам нужно хотя бы передать опцию ``choices``основоположному типу:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
// ...

$builder->add('favorite_cities', CollectionType::class, array(
    'entry_type'   => ChoiceType::class,
    'entry_options'  => array(
        'choices'  => array(
            'Nashville' => 'nashville',
            'Paris'     => 'paris',
            'Berlin'    => 'berlin',
            'London'    => 'london',
        ),
    ),
));

entry_type

тип: string или FormTypeInterface обязательно

Это тип поля для каждого объекта в этой коллекции (например, TextType, ChoiceType, и т.л.). Например, если у вас есть массив адресов электронной почты, то вы будете использовать EmailType. Если вы хотите встроить коллекцию в какую-то другую форму, создайте новый экземпляр вашего типа формы и передайте его в качестве опции.

prototype

тип: boolean по умолчанию: true

Эта опция полезна при использовании опции allow_add. Если true (и если allow_add также true), будет доступен специальный атрибут "прототипа", чтобы вы могли отобразить пример "Шаблона" того, как должен выглядеть новый элемент на вашей странице. Атрибут name данный этому элементу - __name__. Это позволяет вам добавлять кнопку "добавить ещё" через JavaScript, который считывает прототип, заменяет __name__ некоторым уникальным именем или числом и отображает его внутри вашей формы. При отправке, он будет добавлен в ваш основоположный массив благодаря опции allow_add.

Поле прототипа может быть отображено через переменную prototype в поле коллекции:

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

Отметьте, что всё, что вам на самом деле нужно, это "виджет", но в зависимости от того, как вы отображаете вашу форму, наличие целого "Ряда формы" может быть легче для вас.

Tip

Если вы отображает целую коллекцию полей одновременно, то прототип ряда формы автоматически доступен в атрибуте data-prototype элемента (например, div или table), который окружает вашу коллекцию.

Чтобы узнать детали о том, как действительно использовать эту опцию, см. пример выше, а также Разрешение "новых" тегов с помощью "прототипа".

prototype_data

тип: mixed по умолчанию: null

Позвляет вам определять конкретные данные для прототипа. Каждый новый добавленный ряд будет изначально содержать данные, установленые этой опцией. По умолчанию, будут использованы данные, сконфигурированные для всех записей с опцией entry_options.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
// ...

$builder->add('tags', CollectionType::class, array(
    'entry_type' => TextType::class,
    'allow_add' => true,
    'prototype' => true,
    'prototype_data' => 'New Tag Placeholder',
));

prototype_name

тип: string по умолчанию: __name__

Если у вас есть несколько коллекций в вашей форме, или даже хуже, вложенные коллекции, вы можете захотеть изменить заполнитель так, чтобы несвязанные заполнители не заменялись те же значением.

Наследуемые оцпии

Эти опции наследуются из FormType. Не все опции указаны здесь - только наиболее применимые к данному типу:

by_reference

type: boolean default: true

In most cases, if you have an author field, then you expect setAuthor() to be called on the underlying object. In some cases, however, setAuthor() may not be called. Setting by_reference to false 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
 9
10
11
12
13
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
// ...

$builder = $this->createFormBuilder($article);
$builder
    ->add('title', TextType::class)
    ->add(
        $builder->create('author', FormType::class, array('by_reference' => ?))
            ->add('name', TextType::class)
            ->add('email', EmailType::class)
    )

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 = clone $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 CollectionType field 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 adder and remover (e.g. addAuthor() and removeAuthor()) to be called.

empty_data

type: mixed

Значение по умолчанию - array() (пустой массив).

This option determines what value the field will return when the submitted value is empty (or missing). It does not set an initial value if none is provided when the form is rendered in a view.

This means it helps you handling form submission with blank fields. For example, if you want the name field to be explicitly set to John Doe when no value is selected, you can do it like this:

1
2
3
4
$builder->add('name', null, array(
    'required'   => false,
    'empty_data' => 'John Doe',
));

This will still render an empty text box, but upon submission the John Doe value will be set. Use the data or placeholder options to show this initial value in the rendered form.

If a form is compound, you can set empty_data as an array, object or closure. See the How to Configure empty Data for a Form Class article for more details about these options.

Note

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

Caution

Form data transformers will still be applied to the empty_data value. This means that an empty string will be cast to null. Use a custom data transformer if you explicitly want to return the empty string.

error_bubbling

тип: boolean по умолчанию: 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: array()

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 configureOptions(OptionsResolver $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 right side contains simply the names of fields in the form.

By default, errors for any property that is not mapped will bubble up to the parent form. You can use the dot (.) on the left side to map errors of all unmapped properties to a particular field. For instance, to map all these errors to the city field, use:

1
2
3
4
5
$resolver->setDefaults(array(
    'error_mapping' => array(
        '.' => 'city',
    ),
));

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
    2
    3
    {{ 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'))
    );
    

label_format

type: string default: null

Configures the string used as the label of the field, in case the label option was not set. This is useful when using keyword translation messages.

If you're using keyword translation messages as labels, you often end up having multiple keyword messages for the same label (e.g. profile_address_street, invoice_address_street). This is because the label is build for each "path" to a field. To avoid duplicated keyword messages, you can configure the label format to a static value, like:

1
2
3
4
5
6
7
8
// ...
$profileFormBuilder->add('address', AddressType::class, array(
    'label_format' => 'form.address.%name%',
));

$invoiceFormBuilder->add('invoice', AddressType::class, array(
    'label_format' => 'form.address.%name%',
));

This option is inherited by the child types. With the code above, the label of the street field of both forms will use the form.address.street keyword message.

Two variables are available in the label format:

%id%
A unique identifier for the field, consisting of the complete path to the field and the field name (e.g. profile_address_street);
%name%
The field name (e.g. street).

The default value (null) results in a "humanized" version of the field name.

Note

The label_format option is evaluated in the form theme. Make sure to update your templates in case you customized form theming.

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.

Переменные поля

Переменная Тип Использование
allow_add boolean Значение опции allow_add.
allow_delete boolean Значение опции allow_delete.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.