Привет, Хабр!

Сергей Пантелеев, Кирилл Несмеянов и Данил Щуцкий собрали новости за ноябрь в PHP, Symfony и Laravel (соответственно). Всё самое интересное. Если вы хотите быть в теме происходящего, этот материал точно для вас. ?

Новости PHP

Вышел PHP 8.4.1

PHP 8.4 – большое обновление языка. Оно содержит множество новых возможностей, таких как хуки свойств, асимметричная область видимости свойств, обновление DOM API, ленивые объекты и многое другое. Как обычно, улучшена производительность и исправлены ошибки.

Исправления безопасности, о которых мы поговорим ниже, не попали в сборку PHP 8.4.0, поэтому цикл PHP 8.4 начался с версии PHP 8.4.1.

Не полагайтесь на тег php-8.4.0, если собираете PHP из исходных кодов.

В день выхода PHP 8.4, на канале CutCode, мы с видными представителями PHP-сообщества – Кириллом Несмеяновым, Алексеем Гагариным, Александром Макаровым, Валентином Удальцовым, Павлом Бучневым и Дмитрием Елисеевым – обсудили главные фишки релиза и поговорили о PHP в целом. Обязательно посмотрите, если пропустили.

Вышли PHP 8.1.31, PHP 8.2.26 и PHP 8.3.14

В этих выпусках исправлены сразу 6 ошибок безопасности:

  • Heap-Use-After-Free при обработке sapi_read_post_data в интерфейсе CLI SAPI (GHSA-4w77-75f9-2c8w).

  • Доступ к внеполосным данным в функции ldap_escape (GHSA-g665-fm4p-vhff).

  • Утечка части содержимого кучи через перечитывание буфера кучи (GHSA-h35g-vwh6-m678).

  • Целочисленное переполнение в квотере dblib и firebird, приводящее к записи внеполосных данных (GHSA-5hqh-c84r-qjcv).

  • Настройка прокси в контексте потока может позволить вводить CRLF в URI (GHSA-c5f2-jwm7-mmq2).

  • Перечитывание одного байта с помощью фильтра convert.quoted-printable-decode (GHSA-r977-prxv-hc43).

Пожалуйста, обновитесь как можно скорее.

PHP.net is temporarily unavailable

Некоторые из вас могли заметить, что 24 ноября на протяжении 12 часов был недоступен сайт php.net. Нет, не потому что накатили PHP 8.4 и все сломалось, проблемы были на стороне CDN-провайдера сайта.

К чему это я, shit happens… Надо лишь разбирать свои ошибки и стараться их не повторять. Команда PHP сейчас работает над Post-Mortem отчетом для минимизации подобных проблем в будущем.

Вышел Composer 2.8.0

В новой версии Composer появилось несколько интересных дополнений:

  • Флаг --patch-only, который позволяет ограничить обновления изменениями на уровне патча, минимизируя риск внесения изменений, нарушающих обратную совместимость.

  • Возможность явно определить, нужно ли передавать дополнительные аргументы/опции в основную команду.

Анонсирован установщик модулей PHP (PIE)

PHP Foundation представил предварительный релиз PHP Installer for Extensions (PIE). PIE призван упростить управление модулями PHP, предоставляя современную, гибкую альтернативу репозиторию PECL.

Модули распространяются через Packagist так же, как и обычные PHP-пакеты. Процесс установки и обновления покажется вам знакомым, если вы уже используете Composer.

Вышел API Platform 4

Новая версия фреймворка для создания REST- и GraphQL API-приложений теперь официально поддерживает Laravel.

Вышел PHPStan 2.0

Первая версия PHPStan была выпущена чуть более трех лет назад. С тех пор  команда сделала более 170 релизов, внедряя новые функции, исправляя ошибки и закладывая основу для второй версии.

В новой версии PHPStan добавлен более строгий 10 уровень анализа кода, добавлена поддержка типа list, уменьшено потребление памяти, улучшена производительность и многое другое.

? PHP Foundation исполнилось 3 года

Фонд PHP Foundation был основан три года назад.

За прошедший год PHP Foundation поддержал работу 10 основных разработчиков и внёс значительный вклад в развитие языка PHP.

Поддержать PHP Foundation можно с помощью OpenCollective или GitHub Sponsors.

Вышел PhpStorm 2024.3

В PhpStorm 2024.3 добавлены инспекции и быстрые исправления, упрощающие переход на PHP 8.4.

Помимо поддержки PHP 8.4, главные новинки этой версии:

  • Улучшенный AI Assistant

  • Поддержка xdebug_notify()

  • Интерпретатор PHP из Laravel Herd

Вышел Laravel Idea 9

Вышла девятая версия плагина Laravel Idea для PhpStorm.

В новой версии добавлено создание модели Eloquent из существующей таблицы базы данных, возможность легко запускать Artisan команды, добавлена поддержка фасадов в реальном времени и много другое!

Разработчики из России, Белоруссии и Украины могут подать заявку на получение бесплатного ключа на сайте laravel.su.

Кстати, Адель Файзрахманов – автор Laravel Idea – принимал участие в пилотном выпуске нашей викторины по PHP, посмотрите, если пропустили.

PHP Russia 2024

В Москве прошла конференция Highload, в рамках которой 16 докладов выделены под PHP Russia. Командой CutCode мы поехали на конференцию и скоро выпустим для вас небольшой видео отчёт.

Пробрались за кулисы, поснимали внутреннюю кухню, поговорили с организаторами, докладчиками и зрителями.

«Своя игра» по PHP #4

За прошедший год мы провели уже три викторины по PHP и под конец года мы организуем викторину среди победителей предыдущих игр. В финальной игре этого года встретятся Кирилл Несмеянов, Алексей Гагарин и Павел Бучнев.

Присылайте свои вопросы ребятам, некоторые мы добавим в игру или обсудим их в перерывах между раундами, а автор самого интересного вопроса, получит слоника.

PHP Core Roundup

Большинство новостей ядра PHP подробно освещаются в серии PHP Core Roundup от PHP Foundation, мы лишь быстро по ним пробежимся:

✅RFC: Add persistent curl share handles

Сейчас модуль cURL не поддерживает постоянные дескрипторы совместного доступа cURL. Соединения передаются только между дескрипторами в рамках одного SAPI-запроса.

В PHP 8.5 появится новая функция curl_share_init_persistent(), которая позволит сохранять дескрипторы cURL в глобальной памяти и повторно использовать их в последующих запросах. Улучшение направлено на повышение производительности за счет снижения накладных расходов на инициализацию дескрипторов cURL каждый раз во время их использования.

Matthieu Napoli, автор Bref:

Большинство PHP-приложений проводят большую часть своего времени, выполняя операции ввода-вывода (не используя процессор). <...>

Но вызов API (через HTTP) может занимать очень много времени. Разные порядки величины.

Почему? Потому что каждый раз устанавливается TPC и HTTPS-соединение. Это может занять 100 мс или 200 мс (HTTPS – это безумие). На каждый запрос, потому что PHP (с FPM) каждый раз создает соединение заново.

