Вышла новая версия Symfony 7.3

Дата публикации: 2025-05-29
Просмотры: 39

Вышла очередная минорная версия Symfony 7.3
Как и все минорные обновления Symfony поддерживает обратную совместимость.
А это означает, что вы сможете легко обновиться, ничего не меняя в своем коде.

Вышла новая версия Symfony 7.3

Ниже перечислены некоторые изменения для этой версии:

Invokable Commands and Input Attributes

Компонент Console — самый популярный пакет Symfony

Типичная команда, созданная с помощью Symfony Console, выглядит следующим образом:

// src/Command/CreateUserCommand.php
namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:create-user')]
class CreateUserCommand extends Command
{
    protected function configure(): void
    {
        $this->addArgument('name', InputArgument::REQUIRED);
        $this->addOption('activate', null, InputOption::VALUE_NONE);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $name = $input->getArgument('name');
        $activate = (bool) $input->getOption('activate');

        // ...

        return Command::SUCCESS;
    }
}

В Symfony 7.3 вы можете определить ту же самую команду следующим образом:

use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Option;
// ...

#[AsCommand(name: 'app:create-user')]
class CreateUserCommand
{
    public function __invoke(
        SymfonyStyle $io, #[Argument] string $name, #[Option] bool $activate = false,
    ): int
    {
        // ...

        return Command::SUCCESS;
    }
}

Основные изменения:

  • Вашему классу команды больше не нужно расширять базовый класс Symfony Command;
  • Вам не нужно переопределять configure() метод для определения параметров и аргументов команды;
  • Значения параметров и аргументов доступны напрямую как переменные, без необходимости вызова $input->getOption() или $input->getArgument().

Global Translation Parameters

Компонент Symfony Translation предоставляет инструменты для перевода вашего приложения.

Одной из его основных функций является перевод сообщений в PHP-коде или шаблонах Twig:

{{ message|trans({'%name%': 'Fabien'}) }}

В этом примере message — это переменная, содержащая фактическое сообщение, а также %name% заполнитель, используемый для вставки динамического содержимого.

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

Вот почему Symfony 7.3 вводит глобальные параметры перевода, которые можно использовать в любом сообщении перевода без их явной передачи. Сначала определите эти параметры с помощью новой translator.globals опции:

# config/packages/translator.yaml
translator:
    # ...
    globals:
        '{app_name}': 'My application'
        '{version}': '1.2.3'
        # the value van be a TranslatableMessage itself
        '{homepage}': { message: 'homepage_url', parameters: { show_scheme: false }, domain: 'global' }

После определения вы можете использовать эти параметры в сообщениях перевода в любом месте вашего приложения:

