Як працювати з темами форми

Дата оновлення перекладу 2024-05-27

Як працювати з темами форми

Ця стаття пояснює як використовувати будь-яку з наданих Symfony тем форми у вашому додатку, і як створювати ваші власні користувацькі теми форми.

Вбудовані теми форми Symfony

Symfony постачається з декількома вбудованими темами форми, які змушують ваші форми виглядати приголомшливо з використаннями найпопулярніших фреймворків CSS. Кожна тема визначається в одному шаблоні Twig, і вони підключаються в опції twig.form_themes :

Tip

Прочитайте статті про тему форми Symfony Bootstrap 4 і тему форми Symfony Bootstrap 5, щоб дізнатися про них більше.

Застосування тем до всіх форм

Форми Symfony за замовчуванням використовують тему form_div_layout.html.twig. Якщо ви хочете використовувати іншу тему для всіх форм вашого додатку, сконфігуруйте це в опції twig.form_themes:

1
2
3
4
# config/packages/twig.yaml
twig:
    form_themes: ['bootstrap_5_horizontal_layout.html.twig']
    # ...

Ви можете передати декілька тем цій опції, так як іноді теми форм лише перевизначають декілька елементів. Таким чином, якщо якась тема не перевизначає якийсь елемент, Symfony дивиться в інших темах.

Порядок тем в опції twig.form_themes важливий. Кожна тема перевизначає всі попередні теми, тому ви повинні розміщувати найважливіші теми наприкінці списку.

Застосування тем до однієї форми

Хоча в більшості випадків ви застосовуватимете теми форм голбально, вам може знадобитися застосувати тему до якоїсь конкреної форми. Ви можете зробити це за допомогою тегу Twig form_theme :

