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

Що нового в Symfony 6.4

  1. 01LTS-реліз: виправлення помилок до листопада 2026 року, патчі безпеки до листопада 2027-го.
  2. 02Можливості ті самі, що в Symfony 7.0, який вийшов одночасно з ним. Різниця в тому, що 6.4 потребує PHP 8.1 замість 8.2 і ще тримає застарілий код, лише попереджаючи про deprecation.
  3. 03Це остання версія гілки 6.x, тож план переходу на 7.x починається тут: у 7.0 застарілий код уже видалено, і кожне попередження з 6.4 треба прибрати заздалегідь.
СТАТУС Активна підтримка
Реліз
29 листопада 2023
Останній патч
6.4.47
Активна підтримка до
30.11.2026
Безпека до
30.11.2027
НОВІ МОЖЛИВОСТІ · 6

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

1.1 · Console

Профайлер для консольних команд

Профайлер показував лише HTTP-запити, а повільну чи збійну команду доводилось розбирати по логах. Тепер будь-яка команда, зокрема ваша, приймає глобальну опцію --profile: у профілі видно аргументи, час і памʼять, слухачі подій, логи, deprecation і звичні панелі на кшталт запитів Doctrine. Потрібні debug-режим, увімкнений профайлер і компонент Stopwatch.

+ SYMFONY 6.4
php bin/console --profile app:import-vacancies

# з -v у виводі буде посилання на профіль, з -vvv ще й час та памʼять
php bin/console --profile -v app:import-vacancies
1.2 · DependencyInjection

#[AutowireLocator] і #[AutowireIterator]

Коли сервісу потрібні кілька обробників, а за виклик спрацьовує один, створювати всі одразу дорого, а інжектити весь контейнер погана практика. Раніше locator оголошували через ServiceSubscriberInterface або конфігурацію, тепер вистачає атрибута зі списком класів чи назвою тегу. Сервіс із ? на початку стає необовʼязковим, а #[AutowireIterator] замість locator дає iterable.

+ SYMFONY 6.4
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;

class CommandBus
{
    public function __construct(
        #[AutowireLocator([FooHandler::class, 'baz' => '?'.BazHandler::class])]
        private ContainerInterface $handlers,
        #[AutowireIterator('app.middleware')]
        private iterable $middlewares,
    ) {
    }
}
− SYMFONY 6.3
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

class CommandBus implements ServiceSubscriberInterface
{
    public function __construct(private ContainerInterface $locator)
    {
    }

    public static function getSubscribedServices(): array
    {
        return [FooHandler::class, BarHandler::class];
    }
}
1.3 · Messenger

Готові обробники для Messenger

Щоб запустити команду чи скрипт через чергу, раніше писали свій клас повідомлення й обробник до нього. Тепер FrameworkBundle сам реєструє обробники для RunCommandMessage, RunProcessMessage і PingWebhookMessage. Результат теж повертається: RunCommandContext і RunProcessContext з кодом виходу та виводом, а для пінга відповідь HTTP-клієнта.

+ SYMFONY 6.4
use Symfony\Component\Console\Messenger\RunCommandMessage;
use Symfony\Component\HttpClient\Messenger\PingWebhookMessage;
use Symfony\Component\Process\Messenger\RunProcessMessage;

$bus->dispatch(new RunCommandMessage('app:sitemap:generate --force'));
$bus->dispatch(new RunProcessMessage(['bin/import.sh'], cwd: '/srv/app'));
$bus->dispatch(new PingWebhookMessage('GET', 'https://example.com/', throw: false));
− SYMFONY 6.3
#[AsMessageHandler]
final class GenerateSitemapHandler
{
    public function __invoke(GenerateSitemap $message): void
    {
        // свій клас повідомлення, свій обробник, запуск команди вручну
    }
}
1.4 · Clock

DatePoint

new \DateTimeImmutable() завжди дає реальний час, тож тест із підміненим годинником його не контролює. DatePoint розширює DateTimeImmutable і бере «зараз» із глобального Clock, тому підходить усюди, де очікують DateTimeImmutable чи DateTimeInterface. Некоректна дата чи модифікатор кидають DateMalformedStringException замість false, навіть на PHP до 8.3.

+ SYMFONY 6.4
use Symfony\Component\Clock\DatePoint;

$createdAt = new DatePoint(); // час із Clock
$validUntil = new DatePoint('+1 month', reference: $createdAt);
$utcNow = new DatePoint(timezone: new \DateTimeZone('UTC'));
− SYMFONY 6.3
$createdAt = new \DateTimeImmutable(); // реальний час, тест його не зафіксує
1.5 · AssetMapper

AssetMapper без статусу experimental

