<? phpukraine ЕКОСИСТЕМА
⌕Пошук по платформі
SYMFONY · ЩО НОВОГО

Що нового в Symfony 7.4

  1. 01LTS-реліз, що вийшов разом із 8.0: виправлення помилок до листопада 2028, безпекові патчі до листопада 2029.
  2. 02Головне в коді: form flows для форм у кілька кроків, #[MapInput] і #[Ask] в invokable-командах, ControllerHelper і зважені переходи у Workflow.
  3. 03XML-конфіги, fluent config builders і Request::get() застаріли та зникли у 8.0. Приберіть ці deprecations на 7.4, і перехід на 8.0 обійдеться без сюрпризів.
СТАТУС Активна підтримка
Реліз
27 листопада 2025
Останній патч
7.4.20
Активна підтримка до
30.11.2028
Безпека до
30.11.2029
НОВІ МОЖЛИВОСТІ · 7

Нові можливості

1.1 · Form

Багатокрокові форми

Реєстрацію чи оформлення замовлення на кілька екранів раніше збирали вручну або через сторонні бандли. Тепер кожен крок описується звичайним form type, номер поточного кроку живе у властивості обʼєкта даних, а кнопки «Назад», «Далі» і «Завершити» дає NavigatorFlowType. Назва кроку стає активною групою валідації, тож обмеження легко привʼязати до конкретного екрана.

+ SYMFONY 7.4
use Symfony\Component\Form\Flow\AbstractFlowType;
use Symfony\Component\Form\Flow\FormFlowBuilderInterface;
use Symfony\Component\Form\Flow\Type\NavigatorFlowType;

class UserSignUpType extends AbstractFlowType
{
    public function buildFormFlow(FormFlowBuilderInterface $builder, array $options): void
    {
        $builder->addStep('personal', PersonalType::class);
        $builder->addStep('account', AccountType::class);
        $builder->add('navigator', NavigatorFlowType::class);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => UserSignUp::class,
            'step_property_path' => 'currentStep',
        ]);
    }
}

// у контролері
$flow = $this->createForm(UserSignUpType::class, new UserSignUp())->handleRequest($request);

if ($flow->isSubmitted() && $flow->isValid() && $flow->isFinished()) {
    return $this->redirectToRoute('signup_success');
}

return $this->render('signup.html.twig', ['form' => $flow->getStepForm()]);
1.2 · Console

DTO, enum і #[Ask] у командах

Invokable-команди з 7.3 стали зручнішими. Аргументи й опції можна винести в DTO з #[MapInput], backed enum приймається напряму, а на невідоме значення Symfony сам покаже список допустимих. Атрибут #[Ask] питає відсутній обовʼязковий аргумент в інтерактивному режимі, а для складнішої логіки є #[Interact] на будь-якому публічному методі.

+ SYMFONY 7.4
class AddServerInput
{
    #[Argument]
    #[Ask('Регіон сервера')]
    public CloudRegion $region;

    #[Option]
    public ?ServerSize $size = null;
}

