Як створити розширення типу форми

Дата оновлення перекладу 2022-11-20

Як створити розширення типу форми

Розширення типів форми надзвичайно потужні: вони дозволяють вам змінювати будь-які існуючі типи полів форми по всій системі.

Вони мають 2 основних застосування:

  1. Ви хочете додати особливу функцію до єдиного типу форми (як додавання функції "завантажити" до типу поля FileType);
  2. Ви хочете додати спільную функцію до декількох типів (як додавання тексту "допомога" до кожного типу на кшталт "введення тексту").

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

Визначення розширення типу форми

Спочатку, створіть клас розширення типу форми, що розширюється з AbstractTypeExtension (ви можете натомість реалізувати FormTypeExtensionInterface, якщо хочете):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Form/Extension/ImageTypeExtension.php
namespace App\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FileType;

class ImageTypeExtension extends AbstractTypeExtension
{
    /**
     * Повертає масив розширених типів.
     */
    public static function getExtendedTypes(): iterable
    {
        // повернути [FormType::class], щоб змінити (майже) кожне поле у системі
        return [FileType::class];
    }
}

Єдиний метод, який ви повинні реалізувати - це getExtendedTypes(), який використовується для конфігурації того, які типи полів ви хочете змінити.

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

  • buildForm()
  • buildView()
  • configureOptions()
  • finishView()

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

Реєстрація вашого розширення типу форми в якості сервісу

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

Tip

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

Як тільки розширення буде зареєстроване, будь-який метод, який ви перевизначили (наприклад, buildForm()) буде викликаний кожний раз, коли будь-яке поле заданого типу (FileType) буде побудовано.

Tip

Виконайте наступну команду, щоб переконатися, що розширення типу форми було успішно зареєстроване у додатку:

1
$ php bin/console debug:form

Додавання розширень бізнес-логіки

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Entity/Media.php
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Media
{
    // ...

    /**
     * @var string The path - typically stored in the database
     */
    private $path;

    // ...

    public function getWebPath(): string
    {
        // ... $webPath як повний URL зображення, буде використаний у шаблоні

        return $webPath;
    }
}

Ваш клас розширення типу форми повинен буде зробити дві речі, щоб розширити тип форми FileType::class:

  1. Перевизначити метод configureOptions(), щоб будь-яке поле FileType могло мать опцію image_property;
  2. Перевизначити методи buildView(), щоб передати URL зображення до перегляду.

Наприклад:

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
// src/Form/Extension/ImageTypeExtension.php
namespace App\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\PropertyAccess\PropertyAccess;

class ImageTypeExtension extends AbstractTypeExtension
{
    public static function getExtendedTypes(): iterable
    {
        // повернути [FormType::class], щоб змінити (майже) всі поля у системі
        return [FileType::class];
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        // легалізує те, що поля FileType мають опцію image_property
        $resolver->setDefined(['image_property']);
    }

    public function buildView(FormView $view, FormInterface $form, array $options): void
    {
        if (isset($options['image_property'])) {
            // це буде тим класом/сутністю, який привʼязано до вашої форми (наприклад, Media)
            $parentData = $form->getParent()->getData();

            $imageUrl = null;
            if (null !== $parentData) {
                $accessor = PropertyAccess::createPropertyAccessor();
                $imageUrl = $accessor->getValue($parentData, $options['image_property']);
            }

            // встановлює змінну "image_url", яка буде доступна при відображенні цього поля
            $view->vars['image_url'] = $imageUrl;
        }
    }

}

Перевизначіть фрагмент шаблону віджета файлу

Кожний тип поля відображається фрагментом шаблону. Ці фрагменти шаблонів можуть бути перевизначені, щоб налаштувати відображення форми. Щоб дізнатися більше, ви можете звернутися до правил іменування фрагментів форми .

У вашому класі розширення ви додали нову змінну (image_url), але вам все ще потрібно скористатися перевагою цієї нової змінної у ваших шаблонах. Особливо вам потрібно перевизначити блок file_widget:

  • Twig
1
2
3
4
5
6
7
8
9
10
11
12
13
{# templates/form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}

{% block file_widget %}
    {% spaceless %}

    {{ block('form_widget') }}
    {% if image_url is not null %}
        <img src="{{ asset(image_url) }}"/>
    {% endif %}

    {% endspaceless %}
{% endblock %}

Обовʼязково сконфігуруйте шаблон теми цієї форми так, щоб система форм бачила його.

Використання розширення типу форми

Тепер, при додаванні типу поля FileType::class до вашої форми, ви можете вказати опцію image_property, яка буде використана для відображення зображення поруч з полем файлу. Наприклад:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Form/Type/MediaType.php
namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class MediaType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class)
            ->add('file', FileType::class, ['image_property' => 'webPath']);
    }
}

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

Спільні розширення типу форми

Ви можете змінювати декілька типів форми одночасно, вказавши їх спільного батька (Справочник типов форм). Наприклад, декілька типів форми наслідують з типу форми TextType (так як EmailType, SearchType, UrlType, і т.д.). Розширення типу форми, застосоване до TextType (тобто, метод getExtendedType() повертає TextType::class) буде застосовуватися до всіх типів цієї форми.

Таким же чином, так як більшість типів форми, доступних у Symfony наслідують з типу форми FormType, розширення типу форми, застосоване до FormType, буде застосоване до усіх (помітним виключенням є типи форми ButtonType). Також майте на увазі, що якщо ви створили (або використовуєте) користувацький тип форми, то може бути так, що він не розширює FormType, і тоді ваше розширення типу форми не можна буде застосовувати до нього.

Інша опція - повернути декілька типів форми в методі getExtendedTypes(), щоб розширити їх всі:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Form/Extension/DateTimeExtension.php
namespace App\Form\Extension;
// ...
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;

class DateTimeExtension extends AbstractTypeExtension
{
    // ...

    public static function getExtendedTypes(): iterable
    {
        return [DateTimeType::class, DateType::class, TimeType::class];
    }
}