Вот почему переход на Laravel Octane или Symfony Runtime иногда имеет такое значение: поддерживая процесс PHP между запросами, мы можем сэкономить эти 100 мс. По сравнению с тем, что мы экономим на времени загрузки фреймворка, это очень много.

RFC предлагает разделять HTTP-соединения между запросами в PHP-FPM: это очень важно, потому что может принести некоторые из этих преимуществ всем PHP-приложениям, без необходимости переходить на Octane/Runtime (и связанные с ними недостатки).

✅RFC: Support Closures in constant expressions

Некоторые конструкции PHP ограничены возможностью принимать только постоянные выражения. Эти выражения могут содержать только ограниченное количество операций, которые можно кратко охарактеризовать как неизменяемые значения.

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

Поскольку замыкания – это фактически просто исходный PHP-код (или, скорее, опкоды PHP), они являются неизменяемыми значениями, и поэтому нет никаких фундаментальных причин, по которым они не должны быть разрешены в постоянных выражениях.

Tim Düsterhus и Volker Dusch предлагают разрешить передавать замыкания в параметры атрибутов, значения свойств, параметры по умолчанию, константы, а также константы классов.

final class Locale
{
    #[Validator\Custom(static function (string $languageCode): bool {
        return \preg_match('/^[a-z][a-z]$/', $languageCode);
    })]
    public string $languageCode;
}

? RFC: Records

Value Object – это неизменяемый объект, который представляет некоторое значение или концепцию в приложении.

Robert Landers предлагает добавить новое ключевое слово record определения неизменяемых Value Object.

Объекты Record могут реализовывать интерфейсы и использовать трейты, но не могут расширять другие объекты Record или классы.

У каждого объекта Record также будет доступен метод with для частичного обновления свойства, создавая новый экземпляр объекта с обновленными свойствами.

record Planet(string $name, int $population);

$pluto = Planet("Pluto", 0);
$pluto = $pluto->with(population: 1);

? RFC: Data Class

Еще один RFC от Robert Landers, который появился благодаря обсуждению RFC про объекты Records.

Robert предлагает добавить новый модификатор класса: data, который радикально поменяет работу классов, делая их сравнимыми по значению, а не по ссылке, и любые мутации ведут себя скорее как массивы, чем как объекты. При желании его можно комбинировать с другими модификаторами, такими как readonly, для обеспечения неизменяемости.

data class Rectangle {
    public function __construct(public int $width, public int $height) {}

    public function area(): int {
        return $this->width * $this->height;
    }

    public function resize(int $width, int $height): static {
        $this->height = $height;
        $this->width = $width;
        return $this;
    }
}

$rectangle = new Rectangle(10, 20);
$newRectangle = $rectangle;
$newRectangle->width = 30;
$otherRectangle = new Rectangle(30, 20);

assert($rectangle !== $newRectangle); // true
assert($newRectangle === $otherRectangle); // true

$bigRectangle = $rectangle->resize(10, 20);
assert($bigRectangle !== $rectangle); // true

✅ RFC: Policy on 3rd party code

В PHP долгое время был негласный запрет на использование и упоминание сторонних фреймворков и инструментов, чтобы не отдавать предпочтение какой-либо экосистеме, даже если какой-то инструмент является де-факто стандартом отрасли (например, Composer), либо уже используется в PHP (например, DokuWiki).

Larry Garfield предложил принять политику о сторонних проектах, которые может использовать или упоминать PHP.

? RFC: PHP.net Analytics

Еще один RFC, напрямую не связанный с разработкой – добавление трекера аналитики на сайт php.net.

Larry Garfield и Роман Пронский предложили установить трекер Matomo, который будет собирать обезличенную информацию о поведении пользователей на сайте, кроме того, данные трекера будут доступны только команде PHP Infrastructure и не будут передаваться третьим лицам.

На данный момент документация PHP содержит более 17 000 страниц, трекер поможет оптимизировать работу над документацией, а также понять поведение пользователей на сайте.

Драма WordPress и WP Engine

Вокруг WordPress разгорается скандал. Проблемой стал конфликт между Matthew Mullenweg, одним из создателей WordPress и CEO компании Automattic, и WP Engine, хостингом для сайтов, созданных на WordPress.

В середине сентября Matthew Mullenweg опубликовал в своём блоге сообщение, в котором назвал WP Engine «раковой опухолью WordPress». Он раскритиковал хостера за отключение истории изменений для каждой записи. Matthew Mullenweg считает, что эта функция лежит в основе «обещания защитить данные пользователя», а WP Engine, отключая её, просто пытается сэкономить. 

Он также обратился к инвестору WP Engine, компании Silver Lake, которую обвинил в недостаточном вкладе в проект. Кроме того, он заявил, что использование бренда WP хостингом WP Engine вводит пользователей в заблуждение: хостинг необоснованно позиционирует себя как часть WordPress.

После этого Matthew Mullenweg запретил WP Engine доступ к ресурсам WordPress. Этот шаг сломал множество сайтов, помешав им обновлять плагины и темы. Часть ресурсов стала уязвимее для кибератак, что вызвало возмущение среди сообщества.

Эта новость не попала бы в дайджест, если бы в социальных сетях неравнодушные пользователи не обрушились с критикой на PHP Foundation за то, что фонд поблагодарил компанию Automattic за спонсорскую помощь. Некоторым показалось неуместным благодарить компанию в подобной ситуации.

Laravel дайджест

Обновления Laravel

[11.30] Allow the authorize method to accept Backed Enums directly

https://github.com/laravel/framework/discussions/53330

PR продолжает развивать тему с Enum, и теперь в контроллере в методе Authorize также допускаются Enum.

enum DashboardPermission: string
{
    case VIEW = 'dashboard.view';
}

   public function index(): Response
    {
        $this->authorize(DashboardPermission::VIEW);
        //
    }

[11.30] use exists() instead of count() 

https://github.com/laravel/framework/pull/53328

Небольшой pull request, который с улучшением оптимизации. До этого под капотом использовался count, что не очень хорошо. Его заменили на exists.

[11.30] Introduce HasUniqueStringIds

https://github.com/laravel/framework/pull/53280

Следующий pull request у нас затрагивает Eloquent модели - возможности взаимодействовать с UUID и с ULID. Но при этом автор пишет, что существует проблема - проверка валидности вшита в метод resolveRouteBindingQuery. И если нам потребуется полностью изменить логику и использовать собственные идентификаторы, то тем самым вместе с этим придется и полностью переопределить огромный метод resolveRouteBinding для того, чтобы заменить всего одно условие в двух местах:

trait HasTwrnsTrait
{
    use HasUuids;

    public function newUniqueId(): string
    {
        return (string) Twrn::new();
    }

    public function resolveRouteBindingQuery($query, $value, $field = null)
    {
        if ($field && in_array($field, $this->uniqueIds()) && ! Twrn::isValid($value)) {
            throw (new ModelNotFoundException)->setModel(get_class($this), $value);
        }

        if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! Twrn::isValid($value)) {
            throw (new ModelNotFoundException)->setModel(get_class($this), $value);
        }

