Всем привет!

Мы ежемесячно смотрим новости PHP и Laravel. Самое время поговорить о самом лучшем, самом совершенном, великолепном и бесподобном PHP-фреймворке современности - о Symfony! 

Это Кирилл Несмеянов и я постараюсь ежемесячно радовать вас обзором новостей по Symfony. Общаемся если что в тематическом чате в telegram.

Что ждет Symfony в ближайшее время

Ближайшие два месяца сделают нам подарок - в конце ноября выходит версия 7.2. Самое время обсудить новости за прошедший октябрь и рассказать о новом функционале и компонентах.

Несколько дней назад вышла версия 7.2 beta 1 (на момент написания статьи уже RC1), так что весь набор компонентов и функционала уже доступен для тестирования прямо сейчас.

Валидатор

Компонент валидатора предоставил нам 3 новых правила валидации (или 3 новых Assert’а, или Constraint’а, если использовать англицизмы).

Правило "Week" (неделя)

Проверяет переданную неделю на корректность у строки или Stringable-объекта согласно спецификации ISO 8601. В частности, этот формат применяется в соответствующем HTML-элементе <input type="week">.

Напомню, что стандартный календарный год может содержать 52 или 53 недели. Правило обеспечивает не только корректность формата, но и корректность проверки этого интервала для текущего календарного года согласно указанной локали и времени.

Конечно, вы можете указывать ограничения на допустимый интервал отдельно, без ограничений на минимально и максимально допустимые значения.

use Symfony\Component\Validator\Constraints as Assert;
final readonly class BanAccountRequest
{
    public function __construct(
        #[Assert\Week(min: '2024-W01', max: '2024-W20')]
        public string|\Stringable $week,
    ) {}
}

Правило "WordCount" (количество слов)

Как и любая проверка, требующая строку, может обрабатывать любые строки (внезапно!) и Stringable-объекты. 

Правило проверяет эту "строку" на количество слов. Однако подсчёт количества слов не совсем тривиальная задача и тесно связана с локалью. 

Нельзя просто так взять и подсчитать количество пробельных или пунктуационных символов. Для разных языков могут использоваться различные символы для разделения слов. И там, где в одном языке какой-то символ является разделителем, в другом он всего лишь диалектическая особенность. Вспомним, к примеру, перенос одного слова с помощью дефиса в русском языке.

В любом случае, правило полагается на уже готовое и широко применимое расширение intl, а конкретно класс IntlBreakIterator.

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

use Symfony\Component\Validator\Constraints as Assert;
final readonly class BanAccountRequest
{
    public function __construct(
        #[Assert\WordCount(min: 100, max: 200)]
        public string|\Stringable $lastAccountWish,
    ) {}
}

Правило YAML

Проверяет строку или Stringable-объект на соответствие YAML-формату. Кажется, что сложно придумать применение такому правилу валидации, однако бывают случаи, когда требуется сохранить, например, конфигурацию.

Конечно же, с помощью флагов мы можем указать дополнительные настройки для правил разбора YAML-формата.

use Symfony\Component\Validator\Constraints as Assert;
final readonly class UpdateConfigRequest
{
    public function __construct(
        #[Assert\Yaml]
        public string|\Stringable $config,
    ) {}
}

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-week-wordcount-and-yaml-constraints

Валидатор (часть 2)

Теперь можно объединять правила валидации в одно большое без написания агрегатов для обработчика. Достаточно отнаследоваться от класса Symfony\Component\Validator\Constraints\Compound и вернуть массив вложенных проверок.

#[\Attribute]
class BetterPasswordValidation extends Compound
{
    protected function getConstraints(array $options): array
    {
        return [
            new Assert\NotBlank(allowNull: false),
            new Assert\Length(min: 8, max: 255),
            new Assert\NotCompromisedPassword(),
            new Assert\Type('string'),
            new Assert\Regex('/[A-Z]+/'),
            // ...
        ];
    }
}