{{ 'Application version: {version}'|trans }}
{# output: "Application version: 1.2.3" #}

{# parameters passed explicitly override global parameters #}
{{ 'Package version: {version}'|trans({'{version}': '2.3.4'}) }}
{# output: "Package version: 2.3.4" #}

Configurable Compound Rate Limiter

Компонент Rate Limiter позволяет вам контролировать, как часто может происходить определенное событие. Он обычно используется для ограничения попыток входа, ограничения загрузки файлов, применения ограничений запросов в ваших API и многого другого.

Иногда ваши политики ограничения скорости могут быть сложными и требовать объединения нескольких отдельных политик.

В Symfony 7.3 улучшили этоn механизм с помощью настраиваемых ограничителей составных запросов. Например, чтобы настроить ограничитель скорости составных запросов для вашей контактной формы:

# config/packages/framework.yaml
framework:
    rate_limiter:
        two_per_minute:
            policy: 'fixed_window'
            limit: 2
            interval: '1 minute'
        five_per_hour:
            policy: 'fixed_window'
            limit: 5
            interval: '1 hour'
            lock_factory: null
        contact_form:
            policy: 'compound'
            limiters: [two_per_minute, five_per_hour]

При такой настройке пользователи могут отправлять до двух сообщений о контактах в минуту и не более пяти сообщений в течение часа. В вашем контроллере или службе введите этот ограничитель скорости, как и любой другой ограничитель:

public function contactAction(RateLimiterFactoryInterface $contactFormLimiter)
{
    // $contactFormLimiter is a CompoundRateLimiterFactory containing
    // "two_per_minute" & "five_per_hour"
}

JsonPath Component

Symfony 7.3 добавила новый компонент JsonPath, мощный новый инструмент для запроса и извлечения данных из JSON.

Возьмем следующий фрагмент JSON:

$json = <<<'JSON'
    {"store": {"book": [
        {"category": "reference", "author": "Nigel Rees", "title": "Sayings", "price": 8.95},
        {"category": "fiction", "author": "Evelyn Waugh", "title": "Sword", "price": 12.99}
    ]}}
    JSON;

Как и в случае с компонентом DomCrawler для обхода HTML/XML, вам сначала нужно будет создать сканер, прежде чем запрашивать JSON:

use Symfony\Component\JsonPath\JsonCrawler;

$crawler = new JsonCrawler($json);

Далее используйте find() метод для выполнения запросов:

// querying for a property of some item (e.g. to get the title of the first book)
$result = $crawler->find('$.store.book[0].title');
// outputs: ['Sayings']

Синтаксис запроса соответствует RFC 9535 (JSONPath: выражения запросов для JSON), предоставляя вам доступ к мощным функциям:

// extract descendants from items (e.g. to get all authors)
$result = $crawler->find('$..author');
// outputs: ['Nigel Rees', 'Evelyn Waugh']

// filter contents using expressions
$result = $crawler->find('$.store.book[?(@.price < 10)]');
// you can use functions like length(), count(), match(), etc.
$result = $crawler->find('$.store.book[?length(@.author) > 11]');
$result = $crawler->find('$.store.book[?match(@.author, "[A-Z].*el.+")]');

Предпочитаете ли вы строить запросы плавно? Компонент JsonPath также поддерживает программный, цепочечный API:

use Symfony\Component\JsonPath\JsonPath;

$path = new JsonPath();
$path = $path
    ->key('store')
        ->key('book')
            ->index(0)
                ->key('author');
$result = $crawler->find($path);
// outputs: ['Nigel Rees']

ObjectMapper Component

Во многих приложениях Symfony обычно используются объекты DTO для переноса данных между слоями. Например, вы можете создать DTO из сущности Doctrine, изменить его с помощью формы Symfony, а затем сопоставить обновленные данные обратно с сущностью.

Код для обратного преобразования DTO в сущность часто выглядит следующим образом:

$user = new User();
$user->name = $dto->name;
$user->email = $dto->email;
$user->roles = ['ROLE_USER'];
// ...

Это утомительно и чревато ошибками. Вот почему Symfony 7.3 представляет новый компонент ObjectMapper для передачи данных между объектами. С ним предыдущий пример становится намного проще:

// when creating a new object from another one
$user = $mapper->map($dto, User::class);

// when updating an existing object with another one
$mapper->map($dto, $user);

В этом примере $mapper — это основная служба сопоставления объектов, автоматически подключаемая к контроллерам и службам при указании типа для Symfony\Component\ObjectMapper\ObjectMapperInterface:

// src/Controller/UserController.php
namespace App\Controller;

use App\Dto\UserInput;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\ObjectMapper\ObjectMapperInterface;

class UserController extends AbstractController
{
    public function updateUser(UserInput $userInput, ObjectMapperInterface $objectMapper): Response
    {
        $user = new User();
        // map properties from UserInput to User
        $objectMapper->map($userInput, $user);

        // ... persist $user and return response
        return new Response('User updated!');
    }
}

ObjectMapper использует компонент PropertyAccess для установки или обновления значений свойств в целевом объекте. Вы также можете использовать #[Map] атрибут для явной настройки поведения отображения:

// src/Dto/UserDto.php
namespace App\Dto;

use App\Entity\User;
use Symfony\Component\ObjectMapper\Attribute\Map;

#[Map(target: User::class)]
class UserDto
{
    // 'target' refers to the name of this property in the target object
    #[Map(target: 'emailAddress')]
    public string $email = '';

    // 'if' allows to define expressions evaluated at runtime;
    // if the result is TRUE, the property is mapped; otherwise it's ignored
    #[Map(if: false)]
    public string $debugOnly = '';

    // 'transform' applies a PHP function or callback before mapping the value
    #[Map(transform: 'strtolower')]
    public string $username = '';
}

Этот новый компонент отмечен как экспериментальная функция в Symfony 7.3, поэтому он может развиваться, но уже сейчас он мощный, гибкий и готов к использованию во многих сценариях.


Об этих и других улучшениях вы можете почитать по ссылке ниже.
Ссылка на источник: https://symfony.com/blog/symfony-7-3-0-released

Предыдущая статья:

Битовые флаги - зачем это надо?
Поделиться статьей:

Комментарии (1):

Все комментарии проходят модерацию.
avatar
Новая версия со множеством интересных изменений.
Быстрее устанавливайте и изучайте.
Подписка на новости
Узнавайте о новых статьях первыми.
Профиль