AssetMapper зʼявився в 6.3 як спосіб підключати JavaScript і CSS без збірки та Node, а в 6.4 на нього поширилась обіцянка зворотної сумісності. importmap:require тепер завжди кладе пакет у assets/vendor/ замість посилання на jsDelivr, тож CSP може дозволяти ресурси лише з вашого домену. Для залежностей є importmap:audit, який шукає відомі вразливості й повертає ненульовий код, та importmap:outdated.

+ SYMFONY 6.4
php bin/console importmap:require bootstrap   # файли лягають у assets/vendor/
php bin/console importmap:install             # як composer install, але для assets/vendor/
php bin/console importmap:audit               # відомі вразливості в пакетах
php bin/console importmap:outdated            # пакети, для яких є новіші версії
− SYMFONY 6.3
# Symfony 6.3: в importmap.php потрапляє посилання на jsDelivr
php bin/console importmap:require bootstrap
1.6 · Process

PhpSubprocess

Якщо запустити php -d memory_limit=-1 bin/console app:import, а команда стартує підпроцес через Process, той візьме memory_limit з php.ini. PhpSubprocess передає дочірньому процесу конфігурацію батьківського і сам додає PHP-бінарник на початок команди.

+ SYMFONY 6.4
use Symfony\Component\Process\PhpSubprocess;

// підпроцес успадковує налаштування PHP батьківського процесу
$process = new PhpSubprocess(['bin/console', 'cache:pool:prune']);
$process->run();
− SYMFONY 6.3
use Symfony\Component\Process\Process;

// підпроцес бере налаштування з php.ini
$process = new Process(['bin/console', 'cache:pool:prune']);
$process->run();
СИНТАКСИС · 2

Синтаксис

2.1 · Routing

Маршрути за іменем класу контролера

Імʼя маршруту це довільний рядок, який легко переплутати або забути оновити. Якщо метод контролера має рівно один маршрут, 6.4 додає до нього псевдонім 'App\Controller\VacancyController::index', а invokable-контролеру вистачає імені класу. URL генерується через ::class без «магічних» рядків, а debug:router --show-aliases показує ці псевдоніми.

+ SYMFONY 6.4
use App\Controller\ShowVacancyController;
use App\Controller\VacancyController;

$url = $urlGenerator->generate(VacancyController::class.'::index');

// invokable-контролер з одним маршрутом
$url = $urlGenerator->generate(ShowVacancyController::class, ['id' => 42]);
− SYMFONY 6.3
$url = $urlGenerator->generate('vacancy_index');
2.2 · Serializer

#[Groups] і #[Context] на класі

Коли всі властивості сутності входять в одну групу, атрибут доводилось повторювати на кожній. Тепер #[Groups] на класі додає групу всім властивостям, а їхні власні групи зберігаються. #[Context] на класі так само задає, скажімо, формат дат для всіх полів, і окрема властивість може його перевизначити.

+ SYMFONY 6.4
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;

#[Groups(['show_product'])]
#[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])]
class Product
{
    #[Groups(['list_product'])]
    private string $name;

    private string $description;
}
− SYMFONY 6.3
use Symfony\Component\Serializer\Annotation\Groups;

class Product
{
    #[Groups(['show_product', 'list_product'])]
    private string $name;

    #[Groups(['show_product'])]
    private string $description;
}
ФУНКЦІЇ · 2

Нові функції

3.1 · FrameworkBundle

renderBlock() і renderBlockView()

Для Turbo Streams чи AJAX-фрагментів часто потрібен лише один блок шаблону. Раніше для цього діставали twig із контейнера й самі загортали результат у Response. Тепер renderBlock() одразу повертає Response, а renderBlockView() рядок.

+ SYMFONY 6.4
return $this->renderBlock('vacancy/show.html.twig', 'salary', $context);

// лише HTML блоку
$html = $this->renderBlockView('vacancy/show.html.twig', 'salary', $context);
− SYMFONY 6.3
return new Response(
    $this->container->get('twig')->load('vacancy/show.html.twig')->renderBlock('salary', $context)
);
3.2 · FrameworkBundle

Нові асерти для функціональних тестів

Щоб переконатися, що хоч один пункт списку містить текст, тексти збирали через each() і шукали серед них. Тепер для цього є assertAnySelectorTextContains() і assertAnySelectorTextSame(), а ще асерти для запитів через HttpClient і assertEmailSubjectContains() для теми листа. HttpClient-асерти працюють через профайлер, тому перед запитом викликайте $client->enableProfiler().

+ SYMFONY 6.4
$client->enableProfiler();
$client->request('GET', '/vacancies');