        return parent::resolveRouteBindingQuery($query, $value, $field);
    }
}

Автор предлагает решение:

trait HasTwrnsTrait
{
    use HasUniqueStringIds;

    public function newUniqueId()
    {
        return (string) Twrn::new();
    }

    protected function isValidUniqueId($value): bool
    {
        return Twrn::isValid($value);
    }
}

Видим, что у нас трейт в трейте, он создал еще один трейт. Дубли, которые присутствовали в максимальном количестве в двух трейтах до этого, были вынесены в новый трейт и соответственно добавлен новый метод, он абстрактный и реализуется в трейтах на уровень выше. Получается что там, где у нас была вшита логика по ULID и UUID, она поменялась на этот метод. Возможно, скажете, что много странностей, но так бывает, вот такая возможность появилась в этом pull request.

Казалось бы, логика ULID и UUID, а при этом дают возможность генерировать в них все что угодно, выходя за ответственность этих трейтов. Все это говорит о том, что в целом обсуждения по линчеванию Laravel, которые мы делали недавно и не требуются. Все странные вещи в Laravel можно посмотреть в обновлениях  Laravel, где мы постоянно натыкаемся на  странные решения. Это обновление - яркий пример

[11.30] Add withoutDefer and withDefer testing helpers

https://github.com/laravel/framework/pull/53340

Следующему pull request, затрагивает он тесты с отложенным выполнением, и на уровне тестов мы можем воспользоваться методами withDefer, withoutDefer, чтобы тестировать с или без выполнения отложенных функций.

[11.31] Cache token repository

https://github.com/laravel/framework/pull/53428

Первый pull request в 11.31 затрагивает сброс паролей. По умолчанию у нас был только дефолтный драйвер, который хранил токены в базе данных. Дополнительно появился еще драйвер для хранения в кэше.

'passwords' => [

    //new cache driver
    'customers' => [
        'driver'   => 'cache',
        'store'    => 'passwords',
        'provider' => 'customers',
        'expire'   => 60,
        'throttle' => 60,
    ],

   //default old database driver
    'users'     => [
        'provider' => 'users',
        'table'    =>'password_reset_tokens',
        'expire'   => 60,
        'throttle' => 60,
    ],
],

Вот мы его видим в примере и можем использовать. 

[11.31] Add ability to dynamically build mailers on-demand using Mail::build

https://github.com/laravel/framework/pull/53411

Двигаемся дальше к следующему pull request в 11.31. Steve Bauman представляет свой Builder в рамках отправки мейлов в рамках класса Mail, чтобы прямо на лету сбилдить конфигурацию и сразу ее использовать.

use Illuminate\Support\Facades\Mail;

$mailer = Mail::build([
    'transport' => 'smtp',
    'host' => '127.0.0.1',
    'port' => 587,
    'encryption' => 'tls',
    'username' => 'usr',
    'password' => 'pwd',
    'timeout' => 5,
]);

$mailer->send($mailable);

Как видим в примере, все понятно без объяснения.

[11.31] Add ability to dynamically build cache repositories on-demand using Cache::build

https://github.com/laravel/framework/pull/53454

Переходим к следующему pull request, и все тот же Steve Bauman сильно вдохновился своим Builder конфигурации на лету, и то же самое добавил и для кэша.

use Illuminate\Support\Facades\Cache;

$apc = Cache::build([
    'driver' => 'apc',
]);

$array = Cache::build([
    'driver' => 'array',
    'serialize' => false,
]);

$database = Cache::build([
    'driver' => 'database',
    'table' => 'cache',
    'connection' => null,
    'lock_connection' => null,
]);

$file = Cache::build([
    'driver' => 'file',
    'path' => storage_path('framework/cache/data'),
]); 

Все понятно. 

[11.31] Add DB::build method

https://github.com/laravel/framework/pull/53464

Двигаемся к следующему pull request, и у нас все еще Steve Bauman, который никак не мог угомониться и добавил то же самое еще и в DB.

use Illuminate\Support\Facades\DB;

$sqlite = DB::build([
    'driver' => 'sqlite',
    'database' => ':memory:',
]);

$mysql = DB::build([
    'driver' => 'mysql',
    'database' => 'forge',
    'username' => 'root',
    'password' => 'secret',
]);

$pgsql = DB::build([
    'driver' => 'pgsql',
    // ...
]);

$sqlsrv = DB::build([
    'driver' => 'sqlsrv',
    // ...
]);

В примерах все понятно. 

[11.31] Added useCascadeTruncate method for PostgresGrammar

https://github.com/laravel/framework/pull/53343

На стриме у Валентина мы обсуждали проблемы Laravel. Кирилл Мокевнин рассказывал о том, что у него был такой кейс с транкейтом, где он в миграциях вызвал транкейт и в рамках Postgres было нестандартное поведение, так как под капотом Laravel на уровне Postgres использует вместо дефолтного рестрикта каскадный подход. Соответственно, автор pull request явно смотрел этот линч и недолго думая пошел и сделал вот такой pull request, добавив метод useCascadeTruncate в PostgresGrammar и чтобы не делать никаких брейкинг-ченджей, сразу сказал Тейлору, что в 12 и 13 версиях можно будет сделать дефолтное нормальное поведение, но при этом, кому понравились странности из прошлого, смогут использовать его новый метод. Тейлор, видимо, тоже смотрел линч Валентина, плюс прочитал подробное описание и, стиснув зубы, смёржил этот pull request. А мы с вами двигаемся дальше, но заодно делаем себе пометку, что наши стримы смотреть полезно, после них можно пойти сделать pull request и узнать что-то новое под капотом Laravel.

[11.31] Add the ability to append and prepend middleware priority from the application builder

https://github.com/laravel/framework/pull/53326

Продолжается тема с мидлварами в рамках нового, относительно нового со времен 11 Laravel, application builder. Появился новый метод append и prepend. Сразу набор определенных мидлваров, либо до, либо после указанного.

$middleware->appendToPriorityList(after: $middlewareToPutTheNewMiddlewareAfter, append: $theMiddlewareToAppend);
$middleware->prependToPriorityList(before: $middlewareToPutTheNewMiddlewareBefore, prepend: $theMiddlewareToPrepend);

Ну и немного изменили сигнатуру, добавились наименования after, before, append и prepend. В общем, вот такой удобный сахар, который мы с вами будем использовать.

[11.31] Add URL::forceHttps() to enforce HTTPS scheme for URLs

https://github.com/laravel/framework/pull/53381

Следующий pull request в рамках класса URL добавиляет метод forceHttps, и независимо от того, какая у вас в реальности схема, все генерируемые URL в рамках вашего приложения будут иметь схему HTTPS.

URL::forceHttps(
    $this->app->isProduction()
);

Я думаю, многие из вас будут рады этому новому методу.

[11.32] Http Client: fake connection exception

https://github.com/laravel/framework/pull/53485

Перемещаемся к 11.32. Pull request затрагивает HTTP клиент в рамках тестов. До этого мы не могли отслеживать connection exception:

Http::fake(['https://laravel.com' => Http::failedConnection()]);

// Similar syntax to:

Http::fake(['https://laravel.com' => Http::response()]);

теперь мы можем передать failed connection и в итоге сымитировать это поведение.

[11.32] Add support for syncing associations with array or base collection of models

https://github.com/laravel/framework/pull/53495

Интересный pull request в рамках отношений belongs to many. До этого для синхронизации через метод sync нам нужно было передавать ID, теперь же нам доступна сразу передача моделек, всегда удивлялся, почему этого не было изначально, но теперь так делать можно.

$tag = Tag::create(['name' => Str::random()]);
$tag2 = Tag::create(['name' => Str::random()]);

- $post->tags()->sync([$tag->id, $tag2->id]);
+ $post->tags()->sync([$tag, $tag2]);

То же самое еще добавили и с коллекциями. То есть можно и не в виде массива, а сразу передать коллекцию, и это тоже будет работать без каких-то дополнительных методов.

- $post->tags()->sync(Arr::pluck($tags, 'id'));
+ $post->tags()->sync($tags);

[11.32] Introduce Schedule Grouping

https://github.com/laravel/framework/pull/53427

Следующий pull request у нас затрагивает расписание, Schedule. Была проблема, собственно, как и в роутах, только здесь не настолько распространено, так как команды не в таком количестве и не так часто они имеют одинаковые настройки, но в целом такой кейс существовал, и теперь можно объединять вызовы команд по расписанию в определенные группы, чтобы не дублировать портянку из общих методов.

Schedule::group()
    ->everyMinute()
    ->runInBackground()
    ->withoutOverlapping()
    ->schedules(function () {
        Schedule::command('command-one');
        Schedule::command('command-two');
        Schedule::command('command-three');
    });

Schedule::group()
    ->everyMinute()
    ->runInBackground()
    ->withoutOverlapping()
    ->schedules(function () {
        Schedule::command('command-one');
        Schedule::command('command-two');
        Schedule::command('command-three')->everyTenMinutes();  // Override the group's cron expression
    });

Schedule::group()
    ->runInBackground()
    ->withoutOverlapping()
    ->schedules(function () {
        Schedule::group()->everyMinute()->schedules(function () {
            Schedule::command('command-one');
            Schedule::command('command-two');
        });

        Schedule::group()->everyTenMinutes()->schedules(function () {
            Schedule::command('command-three');
            Schedule::command('command-four');
        });
    });

[11.32] Add "head" slot to email layout

https://github.com/laravel/framework/pull/53531

В Blade-шаблоне для отправки email-уведомлений появился slot-head, чтобы можно было добавить стили, что-либо еще в head-блок наших писем.

<x-slot:head>
<style>
@media only screen and (max-width: 600px) {
  .inner-body {
  border-radius: 0 !important;
  }
}

</style>
</x-slot:head>

[11.33] Add "createQuietly" method

https://github.com/laravel/framework/pull/53558

Pull request в моделе добавляет новый метод createQuietly. Вроде бы он был раньше, но, как оказалось, нет. Добавили в 11.33, чтобы создать и при этом не дергать никаких эвентов.

[11.33] Add Request::enums method to retrieve an array of enums

https://github.com/laravel/framework/pull/53540

Следующий pull request снова от Steve Bauman, мы его сегодня в дайджесте видели уже многократно, он делал билдеры для всего, что только можно. В данной ситуации он прокачал объекты реквеста.

// app/Http/Controllers/WebhookController.php

public function store(Request $request)
{
    $request->validate([
        'url' => 'required|url',
        'events' => 'required|array',
        'events.*' => Rule::enum(WebhookEvent::class),
    ]);

    // [WebhookEvent::UserCreated, WebhookEvent::UserUpdated]
    $events = $request->enums('events', WebhookEvent::class);
}

До этого был метод enum, который строку из реквеста трансформировал в определенный enum. Также появился enums, когда в реквесте массив, и мы сразу трансформируем в массив enum.

Webhook::create([
    'events' => $request->enums('events', WebhookEvent::class),
    // ...
]);

[11.33] prefer new Collection() over collect()

https://github.com/laravel/framework/pull/53563

Интересный pull request. Автор устал от того, что в ядре используется под капотом и переделал все упоминания на instance collection напрямую. Если заглянем, у нас много изменений, и соответственно везде исправлено в надежде, что со временем код будет становиться лучше, и вот такие моменты будут исправляться.

[11.33] PHP 8.4 Code compatibility

https://github.com/laravel/framework/pull/53571

Следующий pull request у нас затрагивает совместимость с PHP 8.4. Кстати, мы делали стрим, где рассматривали подробно с крутыми гостями каждую фичу и обсуждали, что вообще в целом у нас 8.4 и что нас ждет в дальнейшем, поэтому кто не смотрел, обязательно переходите и посмотрите. И как видим по изменениям, их не так много, чтобы добавить эту совместимость.

[11.33] Supports laravel/serializable-closure 2

https://github.com/laravel/framework/pull/53552

Последний PR в этом дайджесте Laravel добавляет поддержку Serializable замыканий версии 2.

Обсудить новости можно в чате по Laravel - https://t.me/laravel_chat

Symfony дайджест

По традиции я, Кирилл Несмеянов, продолжаю вас знакомить с самым лучшим, самым удобным, мощным, гибким и элегантным фреймворком тысячелетия - Symfony.

Прошёл ноябрь, а это значит, что на дворе релиз версии 7.2. Что ещё нового он нам подготовил? Давайте смотреть…

Консольный индикатор завершения прогресса

Компонент консоли - один из самых популярнейших, даже вне экосистемы Symfony. Его используют более 11000 OSS проектов, а количество загрузок приближается к одному миллиарду.

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

Конечно, этот индикатор можно кастомизировать, изменив анимацию, однако основная проблема состоит в том, что после завершения работы индикатор просто "повисает".

В Symfony 7.2 был добавлен индикатор завершения для прогресса, отображающий ✔ (галочку) в конце работы.

Очевидно, вы можете модифицировать на свой вкус это поведение, используя аргумент finishedIndicatorValue.

use Symfony\Component\Console\Helper\ProgressIndicator;

$indicator = new ProgressIndicator($output, finishedIndicatorValue: '✅');

try {
    $indicator->finish('Усё, шеф!');
} catch (\Throwable) {
    $indicator->finish('Насяйника, оно бжумк!', '?');
}

Источник: symfony.com

Валидатор

Улучшения валидатора в Symfony 7.2. Казалось бы, куда дальше, но вот так бывает.

Улучшения проверки Bic

Для начала стоит заметить, что BIC - довольно специфичная штука, описанная стандартом ISO-9362. Отвечает за спецификацию метода идентификации участников финансовых расчётов. Основан на работах компании SWIFT для идентификации банков в сети SWIFT.

Каждый код BIC представлен в виде цифро-буквенной комбинации из 8 или 11 символов в верхнем регистре. Это всё, что следует знать, думаю, чтобы не углубляться в детали.

