Использование валидации
Введение
Иногда необходимо проверить, что данные корректны, не привязываясь к бизнес-логике. Например, идентификатор пользователя не должен быть меньше 1.
public function __construct(int $userId) { if ($userId <= 0) { throw new \Exception(); } $this->userId = $userId; }
Такие проверки могут увеличивать объем кода, поэтому для упрощения используется валидация через атрибуты.
Установка правил валидации
Рассмотрим пример класса пользователя.
final class User { private ?int $id; private ?string $email; private ?string $phone; // getters & setters ... }
Ограничения:
id
больше 0,email
— валидный адрес,phone
— валидный телефон,- заполнен
email
илиphone
.
Добавим атрибуты валидации.
use Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty; use Bitrix\Main\Validation\Rule\Email; use Bitrix\Main\Validation\Rule\Phone; use Bitrix\Main\Validation\Rule\PositiveNumber; #[AtLeastOnePropertyNotEmpty(['email', 'phone'])] final class User { #[PositiveNumber] private ?int $id; #[Email] private ?string $email; #[Phone] private ?string $phone; // getters & setters ... }
Теперь можно валидировать через \Bitrix\Main\Validation\ValidationService
, который доступен через локатор по ключу main.validation.service
. Это позволяет валидировать класс в том месте, где нужно. Например, при сохранении в базу данных.
use Bitrix\Main\DI\ServiceLocator; use Bitrix\Main\Validation\ValidationService; class UserService { private ValidationService $validation; public function __construct() { $this->validation = ServiceLocator::getInstance()->get('main.validation.service'); } public function create(?string $email, ?string $phone): Result { $user = new User(); $user->setEmail($email); $user->setPhone($phone); $result = $this->validation->validate($user); if (!$result->isSuccess()) { return $result; } // save logic ... } }
ValidationService
предоставляет метод validate()
, возвращающий ValidationResult
. Результат валидации содержит ошибки всех сработавших валидаторов.
- Модификаторы доступа у свойств не учитываются, валидация происходит через рефлексию.
- Если атрибут
nullable
и его значение не установлено, он будет пропущен при валидации.
Валидация вложенных объектов
Если объект сложный и содержит вложенные объекты, можно использовать атрибут \Bitrix\Main\Validation\Rule\Recursive\Validatable
. Это укажет, что объект также должен быть провалидирован.
use Bitrix\Main\Validation\Rule\Composite\Validatable; use Bitrix\Main\Validation\Rule\NotEmpty; use Bitrix\Main\Validation\Rule\PositiveNumber; class Buyer { #[PositiveNumber] public ?int $id; #[Validatable] public ?Order $order; } class Order { #[PositiveNumber] public int $id; #[Validatable] public ?Payment $payment; } class Payment { #[NotEmpty] public string $status; #[NotEmpty(errorMessage: 'Custom message error')] public string $systemCode; } // validation /** @var \Bitrix\Main\Validation\ValidationService $validationService */ $validationService = \Bitrix\Main\DI\ServiceLocator::getInstance()->get('main.validation.service'); $buyer = new Buyer(); $buyer->id = 0; $result1 = $validationService->validate($buyer); // "id: Значение поля меньше допустимого" foreach ($result1->getErrors() as $error) { echo $error->getCode() . ': ' . $error->getMessage(). PHP_EOL; } echo PHP_EOL; $buyer->id = 1; $order = new Order(); $order->id = -1; $buyer->order = $order; $result2 = $validationService->validate($buyer); // "order.id: Значение поля меньше допустимого" foreach ($result2->getErrors() as $error) { echo $error->getCode() . ': ' . $error->getMessage(). PHP_EOL; } echo PHP_EOL; $buyer->order->id = 123; $payment = new Payment(); $payment->status = ''; $payment->systemCode = ''; $buyer->order->payment = $payment; $result3 = $validationService->validate($buyer); // "order.payment.status: Значение поля не может быть пустым" // "order.payment.systemCode: Custom message error" foreach ($result3->getErrors() as $error) { echo $error->getCode() . ': ' . $error->getMessage(). PHP_EOL; }
Валидация в контроллерах
Пример валидации в контроллере.
use Bitrix\Main\Validation\Rule\NotEmpty; use Bitrix\Main\Validation\Rule\PhoneOrEmail; final class CreateUserDto { public function __construct( #[PhoneOrEmail] public ?string $login, #[NotEmpty] public ?string $password, #[NotEmpty] public ?string $passwordRepeat, ) {} }
В коде класс будет выглядеть так:
class UserController extends Controller { private ValidationService $validation; protected function init() { parent::init(); $this->validation = ServiceLocator::getInstance()->get('main.validation.service'); } public function createAction(): Result { $dto = new CreateUserDto(); $dto->login = (string)$this->getRequest()->get('login'); $dto->password = (string)$this->getRequest()->get('password'); $dto->passwordRepeat = (string)$this->getRequest()->get('passwordRepeat'); $result = $this->validation->validate($dto); if (!$result->isSuccess()) { $this->addErrors($result->getErrors()); return false; } // create logic ... } }
Чтобы избежать повторения кода, создайте фабричный метод в DTO:
final class CreateUserDto { public function __construct( #[PhoneOrEmail] public ?string $login = null, #[NotEmpty] public ?string $password = null, #[NotEmpty] public ?string $passwordRepeat = null, ) {} public static function createFromRequest(\Bitrix\Main\HttpRequest $request): self { return new static( login: (string)$request->getRequest()->get('login'), password: (string)$request->getRequest()->get('password'), passwordRepeat: (string)$request->getRequest()->get('passwordRepeat'), ); } }
Воспользуемся автоварингом контроллера и специальным классом 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 ... } }
Если объект CreateUserDto
не валиден, метод createAction
не выполнится. Контроллер вернет ошибку.
{ data: null, errors: [ { code: "name", customData: null, message: "Значение поля не должно быть пустым", }, ], status: "error" }
Валидаторы без атрибутов
Валидаторы можно использовать и без атрибутов.
use Bitrix\Main\Validation\Validator\EmailValidator; $email = 'bitrix@bitrix.com'; $validator = new EmailValidator(); $result = $validator->validate($email); if (!$result->isSuccess()) { // ... }
Собственное сообщение об ошибке
Можно указать свой текст ошибки, который будет возвращен после валидации.
use Bitrix\Main\Validation\Rule\PositiveNumber; class User { public function __construct( #[PositiveNumber(errorMessage: 'Invalid ID!')] public readonly int $id ) { } } $user = new User(-150); /** @var \Bitrix\Main\Validation\ValidationService $service */ $result = $service->validate($user); foreach ($result->getErrors() as $error) { echo $error->getMessage(); } // output: 'Invalid ID!'
Стандартная ошибка валидатора:
use Bitrix\Main\Validation\Rule\PositiveNumber; class User { public function __construct( #[PositiveNumber] public readonly int $id ) { } } $user = new User(-150); /** @var \Bitrix\Main\Validation\ValidationService $service */ $result = $service->validate($user); foreach ($result->getErrors() as $error) { echo $error->getMessage(); } // output: 'Значение поля меньше допустимого'
Получить сработавший валидатор
Результат валидации хранит ошибки \Bitrix\Main\Validation\ValidationError
, которые содержат свойство $this->failedValidator
.
$errors = $service->validate($dto)->getErrors(); foreach ($errors as $error) { $failedValidator = $error->getFailedValidator(); // ... }
Список доступных атрибутов
Свойства:
ElementsType
— все элементы перечисляемого свойства должны быть заданного типаEmail
InArray
— значение свойства является одним из элементов массива (для случаев, когда по какой-то причине не удалось использоватьEnum
)Length
Max
Min
NotEmpty
Phone
PhoneOrEmail
— свойство является либо телефоном, либо почтойPositiveNumber
Range
RegExp
Url
Json
— свойство (строка) является Json
Классы:
AtLeastOnePropertyNotEmpty
— проверяет, что хотя бы одно свойство из заданных не пустое (названия свойств прокидываются в конструктор)
Список доступных валидаторов
AtLeastOnePropertyNotEmpty
— проверяет, что хотя бы одно свойство из заданных не пустое (названия свойств прокидываются в конструктор)Email
InArray
— значение свойства является одним из элементов массива (для случаев, когда по какой-то причине не удалось использоватьEnum
)Length
Max
Min
NotEmpty
Phone
RegExp
Url
Json
— значение переменной (строка) является Json