Вышла новая версия Symfony 7.3
Вышла очередная минорная версия Symfony 7.3
Как и все минорные обновления Symfony поддерживает обратную совместимость.
А это означает, что вы сможете легко обновиться, ничего не меняя в своем коде.
Ниже перечислены некоторые изменения для этой версии:
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):
Григорий Стерин
2025-05-30 00:08Быстрее устанавливайте и изучайте.