#[AsCommand('app:add-server')]
class AddServerCommand
{
    public function __invoke(#[MapInput] AddServerInput $input): int
    {
        // $input->region уже CloudRegion, $input->size уже ServerSize
        return Command::SUCCESS;
    }
}
− SYMFONY 6.4
#[AsCommand('app:add-server')]
class AddServerCommand
{
    public function __invoke(#[Argument] string $region, #[Option] ?int $size = null): int
    {
        $region = CloudRegion::from($region);
        $size = $size !== null ? ServerSize::from($size) : null;
        // ...

        return Command::SUCCESS;
    }
}
1.3 · HttpKernel

Share-директорія

Контейнер і маршрути швидше працюють з локального диска кожного сервера, а пули cache.app мають бути спільними для кластера, тому раніше на мережевий диск монтували весь var/cache. Тепер є share-директорія: метод getShareDir(), параметр %kernel.share_dir% і змінна APP_SHARE_DIR. Туди переїхали cache.app і HTTP-кеш, а без APP_SHARE_DIR шлях збігається з кеш-директорією, як і раніше.

+ SYMFONY 7.4
# .env (так у нових застосунках на 7.4)
APP_SHARE_DIR=$APP_PROJECT_DIR/var/share

# framework.cache.directory: '%kernel.share_dir%/pools/app'
# HTTP-кеш: '%kernel.share_dir%/http_cache'
# контейнер і маршрути лишаються в локальному var/cache
− SYMFONY 6.4
# framework.cache.directory: '%kernel.cache_dir%/pools/app'
# для кластера на спільний диск монтували весь var/cache
1.4 · HttpClient

Кешування відповідей у HttpClient

CachingHttpClient раніше спирався на HttpCache з HttpKernel і файлове сховище Store. Тепер він бере tag-aware пул компонента Cache, поводиться за RFC 9111 і вмикається ключем caching у налаштуваннях scoped-клієнта. Опція shared вирішує, чи можна віддавати кешовану відповідь різним користувачам, а max_ttl обмежує TTL, який задав сервер.

+ SYMFONY 7.4
# config/packages/framework.yaml
framework:
    http_client:
        scoped_clients:
            example.client:
                base_uri: 'https://example.com'
                caching:
                    cache_pool: example_cache_pool
                    max_ttl: 3600
    cache:
        pools:
            example_cache_pool:
                adapter: cache.adapter.redis_tag_aware
                tags: true
− SYMFONY 6.4
use Symfony\Component\HttpClient\CachingHttpClient;
use Symfony\Component\HttpKernel\HttpCache\Store;

$client = new CachingHttpClient(HttpClient::create(), new Store('/path/to/cache/'));
1.5 · Workflow

Зважені переходи у Workflow

У workflow обʼєкт буває в кількох місцях одночасно, але кожне місце було лише прапорцем «є або немає». Вага в from і to задає, скільки екземплярів перехід створює чи споживає, тож «зібрати чотири ніжки перед складанням столу» описується прямо в конфігу. Того ж циклу Workflow навчився працювати з backed enum як місцями і як значенням у MethodMarkingStore.

+ SYMFONY 7.4
# config/packages/workflow.yaml (places, supports і marking_store як зазвичай)
framework:
    workflows:
        make_table:
            type: 'workflow'
            transitions:
                start:
                    from: init
                    to:
                        - place: prepare_leg
                          weight: 4
                        - place: prepare_top
                          weight: 1
                build_leg:
                    from: prepare_leg
                    to: leg_created
                build_top:
                    from: prepare_top
                    to: top_created
                join:
                    from:
                        - place: leg_created
                          weight: 4
                        - top_created
                    to: finished
1.6 · FrameworkBundle

ControllerHelper

render(), redirectToRoute(), addFlash() та інші хелпери тепер є публічними методами сервісу ControllerHelper. Через #[AutowireMethodOf] контролер отримує лише потрібні методи, як замикання або як власний інтерфейс з __invoke(), що дає автодоповнення і статичний аналіз. Для більшості застосунків рекомендованим шляхом лишається AbstractController, а ControllerHelper знадобиться тим, хто пише контролери без привʼязки до фреймворку.

+ SYMFONY 7.4
use Symfony\Bundle\FrameworkBundle\Controller\ControllerHelper;
use Symfony\Component\DependencyInjection\Attribute\AutowireMethodOf;

class ProductController
{
    public function __construct(
        #[AutowireMethodOf(ControllerHelper::class)]
        private \Closure $render,
        #[AutowireMethodOf(ControllerHelper::class)]
        private \Closure $redirectToRoute,
    ) {
    }