Правда, возвращать требуется именно массив, а не iterable. Это хорошая возможность для контрибьюта в Symfony, так как изменение типа на iterable не нарушит обратную совместимость.

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-compound-constraint-improvements

Улучшения консоли

Добавлен новый флаг "silent", работающий аналогично существующему "--quiet", однако исключающий из потока вывода не только логи и отладочную информацию, но и исключения и ошибки.

# Отключает весь "лишний вывод", исключая ошибки
php bin/console command-name --quiet

# Отключает весь "лишний вывод"
php bin/console command-name --silent

Это упростит систематизацию и агрегирование вывода, например в формате JSON, чтобы делегировать их сторонним сервисам и туда не попадало неструктурированных данных. Любители ELK-стека, возрадуйтесь!

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-silent-verbosity

Expression Language

Язык выражений используется для внедрения безопасных конфигурационных выражений, позволяющих императивно описывать правила внедрения зависимостей. Также императивно уточнять правила роутинга (например, для проверки наличия заголовка в запросе для соответствия какому-либо маршруту). Или, например, правил исключения полей DTO при сериализации их в JSON в том же JMS и прочего. Применяется довольно редко на практике, однако почему бы и нет.

use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
$el = new ExpressionLanguage();
$el->evaluate('1 << 4');  // result: 16
$el->evaluate('32 >> 4'); // result: 2
$el->evaluate('~5');      // result: -6
$el->evaluate('$a xor $b');  // true если $a или $b true, но не оба

Возвращаясь к новинкам, помимо существующих бинарных операций & (and), | (or) и ^ (xor) были добавлены операторы бинарных сдвигов, инвертирование битов (aka бинарный "NOT") и логический XOR.

В версии 7.2 также была добавлена поддержка комментариев в выражениях:

$expression = <<<'EXPRESSION'
    /* This expression checks that the purchase is made by
       a customer who doesn't belong to the regular users group */
    customer.group == 'vip_customers'
        or customer.id == 123 /* 123 is an internal test customer */
    EXPRESSION;

Вишенкой на торте является модификация сигнатуры конструктора для использования провайдеров iterable, а не только array:

app.some_context.expr_lang:
    class: Symfony\Component\ExpressionLanguage\ExpressionLanguage
    arguments:
        $providers: !tagged_iterator app.some_context.expr_lang_provider

Symfony грешит тем, что у многих сервисных компонентов требуются массивы, а не iterable, из-за чего невозможно передать tagged-итераторы или другие итераторы. Например, Application symfony консоли, куда в метод addCommands надо передать массив команд, а не итератор. Из-за этого приходится придумывать обходные пути.

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-expression-language-improvements

Messenger

Раньше для маршрутизации сообщения требовалось указать конкретный транспорт или несколько транспортов, куда данное сообщение будет направлено:

framework:
    messenger:
        transports:
            rabbitmq: "%env(MESSENGER_TRANSPORT_DSN)%"
        routing:
            'App\Message\SomeNotification': rabbitmq

Следуя современным трендам к упрощению сборки через атрибуты, в 7.2 был добавлен атрибут AsMessage:

#[AsMessage('rabbitmq')]
final readonly class SomeNotification {}

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-asmessage-attribute

Messenger (часть 2)

Для решения проблем с таймаутами добавлены:

1. Интерфейс KeepaliveReceiverInterface с методом keepalive

2. Событие ConsoleAlarmEvent для сигнала SIGALRM

3. Поддержка флага --keepalive для команд consume и retry

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-keepalive-messenger-transports

Symfony Serializer

В версии 7.2 появилась возможность создавать отдельные сериализаторы с полностью отдельными нормализаторами и настройками через секцию "named_serializers":

serializer:
    named_serializers:
        http_api_v1:
            name_converter: 'serializer.name_converter.camel_case_to_snake_case'
            default_context:
                enable_max_depth: true
        service_client_v42:
            default_context:
                enable_max_depth: false
                include_built_in_normalizers: false
                include_built_in_encoders: true

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

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-named-serializers

