https://symfony.com/blog/new-in-symfony-7-4-request-class-improvements Ключевое: $value = $request->get('some_key'); #deprecated! $value = $request->query->get('some_key'); А также научились парсить тело запроса, даже если метод не POST. (При помощи request_parse_body)
https://symfony.com/blog/new-in-symfony-7-4-attribute-improvements #[Route('/api/login', name: 'api_login', methods: ['POST'])] public function index(#[CurrentUser] AdminUser|Customer $user): Response { // ... } Union типы теперь работают с CurrentUser. #[Route('/_debug/mail-preview', name: 'debug_mail_preview', env: ['dev', 'test'])] public function previewEmail(MailerInterface $mailer): Response { // ... } Можно определять роуты, которые в принципе доступны только в определенных окружениях. #[AsDecorator('api1.client')] #[AsDecorator('api2.client')] #[AsDecorator('api3.client')] class LoggableService implements HttpClientInterface Ранее AsDecorator можно было только один кидать. final class SomeListener { #[AsEventListener] public function doSomething(CustomEvent|AnotherCustomEvent $event): void { // ... } } Снова union. Теперь можно не писать в AsEventListener чего он там слушает. По параметрам определит. #[IsGranted('ROLE_ADMIN', methods: ['GET', 'POST'])] public function someAction() { // ... } Можно на разные методы вешать разные IsGranted. Ранее приходилось заводить несколько роутов.