Проверка входных данных атрибутами - правила, результат, контроллер
Проверяем данные формы и запроса штатными правилами вместо россыпи условий: атрибуты на полях, единый результат и подстановка проверенных данных в действие.
Решение
Правила проверки описывают атрибутами прямо рядом с самими полями. Такой код читается как описание контракта, а не как цепочка условий, разбросанная по обработчику.
Описываем правила на полях:
use Bitrix\Main\Validation\Rule\{Email, NotEmpty, Range};
final class FeedbackDto{ public function __construct( #[NotEmpty] public ?string $name = null, #[Email] public ?string $email = null, #[Range(1, 100)] public ?int $amount = null, // одна граница вместо двух правил ) {}}Атрибуты описывают ожидания от каждого поля по отдельности. Для диапазона берут одно правило с двумя границами, а не пару правил на минимум и максимум.
Проверяем объект сервисом:
$validation = \Bitrix\Main\DI\ServiceLocator::getInstance()->get('main.validation.service');$result = $validation->validate($dto);
if (!$result->isSuccess()) { foreach ($result->getErrors() as $error) { $errors[] = $error->getMessage(); }}// сначала проверяются правила полей, потом правила уровня классаСервис проверки берут из общего реестра по известному ключу. Результат единый: признак успеха и список ошибок с готовыми сообщениями для показа человеку.
Описываем инвариант между полями:
use Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty;
#[AtLeastOnePropertyNotEmpty(['email', 'phone'])]final class ContactDto { /* поля со своими правилами */ }// правило уровня класса проверяет связь между полями, а не одно полеСвязь между несколькими полями выражают отдельным атрибутом на классе. Ручная проверка «хоть что-то из двух заполнено» в обработчике повторяется в каждом сценарии и рано или поздно расходится.
Подставляем проверенные данные в действие:
use Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter;
public function getAutoWiredParameters(): array{ return [new ValidationParameter(FeedbackDto::class, static fn ($className, \Bitrix\Main\HttpRequest $request) => FeedbackDto::createFromRequest($request))];}// в действие приходит уже проверенный объект, а не сырой запросДействие контроллера получает уже готовый и проверенный объект данных. Проверка при этом происходит до вызова кода действия, и обрабатывать сырые значения запроса больше не нужно.
Для одного или двух скалярных параметров действия достаточно атрибутов прямо на них. Заводить отдельный класс данных ради проверки одного числа - лишняя работа, от которой код только запутывается.
Вложенные объекты проверяются только отдельным правилом рекурсивной проверки. Без него вложенная структура остаётся непроверенной, даже когда у неё описаны собственные правила на каждом поле.
Объект, собранный вне контроллера, проверяют явным вызовом сервиса проверки. Автоматическая подстановка работает только для действий, а служебный код вызывает проверку сам.
Типичные проблемы
Проверка пропускает пустой массив.
Правило типа элементов проверяет только их тип, но вовсе не наличие. Рядом с ним ставят отдельное правило непустоты значения.
Ошибка приходит раньше собственного правила.
Неинициализированное поле без значения по умолчанию падает ещё до проверки. Таким полям задают значение по умолчанию или делают их допускающими пустоту.
Правило на поле не срабатывает.
Поле допускает пустое значение и в запросе осталось незаполненным. Такие поля валидация просто пропускает, и обязательность задают отдельным правилом.
Проверка связи между полями расползлась по коду.
Инвариант описан обычными условиями в обработчике вместо атрибута на классе. Правило уровня класса собирает всю такую проверку в одном месте.
В действии всё равно приходится проверять запрос.
Объект данных собирается вручную, без штатной подстановки проверенного объекта. Тогда проверка выполняется позже и часть кода действия работает с сырыми значениями.
Частые вопросы
Когда заводить отдельный класс данных?
Когда полей несколько, они связаны между собой или требуют нормализации. Для одного скалярного параметра хватает атрибута прямо на параметре действия.
Что делать с правилами, которых нет в наборе?
Штатные правила покрывают типовые случаи, а специфичные проверки остаются обычным кодом. Смешивать их допустимо: атрибуты для формата, код для бизнес-условий.
Проверяются ли закрытые свойства?
Да, проверка работает через отражение и не смотрит на модификаторы доступа. Порядок фиксирован: сначала правила полей, потом правила класса.
Заменяет ли это проверку на стороне браузера?
Нет, и наоборот тоже: браузер помогает человеку заполнить форму, сервер защищает данные. Запрос приходит и мимо формы.
Как показать ошибки покупателю?
Из единого результата: он отдаёт список ошибок с сообщениями. Их возвращают в ответ действия и показывают рядом с полями формы.
Смежное
- AJAX на практике - оглавление подтемы
- AJAX-запрос: контроллер, свой файл и ответ в JSON - куда приходят данные формы
- Отправка формы через AJAX: проверка полей, защита, ответ с ошибками - показ ошибок в форме
- Не работает AJAX-запрос: разбор причин - когда данные не доезжают
- Данные от посетителя: экранирование, проверка, сеанс - зачем проверять на сервере
- Подсистемы ядра D7: логирование, валидация, GeoIP, Stepper - устройство подсистемы целиком
- Ядро BX и AJAX - вызовы со стороны браузера
- Контроллер изнутри: маршрут, действие, фильтры, ответ - как фильтры и атрибуты складываются в цепочку