Переводы (Translation)

Symfony уже включает множество линтеров, таких как проверка синтаксиса конфигурации и корректности настроек контейнера. Ранее существовала команда проверки файлов переводов в формате XLIFF (OASIS стандарт файлов интернационализации, ISO 21720).

В версии 7.2 добавлен общий линтер для валидации файлов переводов в любом формате:

php bin/console lint:translations

# Или указываем конкретные локали
php bin/console lint:translations --locale=en --locale=ru

Особенно полезно при использовании синтаксиса ICU формата, где легко забыть запятую, закрывающую скобку или ключ "other" при использовании множественного числа:

   --------- -------------------------------- --------
    Locale    Domains                          Valid?
   --------- -------------------------------- --------
    ru        validators, security, messages   Yes
    en        validators, security, messages   Yes
    de        messages, validators             No
   --------- -------------------------------- --------

Symfony поддерживает команду "translation:extract" для автоматического извлечения переводов. В версии 7.2:

1. Добавлена опция "no-fill" - вместо заглушки вставляет пустую строку

2. В XLIFF-файлах пустая строка заменяет значение с двойным подчеркиванием

Помимо этого, опция sort теперь применяется к файлам каталога переводов.

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-translations-linter

Контейнер

Начиная с версии 5.3 можно указывать окружения для регистрации сервисов. Например, отладочная команда для локальной среды:

#[When(env: 'dev')]
#[AsCommand(name: 'test', description: 'Дропаем базу к чертям собачьим...')]
final class ExampleDebugCommand extends Command
{
   // ...
}

В 7.2 добавили атрибут WhenNot для регистрации сервиса на всех окружениях кроме указанного:

#[WhenNot(env: 'dev'), WhenNot(env: 'test')]
#[AsCommand(name: 'doctrine:fixtures:load', description: 'Защита от дураков')]
final class FixtureDisablerCommand extends Command
{
    // ...
}

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-whennot-attribute

Формы

Фреймворк предоставляет компонент EntityType для создания select-элементов, где значения берутся из сущности. Проблема в том, что при большом количестве значений браузер может зависнуть при отрисовке.

В Symfony 7.2 добавили опцию choice_lazy, позволяющую отключить принудительную отрисовку всех опций. За загрузку данных отвечает LazyChoiceLoader, который можно модифицировать.

В качестве альтернативы можно использовать поле автодополнения из набора компонентов Symfony UX, предоставляющее гибридную реактивную интеграцию бэкенда и фронтенда для простых случаев (например, админок).

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-lazy-choice-loader

Улучшения компонента работы со строками

Добавлен метод kebab - аналог snake_case, использующий дефисы вместо подчёркивания. Теперь вы можете официально "кебабить" в рабочее время и вам ничего за это не сделают!

use function Symfony\Component\String\u;
$string = u('symfonyIsGreat')->kebab();   // $string = 'symfony-is-great'
$string = u('Symfony is great')->kebab(); // $string = 'symfony-is-great'
$string = u('Symfony_is_Great')->kebab(); // $string = 'symfony-is-great'

Ранее можно было обрезать строку... Но не просто по буквам! Второй аргумент
"cut" позволял оставить в сохранности последнее слово.

u('Lorem Ipsum')->truncate(8);             // 'Lorem Ip'
u('Lorem Ipsum')->truncate(8, cut: false); // 'Lorem Ipsum'

Начиная с версии 7.2, помимо булева значения также доступны режимы

транкейта TruncateMode для "округления" результата обрезки в большую или меньшую сторону.

u('Lorem ipsum dolor')->truncate(8, cut: TruncateMode::Char);       // 'Lorem ip'
u('Lorem ipsum dolor')->truncate(8, cut: TruncateMode::WordBefore); // 'Lorem'
u('Lorem ipsum dolor')->truncate(8, cut: TruncateMode::WordAfter);  // 'Lorem ipsum'