В любом случае, пользователи - ленивые существа, поэтому в большинстве случаев плюют на спецификацию и вводят данный код в произвольном регистре. Так что в Symfony 7.2 в данный валидатор было добавлено два режима работы:

  • strict - (режим по умолчанию) строгая проверка согласно стандарту.

  • case-insensitive - регистронезависимая проверка кода.

use Symfony\Component\Validator\Constraints as Assert;

final readonly class TransactionRequestDTO
{
    public function __construct(
        #[Assert\Bic(mode: 'case-insensitive')]
        public string $bic,
        // ...
    ) {}
}

Улучшения проверки Unique

Валидатор Unique проверяет, что все элементы коллекции уникальны в рамках этой коллекции. Однако, когда случается ошибка валидации - ошибка возникает для всего элемента, а не конкретного поля элемента.

В Symfony 7.2 был добавлен аргумент errorPath, позволяющий указать конкретное поле элемента в котором произошла ошибка валидации.

use Symfony\Component\Validator\Constraints as Assert;

final readonly class MessageRequestDTO
{
    public function __construct(
        // ...
        public string|int $id,
        // ...
    ) {}

    public static function getUniqueKey(self $dto): string
    {
        return (string) $dto->id;
    }
}

final readonly class BatchMessageUpdateRequestDTO
{
    public function __construct(
        /**
         * @var iterable<array-key, MessageRequestDTO> 
         */
        #[Assert\Unique(
            normalizer: [MessageRequestDTO::class, 'getKeyForUniqueConstraint'], 
            errorPath: 'id'
        )]
        public iterable $messages,
    ) {}
}

Улучшения проверки Ulid

Symfony предоставляет отдельный компонент для генерации идентификаторов, где в качестве одного из доступных вариантов выступает идентификатор ULID.

Такой идентификатор может быть преобразован в несколько разных форматов, начиная от бинарного, заканчивая форматом RFC-4122.

use Symfony\Component\Uid\Ulid;

$ulid = Ulid::fromString('01E439TP9XJZ9RPFH3T1PYBCR8');

$ulid->toBinary();  // string(16) "\x01\x71\x06\x9d\x59\x3d\x97\xd3\x8b\x3e\x23\xd0\x6d\xe5\xb3\x08"
$ulid->toBase32();  // string(26) "01E439TP9XJZ9RPFH3T1PYBCR8"
$ulid->toBase58();  // string(22) "1BKocMc5BnrVcuq2ti4Eqm"
$ulid->toRfc4122(); // string(36) "0171069d-593d-97d3-8b3e-23d06de5b308"
$ulid->toHex();     // string(34) "0x0171069d593d97d38b3e23d06de5b308"

При этом, во время использования правил валидации Ulid его можно было передавать только в base32 формате. В Symfony 7.2 был добавлен аргумент format у этой проверки, позволяющей указывать любой из доступных форматов.

use Symfony\Component\Validator\Constraints as Assert;

final readonly class MessageRequestDTO
{
    public function __construct(
        #[Assert\Ulid(format: Ulid::FORMAT_RFC4122)]
        public string $id,
        // ...
    ) {}
}

Улучшения зависимых проверок When

Проверка When позволяет применить правило только если выражение возвращает true. В Symfony 7.2, помимо передаваемого аргумента this внутрь выражения был добавлен аргумент context, содержащий весь контекст выполнения правила.

use Symfony\Component\Validator\Constraints as Assert;

final readonly class TransactionRequestDTO
{
    public function __construct(
        #[Assert\When(
            expression: 'this.getType() == "percent" && context.getRoot().ok === true',
            constraints: [
                // ...
            ],
        )]
        public ?int $value,
    ) {}

    public function getType(): string { ... }
}

Источник: symfony.com

Упрощения настроек доверенных прокси

В том случае если приложение Symfony под балансером или обратным прокси, то следует указать Symfony каким IP-адресам обратного прокси следует доверять. Если адрес обратного прокси-сервера постоянно ротируется, то следует настроить диапазон соответствующих адресов.

В Symfony 7.2 была добавлено сокращение PRIVATE_SUBNETS для следующих значений:

  • 127.0.0.0/8    - RFC1700 (Loopback)

  • 10.0.0.0/8     - RFC1918

  • 192.168.0.0/16 - RFC1918

  • 172.16.0.0/12  - RFC1918

  • 169.254.0.0/16 - RFC3927

  • 0.0.0.0/8      - RFC5735

  • 240.0.0.0/4    - RFC1112

  • ::1/128        - Loopback

  • fc00::/7       - Unique Local Address

  • fe80::/10      - Link Local Address

  • ::ffff:0:0/96  - IPv4 translations

  • ::/128         - Unspecified address

Все значения также доступны в виде константы Symfony\Component\HttpFoundation\IpUtils::PRIVATE_SUBNETS. В любом случае, если указать сокращение PRIVATE_SUBNETS, то такую конфигурацию легче читать и поддерживать. Так что перечислять целый список более не требуется.

# config/packages/framework.yaml
framework:
  trusted_proxies: '127.0.0.1,PRIVATE_SUBNETS'

Использование ENV-переменных для конфигурации доверенных прокси

Конфигурация доверенных прокси в настоящее время выполняется в файлах конфигурации с помощью ключей trusted_proxies, trusted_headerstrusted_hosts и trust_x_sendfile_type_header.

# config/packages/framework.yaml
framework:
  # ...
  trusted_proxies: '192.0.0.1,10.0.0.0/8'
  trusted_headers: [ 'x-forwarded-for', 'x-forwarded-host', ... ]
  trusted_hosts: [ '...' ]
  trust_x_sendfile_type_header: true

В Symfony 7.2 аналогичную настройку теперь можно выполнять с помощью переменных окружения, вместо отдельной настройки конфигурации.

  • SYMFONY_TRUSTED_PROXIES

  • SYMFONY_TRUSTED_HEADERS

  • SYMFONY_TRUSTED_HOSTS

  • SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER

Источник: symfony.com

Упрощение однофайловых приложений

Использование однофайловых приложений, вместо классического скелетона - довольно редкая практика, однако идеально подходит для микрофреймворков, которым требуется исключительно роутер.

Создать такое приложение можно, используя всего два компонента:

  • symfony/framework-bundle, включающего в себя основные компоненты ядра и зависимости на HTTP-кернел, конфигурацию, роутер и параметры окружения.

  • symfony/runtime, включающего в себя рантайм-мост под различные SAPI, начиная с классического PHP-FPM и заканчивая Swoole, RoadRunner, ReactPHP, PHP-PM, Workerman, Franken, Bref, Google Cloud и прочими...

composer symfony/framework-bundle symfony/runtime

Улучшения в Symfony 7.2 коснулись и его, позволяя не указывать явно список бандлов и конфигурацию. Так что всё приложение на Symfony теперь можно сократить до пары десятков строк кода:

// index.php
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\Attribute\Route;

require __DIR__ . '/vendor/autoload.php';

final class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    #[Route('/random/{limit}', name: 'random_number')]
    public function __invoke(int $limit): JsonResponse
    {
        return new JsonResponse([
            'number' => \random_int(0, $limit),
        ]);
    }
}

return static function (array $context) {
    return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
}