    public function show(int $id): Response
    {
        if (!$id) {
            return ($this->redirectToRoute)('product_list');
        }

        return ($this->render)('product/show.html.twig', ['product_id' => $id]);
    }
}
− SYMFONY 6.4
class ProductController extends AbstractController
{
    public function show(int $id): Response
    {
        return $this->render('product/show.html.twig', ['product_id' => $id]);
    }
}
1.7 · Validator

Обмеження Video

Для картинок давно є Image, а відео доводилось перевіряти власним кодом. Video розширює File і додає роздільну здатність, співвідношення сторін, орієнтацію, кодеки та формати контейнера. Метадані читає ffprobe, тому на сервері потрібен FFmpeg.

+ SYMFONY 7.4
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Validator\Constraints as Assert;

class VideoContent
{
    #[Assert\Video(
        maxSize: '100M',
        maxWidth: 1920,
        maxHeight: 1080,
        allowPortrait: false,
        allowedCodecs: ['h264', 'hevc'],
        allowedContainers: ['mp4', 'webm'],
    )]
    private File $videoFile;
}
СИНТАКСИС · 3

Синтаксис

2.1 · DependencyInjection

PHP-конфіг масивами

PHP-конфігурацію тепер пишуть масивами тієї ж форми, що й YAML. Symfony генерує config/reference.php з array shapes для всіх встановлених бандлів, тож PhpStorm, PHPStan і Psalm підказують ключі та перевіряють типи. Fluent config builders на кшталт FrameworkConfig застаріли, а форматом за замовчуванням лишається YAML.

+ SYMFONY 7.4
// config/packages/framework.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'framework' => [
        'secret' => env('APP_SECRET'),
        'http_method_override' => true,
        'allowed_http_method_override' => ['PUT', 'PATCH', 'DELETE'],
    ],
]);
− SYMFONY 6.4
// config/packages/framework.php
use Symfony\Config\FrameworkConfig;

return static function (FrameworkConfig $framework): void {
    $framework
        ->httpMethodOverride(true)
        ->allowedHttpMethodOverride(['PUT', 'PATCH', 'DELETE']);
};
2.2 · Validator, Serializer

#[ExtendsValidationFor] і #[ExtendsSerializationFor]

Щоб додати валідацію чи групи серіалізації до класу зі стороннього бандла, раніше писали XML або YAML у теці config/. Тепер створюєте свій клас з атрибутом, повторюєте в ньому потрібні властивості та вішаєте на них звичні атрибути. Під час компіляції контейнера Symfony перевіряє, що ці властивості є в цільовому класі, і зливає метадані обох класів.

+ SYMFONY 7.4
use Acme\Bundle\UserRegistration;
use Symfony\Component\Serializer\Attribute\ExtendsSerializationFor;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Attribute\ExtendsValidationFor;
use Symfony\Component\Validator\Constraints as Assert;

#[ExtendsValidationFor(UserRegistration::class)]
abstract class UserRegistrationValidation
{
    #[Assert\NotBlank(groups: ['my_app'])]
    #[Assert\Length(min: 3, groups: ['my_app'])]
    public string $name = '';

    #[Assert\Email(groups: ['my_app'])]
    public string $email = '';
}

#[ExtendsSerializationFor(UserRegistration::class)]
abstract class UserRegistrationSerialization
{
    #[Groups(['my_app'])]
    public string $email = '';
}
2.3 · Security, Routing

#[IsGranted], #[Route] і #[IsSignatureValid]

#[IsGranted] отримав опцію methods і перевіряє доступ лише для вказаних HTTP-методів, а #[Route] приймає в env масив середовищ. Новий #[IsSignatureValid] перевіряє підпис URI ще до виклику контролера: невалідний підпис дає 404, прострочений 403. Крім того, #[AsDecorator] тепер можна повторювати, щоб один сервіс декорував кілька інших.

+ SYMFONY 7.4
use Symfony\Component\HttpKernel\Attribute\IsSignatureValid;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route('/_debug/invoice', env: ['dev', 'test'])]
public function preview(): Response { /* … */ }

#[Route('/invoices/{id}', methods: ['GET', 'POST'])]
#[IsGranted('ROLE_ACCOUNTANT', methods: 'POST')]
public function edit(int $id): Response { /* … */ }