1
2
3
4
5
6
{# ця тема форми буде застосована лише до форми цього шаблону #}
{% form_theme form 'foundation_5_layout.html.twig' %}

{{ form_start(form) }}
    {# ... #}
{{ form_end(form) }}

Перший аргумент тегу form_theme (у цьому прикладі - form) є іменем змінної, яка зберігає обʼєкт перегляду форми. Другий аргумент - шлях шаблону Twig, який визначає тему форми.

Застосування декількох тем до однієї форми

Форма також може бути налаштована шляхом застосування декількох тем. Щоб зробити це, передайте шлях всіх шаблонів Twig у вигляді масиву, використовуючи ключове слово with (їх порядок важливий, так як кожна тема перевизначить всі попередні):

1
2
3
4
5
6
7
{# застосувати декілька тем форм, але лише до форми цього шаблону #}
{% form_theme form with [
    'foundation_5_layout.html.twig',
    'forms/my_custom_theme.html.twig'
] %}

{# ... #}

Застосування різних тем до дочірньої форми

Ви також можете застосувати тему форми до конкретної доньки вашої форми:

1
{% form_theme form.a_child_form 'form/my_custom_theme.html.twig' %}

Це корисно коли ви хочете мати користувацьку тему для вбудованої форми, що відрізняється від теми вашої основної форми. Вкажіть обидві ваші теми:

1
2
{% form_theme form 'form/my_custom_theme.html.twig' %}
{% form_theme form.a_child_form 'form/my_other_theme.html.twig' %}

Віключення глобальних тем для однієї форми

Глобальні теми форм, визначені у додатку, завжди застосовуються до всіх форм, навіть тих, які використовують тег form_theme для застосування власної теми. Ви можете захотіти відключити це, наприклад, при створенні інтерфейсу адміна для пакету, який може бути встановлений в інших додатках Symfony (і ви не зможете котролювати, які теми будуть включені глобально). Щоб зробити це, додайте ключове слово only після списку тем форми:

1
2
3
{% form_theme form with ['foundation_5_layout.html.twig'] only %}

{# ... #}

Caution

При використання ключового слова only, жодна з вбудованих тем форми Symfony (form_div_layout.html.twig та ін.) не буде застосована. Щоб відобразити ваші форми коректно, вам потрібно або надати повністю функціонуючу тему форми самостійно, або розширювати одну з вбудованих тем форми за допомогою ключового слова Twig замість extends, щоб повторно використати зміст початкової теми.

1
2
3
4
{# templates/form/common.html.twig #}
{% use "form_div_layout.html.twig" %}

{# ... #}

Створення вашої власної теми форми

Symfony використовує блоки Twig, щоб відображати кожну частину форми - ярлики полів, помилки, текстові поля <input>, теги <select> і т.д. Тема - це шаблон Twig з одним або більше таких блоків, які ви хочете використовувати при відображенні форми.

Розгляньте, наприклад, поле форми, яке являє собою властивість цілого числа під назвою age. Якщо ви додасте це до шаблону:

1
{{ form_widget(form.age) }}

Згенерований HTML-зміст буде чимось типу цього (він буде відрізнятися в залежності від тем форми, включених у вашому додатку):

1
<input type="number" id="form_age" name="form[age]" required="required" value="33">

Symfony використовує блок Twig під назвою integer_widget, щоб відобразити це поле. Це тому, що тип поля - integer, і ви відображаєте його widget (а не його label або errors чи help). Перший крок створення теми форми - знати, який блок Twig перевизначити, як пояснюється у наступному розділі.

Іменування фрагмента форми

Використання фрагментів форми відрізняється в залежності від ваших потреб:

  • Якщо ви хочете налаштувати всі поля одного типу (наприклад, всі <textarea>), використовуйте патерн field-type_field-part (наприклад, textarea_widget).
  • Якщо ви хочете налаштувати лише одне конкретне поле (наприклад, <textarea>, використовуване для поля форми description, що редагує продукти), використовуйте патерн _field-id_field-part (наприклад, _product_description_widget).

В обох випадках, field-part може бути будь-якою з цих валідних частин поля форми:

Іменування фрагментів для всіх полів одного типу

Ці імена фрагментів дотримуються патерну type_part, де type відповідає типу відображуваного поля (наприклад, textarea, checkbox, date та ін.), а part - тому, що відображається (наприклад, label, widget і т.д.)

Декілька прикладів імен фрагментів:

  • form_row - використовується form_row() для відображення більшості полів;
  • textarea_widget - використовується form_widget() для відображення поля типу textarea;
  • form_errors - використовується form_errors() для відображення помилок поля;

Іменування фрагментів для окремих полів

Ці імена фрагментів дотримуються патерну _id_part, де id відповідає атрибуту поля id (наприклад, product_description, user_age та ін.), а part - тому, що відображується (наприклад, label, widget і т.д.).

Атрибут id містить як імʼя форми, так і імʼя поля (наприклад, product_price). Імʼя форми може бути встановлене вручну або згенероване автоматично, засновуючись на імені типу вашої форми (наприклад, ProductType прирівнюється до product). Якщо ви не впевнені, яке імʼя у вашої форми, подивіться в HTML-код, відображений для вашої форми. Ви також можете визначити це значення чітко з опцією block_name:

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

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    // ...

    $builder->add('name', TextType::class, [
        'block_name' => 'custom_name',
    ]);
}

У цьому прикладі, імʼя фрагмента буде _product_custom_name_widget замість імені за замовчуванням _product_name_widget.

Користувацьке іменування фрагментів для індивідуальних полів

Опція block_prefix дозволяє полям форми визначати власне користувацьке імʼя фрагмента. Це в основному корисно для налаштування деяких екземплярів одного і того ж поля, без необхідності створювати користувацький тип форми:

1
2
3
4
5
6
7
8
9
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder->add('name', TextType::class, [
        'block_prefix' => 'wrapped_text',
    ]);
}

Тепер ви можете використовувати wrapped_text_row, wrapped_text_widget та ін. в якості імен блоків.

Іменування фрагментів для колекцій

При використанні колекції форми, у вас є декіька способів налаштування колекції кожного з її записів. Спочатку використайте наступні блоки, щоб налаштувати кожну частину всіх колекцій форми:

1
2
3
4
5
{% block collection_row %} ... {% endblock %}
{% block collection_label %} ... {% endblock %}
{% block collection_widget %} ... {% endblock %}
{% block collection_help %} ... {% endblock %}
{% block collection_errors %} ... {% endblock %}

Ви також можете налаштувати кожний запис всіх колекцій з наступними блоками:

1
2
3
4
5
{% block collection_entry_row %} ... {% endblock %}
{% block collection_entry_label %} ... {% endblock %}
{% block collection_entry_widget %} ... {% endblock %}
{% block collection_entry_help %} ... {% endblock %}
{% block collection_entry_errors %} ... {% endblock %}

Нарешті, ви можете налаштувати конкретні колекції форми, а не всі. Наприклад, розгляньте наступний складний приклад, де TaskManagerType має колекцію TaskListType, яка, в свою чергу, має колекцію TaskType:

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
class TaskManagerType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options = []): void
    {
        // ...
        $builder->add('taskLists', CollectionType::class, [
            'entry_type' => TaskListType::class,
            'block_name' => 'task_lists',
        ]);
    }
}

class TaskListType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options = []): void
    {
        // ...
        $builder->add('tasks', CollectionType::class, [
            'entry_type' => TaskType::class,
        ]);
    }
}

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options = []): void
    {
        $builder->add('name');
        // ...
    }
}