Изменения в Symfony 7.2 коснулись не только упрощения настройки кернела, позволяя делать большинство настроек опциональными, но и других интересных "мелочей":

  • Директория config с конфигурацией теперь опциональная. Конфигурацию, если она требуется, можно определить в методе configureContainer.

  • Файл bundles.php теперь тоже опциональный, можно использовать метод registerBundles для дополнения списка бандлов. В стандартной поставке там всего один FrameworkBundle.

Ну и небольшая мелочь, которая, кажется, не совсем касается конкретно этих изменений, однако упомянуть её смысл имеет: теперь сервисы из DI-контейнера успешно внедряются в __invoke-методы контроллеров. Ранее, если не путаю, требовалось написать свой собственный ValueResolver. Но это не точно.

Источник: symfony.com

Новые опции команд

В Symfony 7.2 были улучшены многие существующие команды, добавлены новые опции и функции.

Переменные окружения в линтинге контейнера

В Symfony есть такая замечательная команда lint:container, которая запускает проверку конфигурации контейнера: Проверяет что все сервисы корректно определены, все их зависимости корректно настроены, аргументы переданы, типы соответствуют и т.д. Однако эта команда не проверяет наличие переменных окружений или их значений по умолчанию, которые потом используются в сервисах.

Так что в Symfony 7.2 была добавлена опция --resolve-env-vars, которая дополнительно проверяет, что все переменные окружения на месте и настроены.

php bin/console lint:container --resolve-env-vars --env=prod

Различные форматы статистики очередей

Команда messenger:stats отображает информацию о количестве сообщений в очереди. В отличие от других команд, она не предоставляла различных форматов вывода. В Symfony 7.2 для статистики были добавлены форматы json и text.

Отличный функционал для агрегации данных с помощью внешних систем для вывода, например в Графану.

php bin/console messenger:stats --format=json
php bin/console messenger:stats my_transport_name other_transport_name --format=json

Код выхода при расшифровке "секретов"

В Symfony существует функционал, позволяющий шифровать переменные окружения, которые не допускаются в открытом виде. Этот функционал требуется для того, например, чтобы данные доступа к БД, ключи АПИ и прочие данные случайно не "засветились" где-либо в логах, данных трассировки и прочих местах. Конечно, функционал не решает всех возможных проблем, однако он упрощает их решение.

В любом случае, ранее, при использовании команды secrets:decrypt-to-local, которая расшифровывала все "секреты" с помощью ключа и сохраняла их в локальном хранилище, при наличии ошибок расшифровки можно было получить код выхода 0.

Теперь при наличии каких-либо проблем при декодировании код выхода будет всегда равен 1, что позволяет проще отловить проблемы.

Пропуск неудачных сообщений из очереди

Команда messenger:failed:retry позволяет выводить и повторять некорректно обработанные сообщения. Для каждого сообщения она предоставляет два варианта:

  • Повторить его прямо сейчас.

  • Удалить сообщение из очереди. 

Однако, иногда требуется не удалять это сообщение когда вы не уверены, стоит ли повторять попытку обработки прямо сейчас.

В Symfony 7.2 был добавлен третий вариант: можно пропустить обработку сообщения сейчас, чтобы позже решить, что с ним делать.

Удобно, если вы внезапно обнаружили баг в проде и пока в спешке готовите хотфикс для него - не останавливаете работу консамеров.

Фильтрация отладки ассетов

Команда debug:asset-map позволяет отображать подробную информацию о путях AssetMapper, префиксах пространств имен, логических путях и так далее.

По мере роста приложения вывод этой команды становится все менее управляемым и увидеть портянку сообщений из сотен бандлов, разбираясь в ней не очень приятно.

Конечно, grep спасает, однако это не совсем корректное и "легальное" решение проблемы, так что в Symfony 7.2 добавили более "умную" фильтрацию вывода.

# Можно указать имя ресурса или директорию, чтобы отобразить только 
# результаты, которые ему соответствуют.

php bin/console debug:asset-map bootstrap.js
php bin/console debug:asset-map style/

# Можно указать расширение, чтобы отобразить только этот тип файла.
php bin/console debug:asset-map --ext=css

# Можно вывести только ресурсы в каталоге "vendor/" или наоборот,
# исключить любые результаты из него.
php bin/console debug:asset-map --vendor
php bin/console debug:asset-map --no-vendor

# Можно комбинировать все фильтры. Например, найти полужирные
# веб-шрифты в своих собственных (не вендорных) директориях.
php bin/console debug:asset-map bold --no-vendor --ext=woff2

Источник: symfony.com

Переработанный TypeInfo компонент

Symfony (и semver в частности) гарантирует, что любые минорные обновления ничего не "сломают" и версия 7.1 будет обратно совместима с версией 7.0. Очевидно, что и версия 7.2 так же обратна совместима с версией 7.1. Ну по крайней мере ничего критического не должно произойти.

Так же, Symfony поставляет некоторый набор функционала как "экспериментальный". Это набор функционала, которые находятся на раннем, скажем так, "бета-тесте" и могут изменить свой API и поведение на основе вашего, пользовательского опыта и фидбека.

Короче, хватит тянуть гуся за лапку... В общем, с прискорбием сообщаем, что TypeInfo, который был добавлен в Symfony 7.1 как экспериментальный - "всё". Ну как "всё". Старое API компонента - "всё" и изменения, коснувшиеся его, чуть-чуть ломают эту самую обратную совместимость.

После нескольких месяцев использования компонента в реальных приложениях и интеграции его в другие пакеты и библиотеки - компонент было решено чуть-чуть до(пере)работать.

  • Добавлен CompositeTypeInterface и WrappingTypeInterface для лучшего описания того, состоит ли тип из нескольких типов, т.е. композитный или представляет собой просто декоратор одного; 

  • Добавлен NullableType, который расширяет UnionType и при этом является WrappingTypeInterface, что значительно упрощает распознавание типа, допускающего значение null, и получение связанного с ним типа, не допускающего значение null;

  • Удален магический __call() метод, который CollectionType и GenericType использовали для пересылки методов в их "обернутый" (врапнутый) тип. Удалено, поскольку теперь проще узнать, "оборачивает" ли один тип другой;

  • Были переименованы все isXxx() (является) в satisfyXxxx() (удовлетворяет) и добавлены соответствующие методы CompositeTypeInterface::composedTypesSatisfy() и WrappingTypeInterface::wrappedTypeSatisfy()

  • Ну и добавлено множество проверок для предотвращения недопустимых конструкций типов.

Как заявляют core-контрибьюторы Symfony - они теперь, цитата:

"Уверены, что компонент имеет правильную архитектуру и поведение. Вот почему мы больше не считаем его экспериментальной функцией, и вы можете спокойно использовать его, зная, что мы не внесем в него критических изменений."

Ну что ж, поживём - увидим!

Сериализатор

Мой любимый компонент! Сколько человеко-часов было потрачено на исправление изначально криво написанного и полу рабочего компонента - не счесть... 

Поддержка потомков DateTime