#[Route('/invoices/{id}/pay')]
#[IsSignatureValid]
public function pay(int $id): Response { /* … */ }
− SYMFONY 6.4
#[Route('/_debug/invoice', env: 'dev')]
public function preview(): Response { /* … */ }

#[Route('/invoices/{id}/pay')]
public function pay(Request $request, UriSigner $signer): Response
{
    if (!$signer->checkRequest($request)) {
        throw $this->createNotFoundException();
    }
    // …
}
ФУНКЦІЇ · 2

Нові функції

3.1 · Uid

MockUuidFactory і UUID v7

Сервіс, що створює UUID через UuidFactory, тепер легко тестувати: MockUuidFactory по черзі віддає заданий список значень для create(), randomBased(), timeBased() і nameBased(). Сам UuidFactory без аргументів створює v7 замість v6, а мітка часу UUID v7 отримала мікросекундну точність, і генерація стала приблизно на 10% швидшою.

+ SYMFONY 7.4
use Symfony\Component\Uid\Factory\MockUuidFactory;
use Symfony\Component\Uid\UuidV4;

$factory = new MockUuidFactory([
    UuidV4::fromString('11111111-1111-4111-8111-111111111111'),
    UuidV4::fromString('22222222-2222-4222-8222-222222222222'),
]);
$service = new UserService($factory);

$this->assertSame('11111111-1111-4111-8111-111111111111', $service->createUserId());
− SYMFONY 6.4
// UUID випадковий, тож у тесті лишалось перевіряти хіба формат
$this->assertTrue(Uuid::isValid($service->createUserId()));
3.2 · Intl

Валюти за країною і датою

У списку ISO 4217 є німецька марка, ірландський фунт та інші валюти, які давно замінив євро, і в магазині чи білінгу вони лише заважають. Currencies::forCountry() повертає валюти країни, чинні на будь-яку дату, а isValidInCountry() перевіряє пару країна і валюта. CurrencyType отримав опції legal_tender, active_at і not_active_at та за замовчуванням показує тільки чинні платіжні засоби.

+ SYMFONY 7.4
use Symfony\Component\Intl\Currencies;

Currencies::forCountry('FR');              // ['EUR']
Currencies::isValidInCountry('CH', 'CHF'); // true

// повний список, як до 7.4
$builder->add('currency', CurrencyType::class, [
    'legal_tender' => null,
    'active_at' => null,
]);
ПРОДУКТИВНІСТЬ · 2

Продуктивність

4.1 · DomCrawler

Нативний HTML5-парсер

PHP 8.4 має повноцінний HTML5-парсер у класі \Dom\HTMLDocument. Symfony 7.4 використовує його в DomCrawler і HtmlSanitizer замість бібліотеки masterminds/html5, написаної на PHP, і це набагато швидше. Код міняти не треба, а на старіших версіях PHP усе працює як раніше.

+ SYMFONY 7.4
use Symfony\Component\DomCrawler\Crawler;

// на PHP 8.4+ усередині працює \Dom\HTMLDocument, код той самий
$crawler = new Crawler($html);
$title = $crawler->filter('h1')->text();

// вимикати HTML5-парсер на PHP 8.4 тепер deprecated
new Crawler($html, useHtml5Parser: false);
4.2 · Runtime

Worker mode FrankenPHP без окремого пакета

У worker mode FrankenPHP завантажує застосунок один раз і тримає його в памʼяті між запитами, що дає великий приріст швидкості. Раніше для цього ставили runtime/frankenphp-symfony, а тепер SymfonyRuntime сам бачить змінну FRANKENPHP_WORKER і вмикає FrankenPhpWorkerRunner. Скільки запитів воркер обробляє до перезапуску, задає FRANKENPHP_LOOP_MAX, за замовчуванням 500.

+ SYMFONY 7.4
composer remove runtime/frankenphp-symfony
# symfony/runtime 7.4 сам перемикається на FrankenPhpWorkerRunner,
# коли бачить FRANKENPHP_WORKER; якщо APP_RUNTIME вказував
# на клас зі старого пакета, приберіть і його
− SYMFONY 6.4
composer require runtime/frankenphp-symfony
БЕЗПЕКА · 2