Потім ви отримаєте всі наступні налаштовувані блоки (де * може бути змінений на row, widget, label, або help):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{% block _task_manager_task_lists_* %}
    {# поле колекції TaskManager #}
{% endblock %}

{% block _task_manager_task_lists_entry_* %}
    {# внутрішній TaskListType #}
{% endblock %}

{% block _task_manager_task_lists_entry_tasks_* %}
    {# поле колекції  TaskListType #}
{% endblock %}

{% block _task_manager_task_lists_entry_tasks_entry_* %}
    {# внутрішній TaskType #}
{% endblock %}

{% block _task_manager_task_lists_entry_tasks_entry_name_* %}
    {# поле TaskType #}
{% endblock %}

Наслідування фрагментів шаблону

Кожний тип поля має батьківський тип (наприклад, батьківський тип textarea - text, а батьківський тип text - form), і Symfony використовує фрагмент для батьківського типу, якщо базовий фрагмент не існує.

Коли Symfony відображає, наприклад, помилки длля типу textarea, вона спочатку шукає фрагмент textarea_errors, перед тим як звертатися до фрагментів text_errors та form_errors.

Tip

"Батьківський" тип кожного типу поля доступний у довіднику типів форми для кожного типу поля.

Створення теми форми у тому ж шаблоні, що і форма

Це рекомендується, коли проводиться налаштування для конкретної форми у вашому додатку, на кшталт зміни всіх елементів <textarea> у формі, або налаштування дуже особливого поля форми, яке буде оброблено за допомогою JavaScript.

Вам просто потрібно додати спеціальний {% form_theme form _self %} до того ж шаблону, де відображується форма. Це змушує Twig шукати всередині шаблону всі перевизначені блоки форми:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% extends 'base.html.twig' %}

{% form_theme form _self %}

{# це перевизначає віджет будь-якого поля типу цілого числа, але лише у
   формах, відображених всередині цього шаблону #}
{% block integer_widget %}
    <div class="...">
        {# ... відобразить HTML-елемент, щоб відобразити це поле ... #}
    </div>
{% endblock %}

{# це перевизначить весь ряд полів, чий "id" = "product_stock" (і чиє
   "name" = "product[stock]"), але лише у формах, відображених всередині цього шаблону #}
{% block _product_stock_row %}
    <div class="..." id="...">
        {# ... вібразити весь зміст поля, включно з його помилками ... #}
    </div>
{% endblock %}

{# ... відобразити форму ... #}

Головний недолік цього методу в тому, що він працює лише якщо ваш шаблон розширює інший (у попередньому прикладі - 'base.html.twig'). Якщо ваш шаблон цього не робить, ви повинні вказати form_theme на окремий шаблон, як пояснюється у наступному розділі.

Ще один недолік - це те, що налаштовані блоки форми не можуть бути використані повторно при відображенні інших форм в інших шаблонах. Якщо це те, що вам потрібно, створіть тему форми в окремому шаблоні, як пояснюється у наступному розділі.

Створення теми форми в окремому шаблоні

Це рекомендується при створенні тем форми, які використовуються у всьому вашому додатку, або навіть повторно використовуються в інших додатках Symfony. Вам просто потрібно створити десь шаблон Twig, та дотримуватись правил іменування фрагментів форми , щоб знати, які блоки Twig визначати.

Наприклад, якщо ваша тема форми проста, і ви лише хочете перевизначити елементи <input type="integer">, створіть такий шаблон:

1
2
3
4
5
6
{# templates/form/my_theme.html.twig #}
{% block integer_widget %}

    {# ... додайте весь необхідний HTML, CSS і JavaScript для відображення цього поля #}

{% endblock %}

Тепер вам потрібно сказати Symfony, щоб вона використовувала цю тему форми замість (або на додаток до) теми за замовчуванням. Як пояснювалося у попередніх розділах цієї статті, якщо ви хочете застосувати тему глобально до всіх форм, визначте опцію twig.form_themes:

1
2
3
4
# config/packages/twig.yaml
twig:
    form_themes: ['form/my_theme.html.twig']
    # ...

Якщо ви хочете застосувати її лише до деяких конкретних форм, використайте тег form_theme:

1
2
3
4
5
{% form_theme form 'form/my_theme.html.twig' %}

{{ form_start(form) }}
    {# ... #}
{{ form_end(form) }}

Повторне використання частин вбудованої теми форми

Створення повної теми форми вимагає багато роботи, так як існує дуже багато типів полів форми. Замість визначення всіх цих блоків Twig, ви можете визначити лише блоки, в яких ви зацікавлені, а потім сконфігурувати багато тем форми у вашому додатку або шаблоні. Це працює, так як при відображенні блоку, який не перевизначено у вашій користувацькій темі, Symfony резервно використовує інші теми.

Інше рішення - зробити так, щоб ваш шаблон теми форми розширювався з однієї із вбудованих тем, використовуючи тег Twig "use" замість тегу extends, щоб ви могли успадковувати всі його блоки (якщо ви не впевнені, розширте форму з теми за замовчуванням form_div_layout.html.twig):

1
2
3
4
{# templates/form/my_theme.html.twig #}
{% use 'form_div_layout.html.twig' %}

{# ... перевизначити лише блоки, в яких ви зацікавлені #}

Нарешті, ви можете також використати функцію Twig parent(), щоб повторно використати початковий зміст вбудованої теми. Це корисно, коли ви просто хочете зробити невеликі зміни, на кшталт огортання згенерованого HTML у деякий елемент:

1
2
3
4
5
6
7
8
{# templates/form/my_theme.html.twig #}
{% use 'form_div_layout.html.twig' %}

{% block integer_widget %}
    <div class="some-custom-class">
        {{ parent() }}
    </div>
{% endblock %}

Ця техніка також працює при визначенні теми форми у тому ж шаблоні, який відображає форму. Однак, імпортування цих блоків із вбудованих тем, трохи складніше:

1
2
3
4
5
6
7
8
9
10
11
12
13
{% form_theme form _self %}

{# імпортувати блок із вбудованої теми та переіменувати його, щоб він не конфліктував
   з тим же блоком в цьому шаблоні #}
{% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %}

{% block integer_widget %}
    <div class="some-custom-class">
        {{ block('base_integer_widget') }}
    </div>
{% endblock %}

{# ... render the form ... #}

Налаштування помилок валідації форми

Якщо ви визначаєте правила валідації для ваших обʼєктів, ви побачите деякі повідомлення про помилки валідації, якщо відправлені дані не будуть валідними. Ці повідомлення відображаються з функцією form_errors(), і можуть бути налаштовані з блоком Twig form_errors у будь-якій темі форми, як пояснювалося у попередніх розділах.

Важливо памʼятати про те, що деякі помилки асоціюються з усією формою, а не окремим полем. Для того, щоб розрізняти глобальні та локальні помилки, використайте одну зі змінних, доступних у формах, під назвою compound. Якщо вона true, це означає, що те, що зараз відображується - є колекцією полів (наприклад, ціла форма), а не просто окремим полем:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{# templates/form/my_theme.html.twig #}
{% block form_errors %}
    {% if errors|length > 0 %}
        {% if compound %}
            {# ... відобразити глобальні помилки форми #}
            <ul>
                {% for error in errors %}
                    <li>{{ error.message }}</li>
                {% endfor %}
            </ul>
        {% else %}
            {# ... відобразити помилки для одного поля #}
        {% endif %}
    {% endif %}
{% endblock form_errors %}