В текущем нормалайзере для дат DateTimeNormalizer не было никакой поддержки потомков классов дат. Ну вот так, все пакеты, вроде Carbon и Chronos шли лесом и просто не работали. В Symfony 7.2 данное поведение было поправлено и теперь можно сериализовать любых потомков DateTimeInterface.

Конвертация из имён из snake_case

В сериализаторе уже ранее была поддержка автоматического преобразования имён из camelCase в snake_case. В Symfony 7.2 был добавлен класс SnakeCaseToCamelCaseNameConverter, который обеспечивает обратную конвертацию из snake_case в camelCase.

Новые константы UUID

В текущем нормалайзере UidNormalizer уже присутсвует набор констант для различных форматов вывода идентификаторов (NORMALIZATION_FORMAT_*).

В Symfony 7.2 добавили новую константу UidNormalizer::NORMALIZATION_FORMAT_RFC9562 для вывода идентификаторов в формате RFC-9562. Кажется, теперь сериализация идентификаторов возможна в любых доступных форматах.

Удаление зависимости на сериализатор из Webhook

Компонент Webhook использует сериализатор для преобразования данных в JSON. Это жестко "зашитая" зависимость, которую обычно избегают в компонентах Symfony, насколько это возможно. 

В Symfony 7.2 эта зависимость теперь не обязательна. Если сериализатор не установлен, то компонент Webhook будет использовать json_encode() функцию, вместо сериализатора.

Источник: symfony.com

Стейтлес CSRF

Symfony 7.2 добавляет значительное улучшение функций безопасности: защита от CSRF (подделка межсайтовых запросов) без сохранения состояния.

Этот функционал использует комбинацию данных cookie и других заголовков HTTP для проверки непостоянных (ну т.е. не сохраняемых, non-persistent) токенов.

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

Кроме того, отсутствие использования сессий гарантирует, что пользователи не потеряют данные, если потратят слишком много времени на отправку формы.

Например, если сессия будет уничтожена во время заполнения формы, то функция «Запомнить меня» восстановит состояние, и отправленная форма всё равно будет принята.

Включение стейтлес CSRF

Для включения стейтлес CSRF в вашем проекте следует добавить несколько опций в настройках:

# config/packages/framework.yaml
framework:
    csrf_protection:
        stateless_token_ids: ['my_stateless_token_id']

Опция stateless_token_ids обеспечивает "безопасность" этого функционала CSRF, так же как и традиционная функция CSRF, поскольку она явно перечисляет идентификаторы токенов, разрешенные при использовании новой функции.

Помимо этого добавлены некоторые другие настройки секции csrf_protection: 

  • cookie_name - Имя используемого cookie (по умолчанию: csrf-token);

  • check_header - В том случае если значение содержит true, то CSRF-токен проверяется в заголовке HTTP в дополнение к cookie (по умолчанию: false).

Как это работает?

Сначала проверяются HTTP-заголовки Origin и Referer на соответствие истории запроса. Поведение основано на возможности приложения определить этот источник. Если же приложение находится за реверс-прокси, то следует забыть настроить его для отправки соответствующих HTTP-заголовков X-Forwarded-* и Forwarded.

Затем этот запрос проверяется с помощью cookie и CsrfToken. Если cookie найден, он должен содержать то же значение, что и CsrfToken. За такую "двойную отправку" может отвечать, например JavaScript. Помимо этого значение токена должно быть сгенерировано заново при каждом запросе (т.е. при отправке формы) с использованием криптографически безопасного ГПСЧ (генератора псевдослучайных чисел, PRNG).

Если же механизм двойной отправки не сработал, или отсутствуют HTTP-заголовки Origin и Referer, то скорее всего указывает на то, что JavaScript отключен на стороне клиента (ну или некорректно реализован), или заголовки Origin и Referer были отфильтрованы.

Такие запросы, в которых отсутствует как двойная отправка, так и информация об источнике, считаются небезопасными.

Если вы устанавливаете приложение "с нуля", то эта функция CSRF будет включена по умолчанию. Symfony Flex автоматом добавит следующую конфигурацию:

# config/packages/framework.yaml
framework:
  form:
    csrf_protection:
      token_id: 'submit'
  csrf_protection:
    stateless_token_ids: [ 'submit', 'authenticate', 'logout' ]

Источник: symfony.com

Устаревший функционал

Каждый минорный релиз сопровождается сообщениями об устаревании какого-либо старого функционала. Данный функционал в будущем будет удалён в очередном мажорном релизе. Релиз Symfony 7.2 не стал исключением из этого списка и включил в себя некоторый функционал, который будет удалён через год с релизом версии 8.0.

Опции конфигурации ID сессий 

Сам язык PHP уже включает в себя функционал сессий и возможность их настройки в php.ini с помощью опций session.sid_length и session.sid_bits_per_character. В PHP 8.4 данные опции были помечены как устаревшие, т.к. пользователи могли выставлять слишком короткие и небезопасные значения или наоборот, выставлять слишком длинные значения, которые лишь нагружают процессор, но при этом не сильно влияют на безопасность. В любом случае, особого смысла их отдельно настраивать нет.

В Symfony в настройках сессий так же можно было использовать подобный функционал в секции framework.session конфигурации сессий и в версии 7.2 он так же был помечен как устаревший.

Настройки сборщика мусора по умолчанию

При запуске сессий PHP вызывает обработчик сборщика мусора случайным образом на основе вероятности, определяемой php.ini в настройках session.gc_probability и session.gc_divisor. Вероятность определяется соотношением значения вероятности (probability) к делителю (divisor). Например, значения 5 и 100 означают соотношение 5 к 100, что равняется 5% вероятности вызова сборщика мусора.

В Symfony опция session.gc_probability выставлялась явно и имела значение по умолчанию 1. Это значение переопределяло соответствующую настройку php.ini, что не совсем очевидно. В Symfony 7.2 это значение по умолчанию было удалено и теперь по умолчанию будут использоваться настройки из php.ini.

Но в любом случае механизм сессий по-умолчанию, встроенный в PHP, довольно ограничен, так что всё равно рекомендуется использовать собственный обработчик или те "драйвера", которые поставляются вместе с Symfony.

Другие опции сессий

Помимо вышеперечисленного, в PHP 8.4 были помечены как устаревшие и другие настройки сессий. Соответственно, при использовании драйвера NativeSessionStorage теперь не рекомендуется использовать следующие связанные настройки:

  • referer_check - использовался для проверки заголовка Referer на наличие подстроки, определенной в этой опции. Если данные не совпадали, то сессия считалась недействительной.

  • use_only_cookies - указывало на то, будет ли расширение сессий использовать только заголовок cookie для хранения идентификатора сессий на стороне клиента.

  • use_trans_sid - в документации по PHP сказано, что эта опция отвечала за, цитата: "прозрачную поддержку sid". Это довольно старый функционал, который вместо хранения идентификаторов сессий в cookie использовал query-аргументы для передачи ID сессий, вида: site.ru?PHPSESSID=DEADBEEF. Соответственно, данный функционал модифицировал отдаваемый HTML "на-лету", добавляя аргумент PHPSESSID в каждую ссылку.

  • trans_sid_hosts - задавала хосты для перезаписи для включения идентификатора сессии при включении функционала "прозрачных sid". То есть, если не путаю, проверяла ссылки на соответствие указанным адресам и только тогда их модифицировала.

  • trans_sid_tags - содержала HTML-теги в которых будет автоматом вставляться идентификатор сессии. Формат тегов был вида "название тега" + "=" + "атрибут тега": a=href,area=href,frame=src,input=src,form=, где form - специальный тег, добавляющий внутрь <input hidden="session_id" name="session_name">.