Безпека

5.1 · Messenger

Підпис повідомлень Messenger

Якщо черга погано захищена, зловмисник може покласти в неї власне повідомлення, і воркер його виконає. З sign: true Symfony підписує повідомлення HMAC на основі kernel.secret, пише підпис у заголовки Body-Sign і Sign-Algo та перевіряє його при отриманні. Без підпису чи з неправильним підписом повідомлення не обробляється, а для RunProcessHandler підпис увімкнено за замовчуванням.

+ SYMFONY 7.4
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler(sign: true)]
class SmsNotificationHandler
{
    public function __invoke(SmsNotification $message): void { /* … */ }
}

// config/services.yaml, якщо без атрибута:
// tags: [{ name: messenger.message_handler, sign: true }]
− SYMFONY 6.4
#[AsMessageHandler]
class SmsNotificationHandler
{
    public function __invoke(SmsNotification $message): void { /* … */ }
}
5.2 · HttpFoundation

Обмежений HTTP method override

Підміна методу через параметр _method чи заголовок X-HTTP-METHOD-OVERRIDE потрібна HTML-формам для PUT і DELETE, але досі дозволяла підставити будь-який метод. Підміна на GET, HEAD, CONNECT і TRACE тепер deprecated, а 8.0 її ігнорує. Нова опція allowed_http_method_override задає список дозволених методів, порожній масив вимикає підміну повністю.

+ SYMFONY 7.4
# config/packages/framework.yaml
framework:
    http_method_override: true
    allowed_http_method_override: ['PUT', 'PATCH', 'DELETE']

# без FrameworkBundle:
# Request::setAllowedHttpMethodOverride(['PUT', 'PATCH', 'DELETE']);
DEPRECATED

Застаріло

Ще працює, але дає попередження і зникне в одній із наступних версій.

XML-конфігураціяДля сервісів, маршрутів і бандлів. У 8.0 XML не підтримується, переходьте на YAML, PHP або атрибути.
Fluent config buildersFrameworkConfig, SecurityConfig та інші класи Symfony\Config прибрано у 8.0. Повертайте масив через App::config().
Request::get()Видалено у 8.0. Беріть значення з $request->attributes, query чи request.
Method override на GET, HEAD, CONNECT і TRACESymfony 8.0 таку підміну просто ігнорує.
Application::add() у ConsoleУ 8.0 метода немає, реєструйте команди через addCommand(), він приймає й invokable-команди.
Простір імен Annotation у Routing і SerializerАліаси прибрано у 8.0, імпортуйте класи з простору Attribute.
Перелік варіантів першим аргументом ChoiceПишіть Choice(choices: [...]). Позиційний масив у 8.0 вже не спрацює.
Масив опцій у власних ConstraintІніціалізуйте властивості в конструкторі з #[HasNamedArguments]. Масив опцій і getRequiredOptions() у 8.0 більше не працюють.
__sleep() і __wakeup() у ядрі та data collectorsПерейдіть на __serialize() і __unserialize(), старі методи у 8.0 не підтримуються.
Вимкнений HTML5-парсер у DomCrawler і BrowserKitSymfony 8.0 завжди розбирає HTML нативним HTML5-парсером.
BREAKING

Ламає сумісність

Перевірте це до оновлення: такий код перестане працювати або поводитиметься інакше.

CurrencyTypeПоказує лише валюти, чинні сьогодні як законний платіжний засіб. Повний список повертають legal_tender і active_at зі значенням null.
$this у PHP-конфігахВ актуальних патчах 7.4 звернення до $this чи внутрішнього стану завантажувача кидає LogicException. Використовуйте змінну $loader.
cache.app у var/shareКоли задано APP_SHARE_DIR, як у нових рецептах, пули cache.app і HTTP-кеш пишуться туди. Тека має бути доступна на запис, а на кластері спільна.

Як оновитися

0 / 5