Привет! Сегодня мы расскажем, какие нововведения появились в контроллерах ядра за последнее время.
Для начала вспомним, что контроллеры — это часть MVC архитектуры, которая отвечает за обработку запроса и генерирование ответа.
Про MVC много чего написано, поэтому не будем акцентировать на этом внимание. Лучше вспомним, что контроллеры состоят из одного или нескольких действий.
А еще для действий контроллеров доступно автоматическое связывание. Мы уже рассказывали об этом тут. Если не читали, то рекомендую посмотреть. Так будет легче понимать то, о чем мы сегодня вам расскажем.
Часто возникает ситуация, когда нужно выполнить какой-либо код до или после выполнения действия контроллера. К примеру, если мы пишем действие создания задачи, то может быть уместно перед вызовом самого действия проверить, передан ли правильный заголовок Content-Type.
Такой код (выполняемый перед действием контроллера) мы разбиваем по классам и называем префильтрами. Код, который выполняется после действия контроллера — постфильтрами
Фильтры
Есть 2 способа конфигурировать (управлять префильтрами и постфильтрами) действия контроллера. Первый — переопределить метод configureActions:
<?php use Bitrix\Main\Engine\ActionFilter\Authentication; use Bitrix\Main\Engine\Controller; final class Entity extends Controller { public function configureActions() { return [ 'get' => [ 'prefilters' => [ new Authentication(), ], ], ]; } public function getAction(string $id) { // ... } }
Скоро будет доступен второй способ — конфигурация через атрибуты методов. Этот подход более современный и читаемый, но в то же время поддерживает всё, что можно сконфигурировать через configureActions:
<?php use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Prefilters; use Bitrix\Main\Engine\ActionFilter\Authentication; use Bitrix\Main\Engine\Controller; final class Entity extends Controller { #[Prefilters([ new Authentication() ])] public function getAction(string $id) { // ... } }
Одновременное использование старого и нового вариантов недопустимо и будет встречено исключением Bitrix\Main\Engine\Exception\ActionConfigurationException.
Также поддерживаются дополняющие и вычитающие конструкции. Старый вариант через configureActions:
<?php use Bitrix\Main\Engine\ActionFilter\Authentication; use Bitrix\Main\Engine\ActionFilter\Csrf; use Bitrix\Main\Engine\Controller; final class Entity extends Controller { public function configureActions() { return [ 'get' => [ '+prefilters' => [ new Authentication(), ], '-prefilters' => [ new Csrf(), ], ], ]; } public function getAction(string $id) { // ... } }
Новый вариант через атрибуты:
<?php use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\DisablePrefilters; use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\EnablePrefilters; use Bitrix\Main\Engine\ActionFilter\Authentication; use Bitrix\Main\Engine\ActionFilter\Csrf; use Bitrix\Main\Engine\Controller; final class Entity extends Controller { #[EnablePrefilters([ new Authentication() ])] #[DisablePrefilters([ new Csrf() ])] public function getAction(string $id) { // ... } }
И полностью аналогично для постфильтров:
<?php use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\DisablePostfilters; use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\EnablePostfilters; use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Postfilters; use Bitrix\Main\Engine\ActionFilter\ClosureWrapper; use Bitrix\Main\Engine\ActionFilter\Cors; use Bitrix\Main\Engine\Controller; final class Entity extends Controller { #[Postfilters([ new Cors(), ])] #[EnablePostfilters([ new Cors(), ])] #[DisablePostfilters([ new ClosureWrapper(), ])] public function getAction(string $id) { // ... } }
Для удобства использования, чтобы не перечислять нужные пре- и постфильтры в массиве, можно использовать сразу же нужные атрибуты:
<?php use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Authentication; use Bitrix\Main\Engine\Controller; final class Entity extends Controller { #[Authentication()] public function getAction(string $id) { // ... } }
Аргументы атрибутов идентичны аргументам самих экшн фильтров, за исключением последнего аргумента filterType. Он используется для дополнения или вычитания экшн фильтров.
На приведенном далее примере методы get и list будут иметь одинаковые префильтры:
<?php use Bitrix\Main\Engine\ActionFilter\Attribute\Rule; use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\DisablePrefilters; use Bitrix\Main\Engine\ActionFilter\Attribute\Rule\EnablePrefilters; use Bitrix\Main\Engine\ActionFilter\Authentication; use Bitrix\Main\Engine\ActionFilter\Csrf; use Bitrix\Main\Engine\ActionFilter\FilterType; use Bitrix\Main\Engine\Controller; final class Entity extends Controller { #[EnablePrefilters([ new Authentication() ])] #[DisablePrefilters([ new Csrf() ])] public function getAction(string $id) { // ... } #[Rule\Authentication(type: FilterType::EnablePrefilter)] #[Rule\Csrf(type: FilterType::DisablePrefilter)] public function listAction() { // ... } }
Из коробки для всех имеющихся экшн фильтров, продублированы соответствующие атрибуты:
-
Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Authentication -
Bitrix\Main\Engine\ActionFilter\Attribute\Rule\CloseSession -
Bitrix\Main\Engine\ActionFilter\Attribute\Rule\ContentType -
Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Cors -
Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Csrf -
Bitrix\Main\Engine\ActionFilter\Attribute\Rule\HttpMethod -
Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Scope -
Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Token
Как это работает под капотом
Прежде всего, все атрибуты используют внутри себя привычные фильтры. Вот пример, как выглядит атрибут Cors:
<?php declare(strict_types=1); namespace Bitrix\Main\Engine\ActionFilter\Attribute\Rule; use Attribute; use Bitrix\Main\Engine\ActionFilter\Attribute\FilterAttributeInterface; use Bitrix\Main\Engine\ActionFilter\FilterType; #[Attribute(Attribute::TARGET_METHOD)] final class Cors implements FilterAttributeInterface { public function __construct( private readonly ?string $origin = null, private readonly ?bool $credentials = null, private readonly FilterType $type = FilterType::EnablePrefilter, ) { } public function getFilters(): array { if ($this->type->isNegative()) { return [\Bitrix\Main\Engine\ActionFilter\Cors::class]; } return [new \Bitrix\Main\Engine\ActionFilter\Cors($this->origin, $this->credentials)]; } public function getType(): FilterType { return $this->type; } }
Вначале он принимает те параметры, которые принимает сам \Bitrix\Main\Engine\ActionFilter\Cors.
Далее, в каждом атрибуте есть параметр FilterType — он и определяет тип фильтра. Вот так он выглядит:
enum FilterType: string { case Prefilter = 'prefilters'; case Postfilter = 'postfilters'; case EnablePrefilter = '+prefilters'; case DisablePrefilter = '-prefilters'; case EnablePostfilter = '+postfilters'; case DisablePostfilter = '-postfilters'; public function isNegative(): bool { return in_array($this, [self::DisablePrefilter, self::DisablePostfilter], true); } }
Как можно заметить, ключи аналогичны методу configureActions.
Поэтому, если в нашем примере мы захотим сделать так, чтобы CloseSession был выключен, то мы сделаем так:
class Task extends \Bitrix\Main\Engine\Controller { #[\Bitrix\Main\Engine\ActionFilter\Attribute\Rule\CloseSession(type: FilterType::DisablePrefilter)] public function getAction(int $id): ?array { // ... return $task; } }
Аналогично для постфильтров.
Если необходимо не выключить или включать заданные постфильтры и префильтры, то можно воспользоваться атрибутами \Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Prefilters, \Bitrix\Main\Engine\ActionFilter\Attribute\Rule\Postfilters
class Task extends \Bitrix\Main\Engine\Controller { #[Prefilters([new Csrf(), new ContentType([ContentType::JSON])])] public function getAction(int $id): ?array { // ... return $task; } }
Обратите внимание, что будут применены ТОЛЬКО перечисленные префильтры, а дефолтные — проигнорируются. Аналогично с постфильтрами
Создание своих атрибутов префильтров
Для этого необходимо:
-
Реализовать сам фильтр (наследник Base)
-
Написать класс атрибута, реализовав интерфейс:
interface FilterAttributeInterface { /** * @return (Base|string)[] */ public function getFilters(): array; public function getType(): FilterType; }
Пример:
#[Attribute(Attribute::TARGET_METHOD)] class MyCustomFilter implements \Bitrix\Main\Engine\ActionFilter\Attribute\FilterAttributeInterface { public function __construct( private readonly array $args, private readonly FilterType $type = FilterType::EnablePrefilter, ) { } public function getFilters(): array { return [new MyCustomBaseFilter($this->args)] } public function getType(): FilterType { return $this->type; } }
Валидация
Мы также упростили валидацию параметров в контроллерах. Новой валидации мы посвятили целую статью.
Ознакомьтесь с ней, если раньше не видели — это поможет лучше понимать то, что будет дальше.
Теперь же мы улучшили связку между контроллерами и валидацией.
Напомню, что раньше для использования валидации в действиях контроллера было необходимо создавать объект, который необходимо прокинуть в специальную обертку — \Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter:
class UserController extends Controller { public function getAutoWiredParameters() { return [ new \Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter( CreateUserDto::class, fn() => CreateUserDto::createFromRequest($this->getRequest()), ), ]; } public function createAction(CreateUserDto $dto): Result { // create logic ... } }
Теперь провалидировать входные данные можно и без явного указания \Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter. Достаточно указать в действии контроллера атрибуты валидации для аргументов, в том числе скалярных.
Например:
class UserController extends \Bitrix\Main\Engine\Controller { public function createAction( #[\Bitrix\Main\Validation\Rule\PhoneOrEmail] string $login, #[\Bitrix\Main\Validation\Rule\NotEmpty] string $password, #[\Bitrix\Main\Validation\Rule\NotEmpty] string $passwordRepeat ): array { // logic here } }
Более того, можно как и раньше передать объект, но вместо регистрации через getAutoWiredParameters достаточно будет добавить ему атрибут Validatable:
class UserController extends \Bitrix\Main\Engine\Controller { public function createAction( #[\Bitrix\Main\Validation\Rule\Recursive\Validatable] CreateUserDto $dto) : Result { // create logic ... } }
Этот пакет уже готовится к выпуску внутри модуля main и совсем скоро (в этом релизе) его можно будет использовать в ваших проектах на базе Bitrix Framework.
ссылка на оригинал статьи https://habr.com/ru/articles/924732/
Добавить комментарий