Очевидно, что это какой-то древний набор "костылей и велосипедов". Не удивительно, что его удалили из языка. Ну и понятно почему удалили из Symfony вслед за PHP 8.4.

Пустые идентификаторы пользователей

Раннее, интерфейс UserInterface::getUserIdentifier() возвращал просто строку. Это не совсем корректно с точки зрения семантики и вообще здравой логики. Поэтому в Symfony 7.2 этот метод должен возвращать "непустую строку". 

Некоторые аутентификаторы, такие как FormLoginAuthenticator и JsonLoginAuthenticator, уже обновлены в соответствии с этим поведением и не позволяют больше идентификаторам пользователей быть пустыми, проверяя это поведение.

Вообще, в некотором роде это разрыв обратной совместимости, однако, кажется, вряд ли найдётся кто-то, кто зачем-то использовал данный функционал.

Использование "!tagged" тега

Данное изменение касается DI-контейнера. Раньше для передачи сервисов, помеченных тегом можно было использовать как !tagged, так и !tagged_iterator.

Если не путаю, то это было псевдонимами, однако использование !tagged не совсем очевидно, т.к. помимо !tagged_iterator есть ещё и !tagged_locator, например, поэтому его объявили устаревшим в Symfony 7.2.

Так что теперь желательно избавиться от этого псевдонима и указывать !tagged_iterator явно.

services:
  App\ExampleFactory:
    arguments:
      # Внедряем все драйвера, помеченные тегом "app.example_driver"
      # в аргумент "$drivers" сервиса "App\ExampleFactory"
      $drivers: !tagged_iterator 'app.example_driver'

Источник: symfony.com

Изменения framework.secret опции

Одним из довольно известных параметров конфигурации Symfony является secret, который можно настроить с помощью параметра framework.secret или APP_SECRET переменной окружения в одном из файлов .env. В конечном итоге само значение передаётся в параметр kernel.secret в приложении, вне зависимости от того как оно было настроено. 

В любом случае, данная опция больше не нужна в приложениях Symfony 7.2 и требуется только в дополнительном функционале, который её использует, так что настраивать её отдельно в явном виде больше не требуется.

Естественно, настройка потребуется если вы используете, например:

  • Ссылки для входа в систему без пароля;

  • Функционал «Запомнить Меня» (Remember Me)  для автоматического входа в систему на основе предыдущих сеансов;

  • Ограничитель скорости (Rate Limiter) для контроля частоты определенных действий;

  • ESI для подключаемого контента при использовании HTTP-кэширования.

В том случае, если вы создаёте новое приложение на Symfony 7.2, то значение "секрета" будет теперь пустым по умолчанию. В том же случае, если вы решите использовать какой-либо функционал, требующий этот "секрет", то будет выброшено соответствующее исключение, если значение "секрета" не будет задано.

На всякий случай хочу напомнить, что Symfony по умолчанию использует довольно небезопасную и странную схему с "коммитами .env в Git", которые содержат значения по умолчанию, а конкретные значения под определённое окружение допускались в .env.dev, .env.prod, .env.test и проч. 

Схема работы довольно бессмысленная и я пока не встречал её использование в реальной продуктовой разработке. Так или иначе, каждый её переделывал на более удобный вариант с игнором любого .env файла, где сам файл .env и был точкой истинности для определённого окружения, и выставляется (создаётся) отдельно на каждом из окружении руками.

Так вот, оказывается что кто-то использует эту схему по умолчанию. Поэтому изменения коснулись и этого поведения во время локальной разработки Symfony, где фреймворк генерировал этот секрет в файле .env, что могло приводить к проблемам с безопасностью (т.к. .env коммитился в Git). Теперь значение будет генерироваться в файле .env.dev, что поможет избежать проблем, когда "секрет" был общим для локального и боевого стендов.

Ну в общем, всё по классике. Сами создаём себе проблемы - сами героически решаем.

Источник: symfony.com

Обсудить новости можно в чате по Symfony - https://t.me/symfony_cutcode

Видео версия дайджеста

 

Комментарии (5)


  1. psman
    09.12.2024 12:12

    В текущий реалиях новости о Шторме как то кажутся чуточку бесполезными после аннулирование купленных ранее лицензий.


  1. zorn-v100500
    09.12.2024 12:12

    Схема работы довольно бессмысленная и я пока не встречал её использование в реальной продуктовой разработке. Так или иначе, каждый её переделывал на более удобный вариант с игнором любого .env файла, где сам файл .env и был точкой истинности для определённого окружения, и выставляется (создаётся) отдельно на каждом из окружении руками.

    Очень даже удобно, когда в `.env` видно какие локальные настройки можно переопределить. Если открыть хоть через 10 лет, не надо будет по всему коду искать, как же эта переменная называлась.

    А настоящие настройки храню в `.env.local`, так что с "каждый её переделывал" вы погорячились ;)

    Более того, я подобную схему использую не только в PHP - в vuejs тоже что то подобное по умолчанию


    1. SerafimArts
      09.12.2024 12:12

      Согласен, чуть-чуть чувствуется что перегнул с иронией.

      Однако не поверите - реально на каждом проекте (говорю про 3 разных, начиная с симфы 3.4, заканчивая 7.х) видел другую, где всё ровно наоборот: .env.example как шаблон, а .env уже реальные значения под гитигнором. Точно такая же схема и в ларке идёт по умолчанию. А оригинальную симфонёвую схему ни разу не встречал.


      1. zorn-v100500
        09.12.2024 12:12

        Ну с лары наверное эта привычка и перекочевала. Только непонятно зачем себе усложнять жизнь - symfony flex например пишет в .env умолчания с маркерами (чтобы почистить при удалении пакета) если есть в рецепте.


        1. SerafimArts
          09.12.2024 12:12

          Ну не только лара, ещё раньше докер подхватывал env файл по дефолту в какой-то версии, а потом заменили на явное указание env_file секции с require: false, решения под ноду ещё. Рекомендации в Rails такие же. Да куча, подозреваю, решений, где рекомендуется именно использование файла .env для текущего окружения, а не его оверрайд.

          Единственное исключение, которое я встречал (помимо дефолтов Symfony) - это Sentry, там .env по-дефолту, а для оверрайда предлагают использовать .env.custom.

          Но в любом случае, по-мне, это примерно тоже самое как поставлять php.ini в качестве шаблона, а чтоб переопределить - надо создать файл php.local.ini. Странное решение.

          P.S. Я уже признался, что погорячился. Можно списать на то, что автор дайджеста по симфе -- токсик и просто хейтит "непривычные для него решения")