$this->assertAnySelectorTextSame('ul.vacancies li', 'Senior Symfony');
$this->assertHttpClientRequest('https://example.com/', 'GET');
$this->assertHttpClientRequestCount(1);
− SYMFONY 6.3
$items = $crawler->filter('ul.vacancies li')->each(fn (Crawler $node) => $node->text());
$this->assertContains('Senior Symfony', $items);
ПРОДУКТИВНІСТЬ · 1

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

4.1 · Finder

Раннє відсікання директорій у Finder

filter() відкидав файли вже після того, як Finder обійшов усе дерево, тож великий node_modules читався до кінця. З другим аргументом true замикання, яке повертає false для директорії, відсікає її одразу, і всередину Finder не заходить. На розлогих деревах це помітно швидше.

+ SYMFONY 6.4
// каталог node_modules пропускається одразу
$finder->files()->in($dir)->filter(
    fn (SplFileInfo $file) => $file->getFilename() !== 'node_modules',
    true,
);
− SYMFONY 6.3
// node_modules обходиться повністю, файли відкидаються потім
$finder->files()->in($dir)->filter(
    fn (SplFileInfo $file) => !str_contains($file->getRelativePathname(), 'node_modules'),
);
БЕЗПЕКА · 3

Безпека

5.2 · SecurityBundle

Logout без власного маршруту

Вихід повністю обробляє файрвол, але маршрут для path доводилось оголошувати самому, часто з порожнім контролером, який ніколи не викликається. Тепер маршрути створює LogoutRouteLoader з іменем _logout_<файрвол>, наприклад _logout_main. У проєктах на Flex імпорт додає рецепт symfony/security-bundle, без Flex його прописують вручну.

+ SYMFONY 6.4
# config/packages/security.yaml
security:
    firewalls:
        main:
            logout:
                path: /logout

# config/routes/security.yaml, Flex додає його сам
_symfony_logout:
    resource: security.route_loader.logout
    type: service
− SYMFONY 6.3
#[Route('/logout', name: 'app_logout', methods: ['GET'])]
public function logout(): never
{
    throw new \LogicException('Цей метод перехоплює файрвол.');
}
5.3 · Security

Хешовані IP і логін у login throttling

Login throttling рахує спроби за IP-адресою й логіном, і ці персональні дані потрапляли в логи. У 6.4 DefaultLoginRateLimiter перетворює їх на HMAC-SHA256 із kernel.secret. Міняти нічого не треба, достатньо оновитися.

+ SYMFONY 6.4
// Symfony 6.4: HMAC-SHA256 від логіна й IP, ключ kernel.secret
$this->localFactory->create($this->hash($username.'-'.$request->getClientIp()));
− SYMFONY 6.3
// Symfony 6.3, DefaultLoginRateLimiter
$this->localFactory->create($username.'-'.$request->getClientIp());
DEPRECATED

Застаріло

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

Інтеграція з Doctrine AnnotationsМаршрути, валідацію й серіалізацію описуйте PHP-атрибутами, а інтеграцію вимкніть опцією framework.annotations або видаліть пакет doctrine/annotations. У 7.0 підтримку анотацій прибрано.
ContainerAwareInterface і ContainerAwareTraitОтримуйте залежності через конструктор, для лінивого доступу є service subscriber. У 7.0 видалено.
Компонент TemplatingВикористовуйте Twig. У 7.0 компонента вже немає.
Неявні значення опцій frameworkЗадайте явно http_method_override, handle_all_throwables, php_errors.log, session.cookie_secure, session.cookie_samesite, session.handler_id, uid.default_uuid_version, uid.time_based_uuid_version і validation.email_validation_mode. У 7.0 їхні значення за замовчуванням змінюються.
enable_annotations у validation і serializerЗамініть на framework.validation.enable_attributes і framework.serializer.enable_attributes. У 7.0 старі опції прибрано.
UriSigner з HttpKernelБеріть Symfony\Component\HttpFoundation\UriSigner. Клас із HttpKernel у 7.0 видалено.
HandlerFailedException::getNestedExceptions()Замість нього й getNestedExceptionsOfClass() викликайте getWrappedExceptions(). У 7.0 старі методи видалено.
WarmableInterface::warmUp() без $buildDirДодайте до власних cache warmer другий аргумент $buildDir. У 6.4 він ще в коментарі, у 7.0 стає частиною сигнатури.
BREAKING

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

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

Дати із заголовків стали DateTimeImmutableHeaderBag::getDate(), Response::getDate(), getExpires() і getLastModified() повертають DateTimeImmutable, тож modify() на результаті більше не змінює сам обʼєкт.
render() не викликає renderView()Якщо базовий контролер перевизначає renderView(), AbstractController::render() у 6.4 цей код оминає.

Як оновитися

0 / 5