Ну и последнее. Добавлен испанский инфлектор (склонятор слов). 

use Symfony\Component\String\Inflector\SpanishInflector;
$inflector = new SpanishInflector();
$inflector->singularize('aviones'); // returns ['avión']
$inflector->pluralize('miércoles'); // returns ['miércoles']

Теперь вы можете множить испанские слова без испанского стыда. Такие дела.

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-string-component-improvements

Нотификатор и Mailer

Добавлены новые драйверы и интеграции:

Для нотификатора:

- Primotexto

- Sipgate

- Sweego

- LINE Bot

Для Mailer:

- Mailchimp

- Sweego

- Mailomat

- Postal

- Mailtrap

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-mailer-and-notifier-integrations

Также для нотификаций

В симфони 7.2 добавлили драйвер нотификаций для... Рабочего стола!

Теперь вы можете слать нотификации прямиком на рабочий стол. Крайне удобно для какого-либо дебага. И крайне неудобно, если вы будете делать это на сервере.

use Symfony\Component\Notifier\Message\DesktopMessage;
$notification = new DesktopMessage(
    'Something Went Wrong',
    'Котаны, у вас там прод упал…',
);

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-desktop-notifications

Улучшения ядра (Framework Bundle)

Темплейт-контроллер теперь позволяет добавлять дополнительные заголовки ответа для статики:

welcome:
    path: /static/some-icon
    controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
    defaults:
        template: 'static/icons/some.svg.twig'
        headers:
            content-type: 'image/svg+xml'

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-template-dx-improvements

Symfony/Twig bridge

Для упрощения частичной отрисовки можно использовать методы-хелперы RenderBlock и RenderBlockView из AbstractController. Это удобно при использовании Symfony UX Turbo или HTMX. В версии 7.2 функционал упростили - достаточно вернуть данные с указанием имени блока и шаблона.

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-template-dx-improvements

Компонент конфигурации

Добавлен метод parameterCannotBeEmpty:

$container->parameterCannotBeEmpty(
    'app.private_key',
    'Value for "app.private_key" must be defined',
);

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-non-empty-container-parameters

Symfony MIME

Во-первых, ранее список энкодеров для объекта TextPart был жёстко захардкожен и представлял из себя 3 типа: “quoted-printable”, “base64” и “8bit”. Теперь можно добавлять пользовательские энкодеры, что потребует реализовать 3 метода.

class MyEncoder implements ContentEncoderInterface
{
    public function encodeByteStream(...): iterable { … }
    public function encodeString(...): string { … }
    public function getName(): string { … }
}

Зарегистрировать и использовать энкодер довольно просто:

TextPart::addEncoder(new MyEncoder());
$content = new TextPart('...', 'utf-8', 'plain', 'my_encoder');

Конечно, пихать всё возможное в статику - довольно странно, однако… Почему бы и нет?

Во-вторых, добавлена поддержка юникода для почты согласно RFC 6531. Не латинские символы теперь возможно использовать не только для имени почты, но и для домена. 

В новости об этом функционале ничего не сказано об автоматическом преобразовании таких имён в Punycode, который используется для обратной совместимости для юникодных имён. Так что имя в юникоде используется "как есть".

Подробнее: https://symfony.com/blog/new-in-symfony-7-2-mime-improvements

На этом новости о Symfony 7.2 за октябрь закончены

Дайджест в формате видео:

Кирилл Несмеянов

Комментарии (3)


  1. gruzoveek
    18.11.2024 07:21

    Отличное обновление!


  1. undersunich
    18.11.2024 07:21

    А laravel Вы что забросили? Не воспринимается как то подход Симфони после Ларавеля


    1. Cutcode Автор
      18.11.2024 07:21

      Laravel не забросили! Новое направление запустили. Будем развивать кругозор! Тем более, Кирилл утверждает что Symfony самый лучший фреймворк