From a860a9510f8af058478543269bdebf77a7f2574a Mon Sep 17 00:00:00 2001 From: Andrei Nikolaev Date: Mon, 26 Jan 2026 21:01:44 +0300 Subject: [PATCH 1/3] Split validator into several pages --- pages/framework/validation.md | 467 ----------------------- pages/framework/validation/controller.md | 116 ++++++ pages/framework/validation/main.md | 256 +++++++++++++ pages/framework/validation/own.md | 256 +++++++++++++ 4 files changed, 628 insertions(+), 467 deletions(-) create mode 100644 pages/framework/validation/controller.md create mode 100644 pages/framework/validation/main.md create mode 100644 pages/framework/validation/own.md diff --git a/pages/framework/validation.md b/pages/framework/validation.md index 6c3c797..0f5e327 100644 --- a/pages/framework/validation.md +++ b/pages/framework/validation.md @@ -1,317 +1,3 @@ ---- -title: Валидация -description: 'Валидация. Документация по Bitrix Framework: принципы работы, архитектура и примеры использования.' ---- - -Валидация данных -- это проверка информации на соответствие заданным правилам. Например, числовой идентификатор должен быть положительным, а email -- соответствовать формату адреса. - -## Как использовать валидацию - -В Bitrix Framework валидацию можно реализовать разными способами. Самый простой способ -- ручные проверки в конструкторе или методах. Такой подход приводит к дублированию кода и усложняет его поддержку. - -```php -public function __construct(int $userId) -{ - if ($userId <= 0) - { - throw new \Exception(); - } - $this->userId = $userId; -} -``` - -Чтобы сократить код, используйте систему валидации на основе атрибутов. Она позволяет: - -- задавать правила в классах, - -- автоматически проверять данные при создании объектов, - -- централизованно обрабатывать ошибки. - -### Как создать правила в классе - -1. Создайте класс. Например, `User` со свойствами `id`, `email` и `phone`. - - ```php - final class User - { - private ?int $id; - private ?string $email; - private ?string $phone; - - // getters & setters ... - } - ``` - -2. Добавьте атрибуты валидации: `#[PositiveNumber]`, `#[Email]` и `#[Phone]`. - - ```php - 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... - } - ``` - -3. Проверьте валидацию. Объект можно проверить через `\Bitrix\Main\Validation\ValidationService` по ключу `main.validation.service`. - - `ValidationService` предоставляет метод `validate()`, который возвращает `ValidationResult`. Результат валидации содержит ошибки всех сработавших валидаторов. - - ```php - 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 ... - } - } - ``` - -{% note warning "" %} - -- Валидация работает через рефлексию, модификаторы доступа не учитываются. - -- Если свойство помечено как `nullable` и не заполнено, валидация пропускает его. - -{% endnote %} - -### Вложенные объекты - -Используйте атрибут `#[Validatable]` для вложенных объектов. - -```php -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; -} -``` - -### Валидация в контроллерах - -В контроллерах валидация помогает убедиться в корректности данных из запроса. - -```php -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, - ) - {} -} -``` - -В коде класс будет выглядеть следующим образом: - -```php -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, чтобы избежать повторения кода. - -```php -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` устранит повторяющуюся логику валидации. - -```php -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` не будет выполнен. Контроллер вернет ошибку. - -```php -{ - data: null, - errors: - [ - { - code: "name", - customData: null, - message: "Значение поля не должно быть пустым", - }, - ], - status: "error" -} -``` - -### Валидаторы без атрибутов - -Валидаторы можно применять без атрибутов для разовой проверки данных, когда нет необходимости описывать правила в объекте. Это подходит для старого кода с массивами и нетипизированными переменными. - -```php -use Bitrix\Main\Validation\Validator\EmailValidator; -$email = 'bitrix@bitrix.ru'; -$validator = new EmailValidator(); -$result = $validator->validate($email); -if (!$result->isSuccess()) -{ - // ... -} -``` ### Сообщение об ошибке после валидации @@ -434,156 +120,3 @@ Bitrix Framework предоставляет готовые атрибуты и - `JsonValidator` -- валидация JSON. -## Как создать собственные валидаторы - -Каждый валидатор реализует интерфейс `\Bitrix\Main\Validation\Validator\ValidatorInterface` с методом `public function validate(mixed $value): ValidationResult`. - -Валидатор выполняет простую задачу -- проверяет значение. Он не определяет, относится ли значение к свойству или классу, и не зависит от атрибутов. - -### Пример валидатора Min - -1. Класс `Min` реализует интерфейс `ValidatorInterface`. - -2. Конструктор принимает минимальное значение. - -3. Метод `validate` создает объект `ValidationResult`, который хранит результаты проверки. - - - Сначала он проверяет, является ли значение числом. Если нет, добавляется ошибка. - - - Затем проверяет, меньше ли значение заданного минимума. Если да, добавляется соответствующая ошибка. - -4. В конце метод возвращает объект `ValidationResult` с результатами проверки. - -```php -namespace Bitrix\Main\Validation\Validator; -use Bitrix\Main\Localization\Loc; -use Bitrix\Main\Validation\ValidationError; -use Bitrix\Main\Validation\ValidationResult; -use Bitrix\Main\Validation\Validator\ValidatorInterface; -final class Min implements ValidatorInterface -{ - public function __construct( - private readonly int $min - ) - { - } - public function validate(mixed $value): ValidationResult - { - $result = new ValidationResult(); - if (!is_numeric($value)) - { - $result->addError( - new ValidationError( - Loc::getMessage('MAIN_VALIDATION_MIN_NOT_A_NUMBER'), - failedValidator: $this - ) - ); - return $result; - } - if ($value < $this->min) - { - $result->addError( - new ValidationError( - Loc::getMessage('MAIN_VALIDATION_MIN_LESS_THAN_MIN'), - failedValidator: $this - ) - ); - } - return $result; - } -} -``` - -## Как создать атрибуты валидации - -Атрибуты валидации разделены на два типа: для свойств и для классов. - -### Атрибуты свойств - -Атрибуты свойств реализуют интерфейс `\Bitrix\Main\Validation\Rule\PropertyValidationAttributeInterface`. Они используют метод `validateProperty(mixed $propertyValue): ValidationResult` для проверки значений свойств. - -Пример простого атрибута для проверки значения свойства: - -```php -use Bitrix\Main\Validation\Rule\PropertyValidationAttributeInterface; -use Bitrix\Main\Validation\ValidationError; -use Bitrix\Main\Validation\ValidationResult; - -#[Attribute(Attribute::TARGET_PROPERTY)] -class NotOne implements PropertyValidationAttributeInterface -{ - public function validateProperty(mixed $propertyValue): ValidationResult - { - $result = new ValidationResult(); - if ($propertyValue === 1) { - $result->addError(new ValidationError('Значение не должно быть равно 1')); - } - return $result; - } -} -``` - -Этот атрибут проверяет, что значение свойства не равно 1. Если условие нарушено, возвращается ошибка. - -Для сложных проверок используйте абстрактный класс `\Bitrix\Main\Validation\Rule\AbstractPropertyValidationAttribute`. Реализуйте метод `getValidators(): array`, чтобы вернуть список валидаторов. - -Пример атрибута `Range`, который проверяет, что значение находится в заданном диапазоне: - -```php -use Attribute; -use Bitrix\Main\Validation\Rule\AbstractPropertyValidationAttribute; -use Bitrix\Main\Validation\Validator\Implementation\Max; -use Bitrix\Main\Validation\Validator\Implementation\Min; - -#[Attribute(Attribute::TARGET_PROPERTY)] -final class Range extends AbstractPropertyValidationAttribute -{ - public function __construct( - private readonly int $min, - private readonly int $max, - protected ?string $errorMessage = null - ) {} - - protected function getValidators(): array - { - return [ - new Min($this->min), - new Max($this->max), - ]; - } -} -``` - -### Атрибуты класса - -Атрибуты класса реализуют интерфейс `\Bitrix\Main\Validation\Rule\ClassValidationAttributeInterface`. Они используют метод `validateObject(object $object): ValidationResult` для проверки объектов. - -Пример атрибута для проверки количества свойств: - -```php -use Bitrix\Main\Validation\ValidationResult; -use Bitrix\Main\Validation\ValidationError; -use Bitrix\Main\Validation\Rule\AbstractClassValidationAttribute; -use ReflectionClass; - -#[Attribute(Attribute::TARGET_CLASS)] -class NotOne extends AbstractClassValidationAttribute -{ - public function validateObject(object $object): ValidationResult - { - $result = new ValidationResult(); - $properties = (new ReflectionClass($object))->getProperties(); - - if (count($properties) > 2) { - $result->addError(new ValidationError('Класс содержит слишком много свойств')); - } - return $result; - } -} -``` - -Этот атрибут проверяет, что в классе не больше двух свойств. Если условие нарушено, метод вернет ошибку. - -### Сообщение об ошибке для атрибута - -Если вы наследуетесь от `AbstractClassValidationAttribute` или `AbstractPropertyValidationAttribute`, можно задать собственное сообщение об ошибке через свойство `$errorMessage`. Это позволит вернуть одну ошибку с вашим текстом вместо стандартных ошибок валидаторов. diff --git a/pages/framework/validation/controller.md b/pages/framework/validation/controller.md new file mode 100644 index 0000000..6a3545c --- /dev/null +++ b/pages/framework/validation/controller.md @@ -0,0 +1,116 @@ +--- +title: Валидация. Контроллеры +description: 'Валидация. Документация по Bitrix Framework: использование в контроллерах' +--- + +В контроллерах валидация используется для проверки корректности данных, поступающих из HTTP-запроса. Это позволяет убедиться, что входные параметры соответствуют ожидаемым форматам и бизнес-правилам до выполнения основной логики действия. + +Для валидации в контроллере создаётся DTO-класс с атрибутами правил: + +```php +use Bitrix\Main\Validation\Rule\NotEmpty; +use Bitrix\Main\Validation\Rule\PhoneOrEmail; + +final class CreateUserDto +{ + public function __construct( + #[PhoneOrEmail] + public ?string $login = null, + + #[NotEmpty] + public ?string $password = null, + + #[NotEmpty] + public ?string $passwordRepeat = null, + ) {} +} +``` + +Затем в контроллере данные из запроса передаются в DTO, и выполняется валидация: + +```php +use Bitrix\Main\DI\ServiceLocator; +use Bitrix\Main\Validation\ValidationService; + +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; + } + + // Логика создания пользователя... + } +} +``` + +## Автоматическая валидация через AutoWire + +Чтобы избежать дублирования кода преобразования запроса в DTO, рекомендуется добавить статический фабричный метод в DTO: + +```php +use Bitrix\Main\HttpRequest; +final class CreateUserDto +{ + // ... свойства и конструктор + + public static function createFromRequest(HttpRequest $request): self + { + return new static( + login: (string) $request->get('login'), + password: (string) $request->get('password'), + passwordRepeat: (string) $request->get('passwordRepeat'), + ); + } +} +``` + +Bitrix Framework предоставляет механизм автоматической инъекции и валидации параметров с помощью `ValidationParameter`: + +```php +use Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter; +class UserController extends Controller +{ + public function getAutoWiredParameters() + { + return [ + new ValidationParameter( + CreateUserDto::class, + fn() => CreateUserDto::createFromRequest($this->getRequest()), + ), + ]; + } + + public function createAction(CreateUserDto $dto): Result + { + // Метод вызовется только если $dto прошёл валидацию. + // В противном случае контроллер вернёт ошибку в формате JSON: + // { + // "data": null, + // "errors": [...], + // "status": "error" + // } + + // Логика создания пользователя... + } +} +``` + +Если объект DTO не проходит валидацию, метод действия **не выполняется**, а контроллер автоматически возвращает клиенту список ошибок. Это позволяет полностью отделить логику валидации от бизнес-кода и упростить обработку некорректных запросов. \ No newline at end of file diff --git a/pages/framework/validation/main.md b/pages/framework/validation/main.md new file mode 100644 index 0000000..4cf4909 --- /dev/null +++ b/pages/framework/validation/main.md @@ -0,0 +1,256 @@ +--- +title: Валидация. Основное +description: 'Валидация. Документация по Bitrix Framework: принципы работы, архитектура и примеры использования.' +--- + +Валидация данных — это проверка входной информации на соответствие ожидаемым правилам. Например, числовой идентификатор должен быть положительным, а email соответствовать формату адреса. + +В Bitrix Framework валидацию можно выполнять вручную, но такой подход быстро приводит к дублированию кода и усложняет поддержку. Гораздо удобнее использовать встроенную систему валидации на основе php-атрибутов: она позволяет описать правила прямо в классе и централизованно их проверить. + +## Разбираемся на примере + +Представим, что нужно создать пользователя с полями email и телефон, при этом должны выполняться условия: +- Если указан email — он должен быть корректным. +- Если указан телефон — он должен быть корректным. +- Хотя бы одно из этих полей обязательно для заполнения. + +Это может выглядть следующим образом: +```php +use Bitrix\Main\Result; +use Bitrix\Main\Error; + +class UserService +{ + public function create(array $userData): Result + { + $createResult = new Result(); + + $emailOrPhoneExist = false; + if ( + array_key_exists('email', $userData) + && !is_null($userData['email']) + && is_string($userData['email']) + )) { + $emailOrPhoneExist = true; + if (!check_email($userData['email'])) { + $createResult->addError(new Error( + "E-mail заполнен некорректно" + )); + } + } + + if ( + array_key_exists('phone', $userData) + && !is_null($userData['phone']) + && is_string($userData['phone']) + )) { + $emailOrPhoneExist = true; + // Обратите внимание: функции check_phone() в ядре Bitrix нет — + // это лишь условный пример ручной проверки. + if (!check_phone($userData['phone'])) { + $createResult->addError(new Error( + "Телефон заполнен некорректно" + )); + } + } + + if (!$emailOrPhoneExist) { + $createResult->addError(new Error( + "Телефон или email обязателен к заполнению" + )); + } + + if (!$createResult->isSuccess()) { + return $createResult; + } + + // other logic ... + } +} +``` + +Этот код работает, но: +- содержит повторяющуюся логику, +- сложно расширять, +- смешивает проверку данных и бизнес-логику. + +Перепишем этот код с использованием валидаторов. + +{% note warning %} + +Система валидации Bitrix Framework работает с объектами, а не с массивами, нам придется создать и использовать специальный класс для передачи данных — его называют DTO (Data Transfer Object). + +{% endnote %} + +Создадим простую структуру для передачи данных в нашу функцию: + +```php +class CreateUser +{ + private ?string $email; + private ?string $phone; + + // getters & setters ... +} +``` + +Теперь добавим атрибуты с правилами валидации: `#[AtLeastOnePropertyNotEmpty]`, `#[Email]` и `#[Phone]`. +В системе существуют и другие правила валидации, но для демонстрации нашего примера достаточно и этих. +```php +use Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty; +use Bitrix\Main\Validation\Rule\Email; +use Bitrix\Main\Validation\Rule\Phone; + +#[AtLeastOnePropertyNotEmpty(['email', 'phone'])] +class CreateUser +{ + #[Email] + private ?string $email; + + #[Phone] + private ?string $phone; + + // getters & setters... +} +``` + +Теперь вся наша проверка сведется к передачи значения сервису валидации: +```php +use Bitrix\Main\Result; +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(CreateUser $userData): Result + { + $result = $this->validation->validate($userData); + if (!$result->isSuccess()) { + return $result; + } + + // other logic ... + } + +} +``` + +{% cut "Примечание к переходу на валидацию с использованием сервиса" %} + +Если вы не хотите или не можете изменять сигнатуру метода, можно создавать DTO внутри: +```php +public function create(array $userData): Result +{ + $createUser = new CreateUser(); + $createUser->setEmail($userData['email']); + $createUser->setPhone($userData['phone']); + + // ... +} +``` +{% endcut %} + + +## Как это работает? + +В системе валидации используются два ключевых понятия: + +- **Валидатор** — это объект реализующий интерфейс `\Bitrix\Main\Validation\Validator\ValidatorInterface`, который проверяет конкретное значение. Он не зависит от имени свойства или класса. Например, `EmailValidator` проверяет, соответствует ли значение формату email. +- **Правило** — это php-аттрибут, применяющийся к свойству или классу и определяющий: *какие валидаторы использовать*, *в каком контексте* и *с какими настройками*. + +Таким образом: +- валидатор — это механизм проверки, +- правило — это инструкция, где и как его использовать. + +{% note warning %} + +- Валидация работает через рефлексию: модификаторы доступа игнорируются. + +- Если свойство помечено как `nullable` и не было явно установлено, оно пропускается при валидации. + +- Если вы присвоили `null` явно — свойство считается инициализированным, и валидация к нему **применяется**. + +{% endnote %} + +## Рекурсивная валидация + +Используйя атрибут `#[Validatable]` можно так же добиться проверки вложенных объектов. + +```php +use Bitrix\Main\Validation\Rule\Recursive\Validatable; +use Bitrix\Main\Validation\Rule\NotEmpty; +use Bitrix\Main\Validation\Rule\PositiveNumber; +use Bitrix\Main\DI\ServiceLocator; +use Bitrix\Main\Validation\ValidationService; + +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; +} + +$payment = new Payment(); +$payment->status = ''; +$payment->systemCode = ''; + +$order = new Order(); +$order->id = -1; +$order->payment = $payment; + +$buyer = new Buyer(); +$buyer->id = 0; +$buyer->order = $order; + +$result = ServiceLocator::getInstance() + ->get('main.validation.service') + ->validate($buyer); + +// id: Значение поля должно быть не меньше, чем 1 +// order.id: Значение поля должно быть не меньше, чем 1 +// order.payment.status: Значение поля не может быть пустым +// order.payment.systemCode: Custom message error +foreach ($result->getErrors() as $error) { + echo $error->getCode() . ': ' . $error->getMessage(). PHP_EOL; +} +``` + +## Валидаторы без атрибутов + +Валидаторы можно применять без атрибутов для разовой проверки данных, когда нет необходимости описывать правила в объекте. Это подходит для старого кода с массивами и нетипизированными переменными. + +```php +use Bitrix\Main\Validation\Validator\EmailValidator; + +$email = 'bitrix@bitrix.ru'; + +$validator = new EmailValidator(); +$result = $validator->validate($email); +if (!$result->isSuccess()) { + // ... +} +``` diff --git a/pages/framework/validation/own.md b/pages/framework/validation/own.md new file mode 100644 index 0000000..11eda19 --- /dev/null +++ b/pages/framework/validation/own.md @@ -0,0 +1,256 @@ +--- +title: Валидация. Собственные правила и валидаторы +description: 'Валидация. Документация по Bitrix Framework: собственные правила валидации' +--- + +Иногда встроенных валидаторов и правил недостаточно для реализации специфической бизнес-логики. В таких случаях Bitrix Framework позволяет расширять возможности системы создавая собственные валидаторы и правила. + +## Валидатор + +Валидатор выполняет простую задачу - проверяет значение. Он не определяет, относится ли значение к свойству или классу, и не зависит от атрибутов. + +Создать валидатор очень просто: создайте класс реализующий интерфейс `\Bitrix\Main\Validation\Validator\ValidatorInterface` с публичный методом `validate(mixed $value): ValidationResult`. +Вы можете добавить в конструктор класса необходимые параметры, если ваш валидатор это подразумевает. +В методе `validate` создать объект `ValidationResult`, который будет хранить результаты проверки. +Выполнить необходимые проверки и добавить в `ValidationResult` ошибки. + +Пример валидатора для определения является ли значение UUID версии 4: + +```php +use Bitrix\Main\Localization\Loc; +use Bitrix\Main\Validation\ValidationError; +use Bitrix\Main\Validation\ValidationResult; +use Bitrix\Main\Validation\Validator\ValidatorInterface; + +class UUIDv4Validator implements ValidatorInterface +{ + public function validate(mixed $value): ValidationResult + { + $result = new ValidationResult(); + + if ( + !is_string($uuid) + || preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', $uuid) !== 1 + ) { + $result->addError(new ValidationError( + message: Loc::getMessage('FUSION_VALIDATION_VALIDATOR_UUIDV4_NOT_VALID'), + failedValidator: $this + )); + return $result; + } + + return $result; + } +} +``` + +> В разработке валидаторов старайтесь придерживаться правила fail fast - не ждите пока будут выполнены все проверки, если хотя бы один из критериев не соответствует добавляйте ошибку и возвращайте не успешный результат валидации. + + +## Правила + +В отличии от валидаторов правила не так универсальны и просты, поскольку зависят от применения: к свойствам и к классу. В Bitrix Framework для этого требуется реализация разных интерфейсов. + +### Правила для свойства + +Правило для свойства реализует интерфейс `Bitrix\Main\Validation\Rule\PropertyValidationAttributeInterface` с публичным методом `validateProperty(mixed $propertyValue): ValidationResult;`. + +Пример простого правила для свойства объекта: +```php +use Attribute; +use Bitrix\Main\Validation\Rule\PropertyValidationAttributeInterface; +use Bitrix\Main\Validation\ValidationError; +use Bitrix\Main\Validation\ValidationResult; + +#[Attribute(Attribute::TARGET_PROPERTY)] +class UUIDv4 implements PropertyValidationAttributeInterface +{ + public function validateProperty(mixed $propertyValue): ValidationResult + { + $result = new ValidationResult(); + + if ( + !is_string($propertyValue) + || preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', $propertyValue) !== 1 + ) { + $result->addError(new ValidationError('Значение не должно быть равно 1')); + return $result; + } + + return $result; + } +} +``` + +Что происходит в этом коде? +Мы описывает класс `UUIDv4`, реализующий интерфейрс `PropertyValidationAttributeInterface` (валидация свойства), который проверяет что указанное значение явялется UUID версии 4. Мы указали что данный класс может являться php-атрибутом чтобы была возможность использовать его в качестве атрибута на проверяемом объекте. + +И хотя этого кода вполне достаточно для расширения системы использовать его все равно не очень удобно. +Что не так с этим правилом? +1. Отсутствие валидаторов. Без валидаторов мы переносим проблему дублирования кода контроллеров/сервисов в правила. +2. Отсутствие возможности изменить текст ошибки. + +Для удобства разработчиков был создан абстрактный класс `Bitrix\Main\Validation\Rule\AbstractPropertyValidationAttribute` позволяющий легко избавиться от этих недостатков. + +```php +use Attribute; +use Bitrix\Main\Localization\LocalizableMessageInterface; +use UUIDv4Validator; + +#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)] +class UUIDv4 extends AbstractPropertyValidationAttribute +{ + public function __construct( + protected string|LocalizableMessageInterface|null $errorMessage = null + ) { + } + + protected function getValidators(): array + { + return [ + new UUIDv4Validator(), + ]; + } +} + +``` + +> Наследование `AbstractPropertyValidationAttribute` позволяет вам вернуть одно сообщение об ошибке errorMessage, вместо стандартных ответов валидатора + +### Правила для класса + +Подобно атрибуту, правило для класса так же имеет свой интерфейс `Bitrix\Main\Validation\Rule\ClassValidationAttributeInterface` с похожим методом `public function validateObject(object $object): ValidationResult;` и так же имеет абстрактный класс для упрощения создания своих правил - `Bitrix\Main\Validation\Rule\AbstractClassValidationAttribute`. + +Рассмотрим пример создания правила для класса реализующего логику интервала дат + +```php +use Attribute; +use Bitrix\Main\Validation\Rule\AbstractClassValidationAttribute; +use Bitrix\Main\Localization\LocalizableMessage; +use Bitrix\Main\Localization\LocalizableMessageInterface; +use Bitrix\Main\Validation\ValidationError; +use Bitrix\Main\Validation\ValidationResult; +use Bitrix\Main\Type\DateTime; +use ReflectionClass; +use ReflectionProperty; +use Bitrix\Main\Localization\Loc; + +Loc::loadMessages(__FILE__); + +#[Attribute(Attribute::TARGET_CLASS)] +class DateInterval extends AbstractClassValidationAttribute +{ + public function __construct( + private readonly string $startDateProperty, + private readonly string $endDateProperty, + private readonly bool $allowSameDate = false, + private readonly bool $allowNullDate = false, + protected string|LocalizableMessageInterface|null $errorMessage = null + ) {} + + public function validateObject(object $object): ValidationResult + { + $result = new ValidationResult(); + + $properties = $this->getProperties($object); + + if (empty($properties)) { + $result->addError(new ValidationError( + new LocalizableMessage( + "FUSION_INTRANET_VALIDATION_RULE_DATE_INTERVAL_EMPTY_PROPERTIES", + ), + )); + + return $this->replaceWithCustomError($result); + } + + $values = $this->getValues($object, ...$properties); + + $leftValue = $values[$this->startDateProperty] ?? null; + $rightValue = $values[$this->endDateProperty] ?? null; + + if ($leftValue === null) { + if ($this->allowNullDate) { + $leftValue = DateTime::createFromTimestamp(0); + } else { + $result->addError(new ValidationError( + new LocalizableMessage( + "FUSION_INTRANET_VALIDATION_RULE_DATE_INTERVAL_LEFT_BORDER_REQUIRED", + ), + )); + } + } + + if ($rightValue === null) { + if ($this->allowNullDate) { + $rightValue = DateTime::createFromTimestamp( + strtotime("9999-12-31 23:59:59"), + ); + } else { + $result->addError(new ValidationError( + new LocalizableMessage( + "FUSION_INTRANET_VALIDATION_RULE_DATE_INTERVAL_RIGHT_BORDER_REQUIRED", + ), + )); + } + } + + if (!$result->isSuccess()) { + return $this->replaceWithCustomError($result); + } + + $cmp = $rightValue <=> $leftValue; + if ($cmp < 0) { + $result->addError(new ValidationError( + new LocalizableMessage( + "FUSION_INTRANET_VALIDATION_RULE_DATE_INTERVAL_DATE_INTERVAL_INVALID", + ), + )); + } elseif ($cmp === 0 && !$this->allowSameDate) { + $result->addError(new ValidationError( + new LocalizableMessage( + "FUSION_INTRANET_VALIDATION_RULE_DATE_INTERVAL_DATE_INTERVAL_SAME_NOT_ALLOWED", + ), + )); + } + + return $this->replaceWithCustomError($result); + } + + private function getProperties(object $object): array + { + $reflection = new ReflectionClass($object); + + return array_filter( + $reflection->getProperties(), + fn(ReflectionProperty $property): bool => in_array( + $property->getName(), + [$this->startDateProperty, $this->endDateProperty], + true, + ), + ); + } + + private function getValues( + object $object, + ReflectionProperty ...$properties, + ): array { + $values = []; + foreach ($properties as $property) { + if ($property->isInitialized($object)) { + $values[$property->getName()] = $property->getValue($object); + } else { + $values[$property->getName()] = null; + } + } + + return $values; + } +} + + +``` + +> Наследование `AbstractClassValidationAttribute` позволяет вам вернуть одно сообщение об ошибке errorMessage, вместо стандартных ответов валидатора + + From 7c43fa8823428fca2a60606baa2db2ba99df7b54 Mon Sep 17 00:00:00 2001 From: Andrei Nikolaev Date: Tue, 27 Jan 2026 22:26:45 +0300 Subject: [PATCH 2/3] Small changes and new article with exist rules --- pages/framework/validation.md | 122 ----- pages/framework/validation/controller.md | 139 ++++-- pages/framework/validation/exist.md | 588 +++++++++++++++++++++++ pages/framework/validation/main.md | 44 ++ pages/framework/validation/own.md | 10 +- toc.yaml | 10 +- 6 files changed, 732 insertions(+), 181 deletions(-) delete mode 100644 pages/framework/validation.md create mode 100644 pages/framework/validation/exist.md diff --git a/pages/framework/validation.md b/pages/framework/validation.md deleted file mode 100644 index 0f5e327..0000000 --- a/pages/framework/validation.md +++ /dev/null @@ -1,122 +0,0 @@ - -### Сообщение об ошибке после валидации - -Можно указать свой текст ошибки, который будет возвращен после валидации. - -```php -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!' -``` - -Стандартный текст ошибки валидатора: - -```php -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`. Каждая ошибка содержит свойство `failedValidator`. - -```php -$errors = $service->validate($dto)->getErrors(); -foreach ($errors as $error) -{ - $failedValidator = $error->getFailedValidator(); - // ... -} -``` - -### Доступные атрибуты и валидаторы - -Bitrix Framework предоставляет готовые атрибуты и валидаторы для самых частых сценариев проверки данных. - -Свойства: - -- `ElementsType` -- проверка типа элементов массива, - -- `Email` -- валидация email, - -- `InArray` -- значение входит в массив допустимых значений, - -- `Length` -- проверка длины строки, - -- `Max` -- максимальное значение, - -- `Min` -- минимальное значение, - -- `NotEmpty` -- не пустое значение, - -- `Phone` -- валидация телефона, - -- `PhoneOrEmail` -- телефон или email, - -- `PositiveNumber` -- положительное число, - -- `Range` -- значение в диапазоне, - -- `RegExp` -- регулярное выражение, - -- `Url` -- валидный URL, - -- `Json` -- валидный JSON. - -Класс: - -`AtLeastOnePropertyNotEmpty` -- хотя бы одно свойство не пусто. - -Валидаторы: - -- `EmailValidator` -- валидация email, - -- `InArrayValidator` -- проверка вхождения в массив, - -- `LengthValidator` -- проверка длины строки, - -- `MaxValidator` -- максимальное значение, - -- `MinValidator` -- минимальное значение, - -- `NotEmptyValidator` -- не пустое значение, - -- `PhoneValidator` -- валидация телефона, - -- `RegExpValidator` -- проверка по регулярному выражению, - -- `UrlValidator` -- валидация URL, - -- `JsonValidator` -- валидация JSON. - diff --git a/pages/framework/validation/controller.md b/pages/framework/validation/controller.md index 6a3545c..ddcf470 100644 --- a/pages/framework/validation/controller.md +++ b/pages/framework/validation/controller.md @@ -1,75 +1,112 @@ --- -title: Валидация. Контроллеры +title: Валидация в контроллерах description: 'Валидация. Документация по Bitrix Framework: использование в контроллерах' --- -В контроллерах валидация используется для проверки корректности данных, поступающих из HTTP-запроса. Это позволяет убедиться, что входные параметры соответствуют ожидаемым форматам и бизнес-правилам до выполнения основной логики действия. +Валидация входящих данных — обязательный этап не только для слоя бизнес-функций, но и обработки HTTP-запроса. Она гарантирует, что запрос с несоответствующими ожидаемым форматам и правилам параметры будет остановлен с ошибкой раньше. -Для валидации в контроллере создаётся DTO-класс с атрибутами правил: +Передача валидации на уровень контроллера (с использованием PHP-атрибутов и встроенного механизма) позволяет избавиться от ручных проверок внутри методов. Это делает код чище, снижает риск ошибок и гарантирует унифицированный формат ответов с ошибками для клиентской части. -```php -use Bitrix\Main\Validation\Rule\NotEmpty; -use Bitrix\Main\Validation\Rule\PhoneOrEmail; +## Валидация простых типов данных -final class CreateUserDto +Для проверки скалярных значений (чисел, строк) достаточно добавить атрибут валидации к аргументу метода действия. +Рассмотрим пример, где идентификатор пользователя должен быть положительным числом. + +**Ручная проверка (устаревший подход):** + +```php +class AwardController extends Controller { - public function __construct( - #[PhoneOrEmail] - public ?string $login = null, - - #[NotEmpty] - public ?string $password = null, - - #[NotEmpty] - public ?string $passwordRepeat = null, - ) {} + public function getByUserIdAction(int $userId): array + { + $awards = []; + + if ($userId <= 0) { + throw new Exception("Неправильно указан идентификатор пользователя"); + } + + // Логика получения данных... + + return $awards; + } } ``` -Затем в контроллере данные из запроса передаются в DTO, и выполняется валидация: +**Валидация через атрибуты:** + +Используйте атрибут `#[PositiveNumber]`. Контроллер автоматически проверит значение до выполнения метода. ```php -use Bitrix\Main\DI\ServiceLocator; -use Bitrix\Main\Validation\ValidationService; +use Bitrix\Main\Validation\Rule\PositiveNumber; -class UserController extends Controller +class AwardController extends Controller { - private ValidationService $validation; - - protected function init() + public function getByUserIdAction( + #[PositiveNumber] + int $userId + ): array { - parent::init(); - $this->validation = ServiceLocator::getInstance()->get('main.validation.service'); + $awards = []; + + // Логика получения данных... + + return $awards; } - - public function createAction(): Result +} +``` + +Если валидация не пройдет, метод не выполнится, а клиент получит ошибку в стандартном формате. + +Пример AJAX-запроса с невалидными данными: + +```js +BX.ajax.runAction( + 'fusion:badge.Award.getByUserId', { - $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; + data: { + userId: -1 } - - // Логика создания пользователя... } +); +``` + +Ответ сервера: + +```json +{ + "status":"error", + "data":null, + "errors":[ + { + "message":"Invalid value to match parameter: [userId] Значение поля должно быть не меньше, чем 1.", + "code":100, + "customData":null + } + ] } ``` ## Автоматическая валидация через AutoWire -Чтобы избежать дублирования кода преобразования запроса в DTO, рекомендуется добавить статический фабричный метод в DTO: +Чтобы избежать ручного заполнения DTO (Data Transfer Object) из запроса в каждом действии, используется механизм `AutoWire` с параметром `ValidationParameter`. ```php use Bitrix\Main\HttpRequest; +use Bitrix\Main\Validation\Rule\NotEmpty; +use Bitrix\Main\Validation\Rule\PhoneOrEmail; + 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(HttpRequest $request): self { @@ -82,10 +119,11 @@ final class CreateUserDto } ``` -Bitrix Framework предоставляет механизм автоматической инъекции и валидации параметров с помощью `ValidationParameter`: +Затем подключите фабричный метод в контроллере через `getAutoWiredParameters`: ```php use Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter; + class UserController extends Controller { public function getAutoWiredParameters() @@ -97,20 +135,15 @@ class UserController extends Controller ), ]; } - - public function createAction(CreateUserDto $dto): Result + + public function createAction(CreateUserDto $dto): ?array { - // Метод вызовется только если $dto прошёл валидацию. - // В противном случае контроллер вернёт ошибку в формате JSON: - // { - // "data": null, - // "errors": [...], - // "status": "error" - // } + // Метод выполнится только если $dto прошел валидацию. + // Иначе контроллер автоматически вернет ошибку. // Логика создания пользователя... } } ``` -Если объект DTO не проходит валидацию, метод действия **не выполняется**, а контроллер автоматически возвращает клиенту список ошибок. Это позволяет полностью отделить логику валидации от бизнес-кода и упростить обработку некорректных запросов. \ No newline at end of file +Если данные в запросе не соответствуют правилам валидации, указанным в DTO, действие `createAction` не будет вызвано, а клиенту сразу вернется JSON с перечнем ошибок. \ No newline at end of file diff --git a/pages/framework/validation/exist.md b/pages/framework/validation/exist.md new file mode 100644 index 0000000..39b5291 --- /dev/null +++ b/pages/framework/validation/exist.md @@ -0,0 +1,588 @@ +--- +title: Валидация. Существующие правила +description: 'Валидация. Документация по Bitrix Framework: собственные правила валидации' +--- + +Bitrix Framework предоставляет готовые атрибуты и валидаторы для самых частых сценариев проверки данных. + +## Для классов + +### `AtLeastOnePropertyNotEmpty` + +Проверяет заполненность хотя бы одного аттрибута. + +Полный класс правила: `Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\AtLeastOneNotEmptyValidator` + +Сигнатура метода: +```php +__construct( + private readonly array $propertyNames, + private readonly bool $allowZero = false, + private readonly bool $allowEmptyString = false, + protected string|LocalizableMessageInterface|null $errorMessage = null, +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty; +use Bitrix\Main\Validation\Rule\PositiveNumber; + +#[AtLeastOnePropertyNotEmpty( + propertyNames: ['id', 'uuid'], + errorMessage: "Нельзя однозначно идентифицировать пользователя" +)] +class UpdateUserName +{ + public function __construct( + #[PositiveNumber] + public ?int $id = null, + public ?string $uuid = null, + public string $fullName + ) {} +} +``` + +### `OnlyOneOfPropertyRequired` + +Проверяет, что ровно одно из указанных свойств объекта содержит непустое значение (пустыми считаются: `null`, `0`, `''`, `false` и пустые массивы). + +Полный класс правила: `Bitrix\Main\Validation\Rule\OnlyOneOfPropertyRequired` +Используемые валидаторы: Отсутствуют (валидация реализована внутри правила) + +Сигнатура метода: +```php +__construct( + private readonly array $propertyNames, + protected string|LocalizableMessageInterface|null $errorMessage = null, +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\OnlyOneOfPropertyRequired; +use Bitrix\Main\Validation\Rule\PositiveNumber; +use Bitrix\Main\Validation\Rule\Email; + +#[OnlyOneOfPropertyRequired( + propertyNames: ['userId', 'email'], + errorMessage: 'Укажите ТОЛЬКО один идентификатор: либо userId, либо email' +)] +class UserLookupRequest +{ + #[PositiveNumber] + public ?int $userId = null; + + #[Email] + public ?string $email = null; + + public ?string $newEmail = null; +} +``` + +Поле `newEmail` не участвует в проверке, так как не указано в `propertyNames`. + + +## Для свойств и параметров + +### `ElementsType` + +Проверяет, что все элементы в массиве (или итерируемом объекте) соответствуют указанному скалярному типу данных или являются экземплярами определенного класса. + +Полный класс правила: `Bitrix\Main\Validation\Rule\ElementsType` +Используемые валидаторы: +- Встроенная логика проверки (без подключения отдельных классов валидаторов) + +Сигнатура метода: +```php +__construct( + private readonly ?Type $typeEnum = null, + private readonly ?string $className = null, + string|LocalizableMessageInterface|null $errorMessage = null +) +``` + +Доступные типы: +- `Type::Integer` - проверка осуществляется php функцией `is_int` +- `Type::String` - проверка осуществляется php функцией `is_string` +- `Type::Float` - проверка осуществляется php функцией `is_float` +- `Type::Numeric` - проверка осуществляется php функцией `is_numeric` + +Пример использования (проверка скалярного типа): +```php +use Bitrix\Main\Validation\Rule\ElementsType; +use Bitrix\Main\Validation\Rule\Enum\Type; + +class UpdateUserRolesDto +{ + public function __construct( + public int $userId, + + #[ElementsType( + typeEnum: Type::Integer, + errorMessage: "ID ролей должны быть целыми числами" + )] + public array $roleIds + ) {} +} +``` + +Пример использования (проверка класса): +```php +use Bitrix\Main\Validation\Rule\ElementsType; +use Bitrix\Tasks\Item\Task; + +class ProcessTasksDto +{ + public function __construct( + #[ElementsType( + className: Task::class, + errorMessage: "Все элементы должны быть объектами Task" + )] + public array $tasks + ) {} +} +``` + +### `Email` + +Проверяет, что значение является корректным адресом электронной почты. + +Полный класс правила: `Bitrix\Main\Validation\Rule\Email` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\EmailValidator` + +Сигнатура метода: +```php +__construct( + private readonly bool $strict = false, + private readonly bool $domainCheck = false, + protected string|LocalizableMessageInterface|null $errorMessage = null +) +``` + +Параметры: +- `strict` (bool) - проверяет что значение содержит только почтовый адрес. Например "Ivan Ivanov " при нестрогой проверке успешно ее пройдет, а при строгой проверке даже пробел перед адресом электронной почты будет ошибкой. +- `domainCheck` (bool) - дополнительно проверят MX и A запись для почтового домена при помощи php функции `checkdnsrr` + + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\Email; + +class UserProfileDto +{ + public function __construct( + public string $name, + + #[Email( + errorMessage: "Пожалуйста, укажите корректный email" + )] + public string $email + ) {} +} +``` + +### `InArray` + +Проверяет, что значение присутствует в списке допустимых значений. + +Полный класс правила: `Bitrix\Main\Validation\Rule\InArray` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\InArrayValidator` + +Сигнатура метода: +```php +__construct( + private readonly array $validValues, + private readonly bool $strict = false, + protected string|LocalizableMessageInterface|null $errorMessage = null, +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\InArray; + +class TaskDto +{ + public function __construct( + public string $title, + + #[InArray( + validValues: ['low', 'medium', 'high'], + errorMessage: "Укажите корректный приоритет задачи" + )] + public string $priority + ) {} +} +``` + +### `Json` + +Проверяет, что значение является строкой, содержащей корректный JSON. + +Полный класс правила: `Bitrix\Main\Validation\Rule\Json` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\JsonValidator` + +Сигнатура метода: +```php +__construct() +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\Json; + +class WidgetSettingsDto +{ + public function __construct( + public string $widgetCode, + + #[Json] + public string $settings + ) {} +} +``` + +### `Length` + +Проверяет, что длина строки находится в заданном диапазоне. Можно задать только минимальную, только максимальную границу или обе сразу. + +Полный класс правила: `Bitrix\Main\Validation\Rule\Length` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\LengthValidator` + +Сигнатура метода: +```php +__construct( + private readonly ?int $min = null, + private readonly ?int $max = null, + protected string|LocalizableMessageInterface|null $errorMessage = null, +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\Length; + +class ChangePasswordDto +{ + public function __construct( + #[Length( + min: 8, + max: 50, + errorMessage: "Пароль должен содержать от 8 до 50 символов" + )] + public string $newPassword + ) {} +} +``` + +### `Max` + +Проверяет, что числовое значение меньше или равно указанному максимуму. + +Полный класс правила: `Bitrix\Main\Validation\Rule\Max` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\MaxValidator` + +Сигнатура метода: +```php +__construct( + private readonly int $max, + protected string|LocalizableMessageInterface|null $errorMessage = null +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\Max; + +class DiscountDto +{ + public function __construct( + public string $name, + + #[Max( + max: 100, + errorMessage: "Скидка не может превышать 100%" + )] + public int $discountPercent + ) {} +} +``` + +### `Min` + +Проверяет, что числовое значение больше или равно указанному минимуму. + +Полный класс правила: `Bitrix\Main\Validation\Rule\Min` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\MinValidator` + +Сигнатура метода: +```php +__construct( + private readonly int $min, + protected string|LocalizableMessageInterface|null $errorMessage = null +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\Min; + +class OrderDto +{ + public function __construct( + public string $itemName, + + #[Min( + min: 10, + errorMessage: "Минимальная сумма заказа должна быть не меньше 10" + )] + public int $totalAmount + ) {} +} +``` + +### `NotEmpty` + +Проверяет, что значение не является пустым. Позволяет настраивать допустимость нулевого значения и строк, содержащих только пробелы. + +Полный класс правила: `Bitrix\Main\Validation\Rule\NotEmpty` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\NotEmptyValidator` + +Сигнатура метода: +```php +__construct( + private readonly bool $allowZero = false, + private readonly bool $allowSpaces = false, + protected string|LocalizableMessageInterface|null $errorMessage = null +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\NotEmpty; + +class TaskDto +{ + public function __construct( + #[NotEmpty( + allowSpaces: false, + errorMessage: "Название задачи не может быть пустым") + ] + public string $title + ) {} +} +``` + +### `Phone` + +Проверяет, что значение является корректным номером телефона. + +Полный класс правила: `Bitrix\Main\Validation\Rule\Phone` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\PhoneValidator` + +Сигнатура метода: +```php +__construct( + protected string|LocalizableMessageInterface|null $errorMessage = null +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\Phone; + +class UserContactDto +{ + public function __construct( + public string $name, + + #[Phone(errorMessage: "Пожалуйста, укажите корректный номер телефона")] + public string $workPhone + ) {} +} +``` + +### `PhoneOrEmail` + +Проверяет, что значение является корректным номером телефона или адресом электронной почты. Валидация проходит успешно, если значение соответствует хотя бы одному из двух форматов. + +Полный класс правила: `Bitrix\Main\Validation\Rule\PhoneOrEmail` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\PhoneValidator` +- `Bitrix\Main\Validation\Validator\EmailValidator` + +Сигнатура метода: +```php +__construct( + private readonly bool $strict = false, + private readonly bool $domainCheck = false, + string|LocalizableMessageInterface|null $errorMessage = null +) +``` + +Параметры: +- `strict` (bool) - проверяет что значение содержит только почтовый адрес. Например "Ivan Ivanov " при нестрогой проверке успешно ее пройдет, а при строгой проверке даже пробел перед адресом электронной почты будет ошибкой. +- `domainCheck` (bool) - дополнительно проверят MX и A запись для почтового домена при помощи php функции `checkdnsrr` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\PhoneOrEmail; + +class UserContactDto +{ + public function __construct( + public string $name, + + #[PhoneOrEmail( + errorMessage: "Укажите корректный номер телефона или email" + )] + public string $contact, + ) {} +} +``` + +### `PositiveNumber` + +Проверяет, что числовое значение является строго положительным (больше нуля). + +Полный класс правила: `Bitrix\Main\Validation\Rule\PositiveNumber` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\MinValidator` + +Сигнатура метода: +```php +__construct( + protected string|LocalizableMessageInterface|null $errorMessage = null +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\PositiveNumber; + +class AwardController extends Controller +{ + public function getByUserIdAction( + #[PositiveNumber(errorMessage: "Идентификатор пользователя должен быть положительным числом")] + int $userId + ): array { + // ... + } +} +``` + +### `Range` + +Проверяет, что числовое значение находится в заданном диапазоне (от `min` до `max` включительно). + +Полный класс правила: `Bitrix\Main\Validation\Rule\Range` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\MinValidator` +- `Bitrix\Main\Validation\Validator\MaxValidator` + +Сигнатура метода: +```php +__construct( + private readonly int $min, + private readonly int $max, + protected string|LocalizableMessageInterface|null $errorMessage = null +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\Range; + +class OrderDto +{ + public function __construct( + public string $itemName, + + #[Range( + min: 1, + max: 100, + errorMessage: "Количество товара должно быть от 1 до 100" + )] + public int $quantity + ) {} +} +``` + +### `RegExp` + +Проверяет, что значение строки соответствует указанному регулярному выражению. + +Полный класс правила: `Bitrix\Main\Validation\Rule\RegExp` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\RegExpValidator` + +Сигнатура метода: +```php +__construct( + private readonly string $pattern, + private readonly int $flags = 0, + private readonly int $offset = 0, + protected string|LocalizableMessageInterface|null $errorMessage = null +) +``` + +Параметры `flags` и `offset` передаются в php-функицю `preg_match`. + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\RegExp; + +class ProductDto +{ + public function __construct( + public string $name, + + #[RegExp( + pattern: '/^[A-Z]{2}\d{4}$/', + errorMessage: "Артикул должен быть в формате: две заглавные буквы и четыре цифры" + )] + public string $sku + ) {} +} +``` + +### `Url` + +Проверяет, что значение является корректным URL-адресом. + +Полный класс правила: `Bitrix\Main\Validation\Rule\Url` +Используемые валидаторы: +- `Bitrix\Main\Validation\Validator\UrlValidator` + +Сигнатура метода: +```php +__construct( + protected string|LocalizableMessageInterface|null $errorMessage = null, +) +``` + +Пример использования: +```php +use Bitrix\Main\Validation\Rule\Url; + +class SiteFeedbackDto +{ + public function __construct( + public string $userName, + + #[Url(errorMessage: "Пожалуйста, укажите корректный адрес сайта")] + public string $userSite, + ) {} +} +``` diff --git a/pages/framework/validation/main.md b/pages/framework/validation/main.md index 4cf4909..bfdb07e 100644 --- a/pages/framework/validation/main.md +++ b/pages/framework/validation/main.md @@ -254,3 +254,47 @@ if (!$result->isSuccess()) { // ... } ``` + +## Сообщение об ошибке после валидации + +Можно указать свой текст ошибки, который будет возвращен после валидации. + +```php +use Bitrix\Main\Validation\Rule\PositiveNumber; + +class User +{ + public function __construct( + #[PositiveNumber(errorMessage: 'Invalid ID!')] + public readonly int $id, + #[PositiveNumber] + public readonly int $departmentId + ) + {} +} + +$user = new User( + id: -150, + departmentId: -1 +); + +/** @var \Bitrix\Main\Validation\ValidationService $service */ +$result = $service->validate($user); +foreach ($result->getErrors() as $error) { + echo $error->getMessage(); +} +// output: 'Invalid ID!' +// output: 'Значение поля меньше допустимого' +``` + +## Получить сработавший валидатор + +Результат валидации хранит ошибки `\Bitrix\Main\Validation\ValidationError`. Каждая ошибка содержит свойство `failedValidator`. + +```php +$errors = $service->validate($dto)->getErrors(); +foreach ($errors as $error) { + $failedValidator = $error->getFailedValidator(); + // ... +} +``` \ No newline at end of file diff --git a/pages/framework/validation/own.md b/pages/framework/validation/own.md index 11eda19..50a0fa8 100644 --- a/pages/framework/validation/own.md +++ b/pages/framework/validation/own.md @@ -29,8 +29,8 @@ class UUIDv4Validator implements ValidatorInterface $result = new ValidationResult(); if ( - !is_string($uuid) - || preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', $uuid) !== 1 + !is_string($value) + || preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', $value) !== 1 ) { $result->addError(new ValidationError( message: Loc::getMessage('FUSION_VALIDATION_VALIDATOR_UUIDV4_NOT_VALID'), @@ -62,7 +62,7 @@ use Bitrix\Main\Validation\Rule\PropertyValidationAttributeInterface; use Bitrix\Main\Validation\ValidationError; use Bitrix\Main\Validation\ValidationResult; -#[Attribute(Attribute::TARGET_PROPERTY)] +#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)] class UUIDv4 implements PropertyValidationAttributeInterface { public function validateProperty(mixed $propertyValue): ValidationResult @@ -73,7 +73,7 @@ class UUIDv4 implements PropertyValidationAttributeInterface !is_string($propertyValue) || preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', $propertyValue) !== 1 ) { - $result->addError(new ValidationError('Значение не должно быть равно 1')); + $result->addError(new ValidationError('Значение должно быть корректным UUID v4')); return $result; } @@ -251,6 +251,6 @@ class DateInterval extends AbstractClassValidationAttribute ``` -> Наследование `AbstractClassValidationAttribute` позволяет вам вернуть одно сообщение об ошибке errorMessage, вместо стандартных ответов валидатора +> Наследование `AbstractClassValidationAttribute` позволяет вам вернуть одно сообщение об ошибке `errorMessage`, вместо стандартных ответов валидатора diff --git a/toc.yaml b/toc.yaml index bf67e27..5e55520 100644 --- a/toc.yaml +++ b/toc.yaml @@ -109,7 +109,15 @@ items: - name: Cookie-файлы href: /pages/framework/cookies.md - name: Валидация - href: /pages/framework/validation.md + items: + - name: Основное + href: /pages/framework/validation/main.md + - name: Существующие правила + href: /pages/framework/validation/exist.md + - name: Контроллеры + href: /pages/framework/validation/controller.md + - name: Свои правила + href: /pages/framework/validation/own.md - name: Результат работы и ошибки href: /pages/framework/results-and-errors.md - name: Компоненты From dddce01662bff844b115ac1a7558fb23e5ce2f77 Mon Sep 17 00:00:00 2001 From: Andrei Nikolaev Date: Mon, 23 Feb 2026 23:09:09 +0300 Subject: [PATCH 3/3] Rework validation article with new rules --- pages/framework/validation.md | 890 ++++++++++------------- pages/framework/validation/controller.md | 149 ---- pages/framework/validation/exist.md | 588 --------------- pages/framework/validation/main.md | 300 -------- pages/framework/validation/own.md | 256 ------- toc.yaml | 10 +- 6 files changed, 386 insertions(+), 1807 deletions(-) delete mode 100644 pages/framework/validation/controller.md delete mode 100644 pages/framework/validation/exist.md delete mode 100644 pages/framework/validation/main.md delete mode 100644 pages/framework/validation/own.md diff --git a/pages/framework/validation.md b/pages/framework/validation.md index 6c3c075..61c3a2b 100644 --- a/pages/framework/validation.md +++ b/pages/framework/validation.md @@ -1,664 +1,458 @@ --- -title: Валидация -description: 'Валидация. Документация по Bitrix Framework: принципы работы, архитектура и примеры использования.' +title: Валидация данных в Bitrix Framework +description: 'Документация по системе валидации Bitrix Framework: принципы работы, архитектура, встроенные правила и создание собственных валидаторов.' --- -Валидация данных -- это проверка информации на соответствие заданным правилам. Например, числовой идентификатор должен быть положительным, а email -- соответствовать формату адреса. +Валидация данных — это проверка входной информации на соответствие ожидаемым правилам. Например, числовой идентификатор должен быть положительным, а email соответствовать формату адреса. -## Как использовать валидацию +В Bitrix Framework валидацию можно выполнять вручную, но такой подход быстро приводит к дублированию кода и усложняет поддержку. Гораздо удобнее использовать встроенную систему валидации на основе PHP-атрибутов: она позволяет описать правила прямо в классе и централизованно их проверить. -В Bitrix Framework валидацию можно реализовать разными способами. Самый простой способ -- ручные проверки в конструкторе или методах. Такой подход приводит к дублированию кода и усложняет его поддержку. +Этот подход гарантирует, что запрос с несоответствующими параметрами будет остановлен с ошибкой раньше выполнения бизнес-логики, делает код чище и обеспечивает унифицированный формат ответов для клиентской части. -```php -public function __construct(int $userId) -{ - if ($userId <= 0) - { - throw new \Exception(); - } - $this->userId = $userId; -} -``` - -Чтобы сократить код, используйте систему валидации на основе атрибутов. Она позволяет: +## Основные понятия -- задавать правила в классах, +В системе валидации используются два ключевых понятия: -- автоматически проверять данные при создании объектов, +- **Валидатор** — это объект, реализующий интерфейс `\Bitrix\Main\Validation\Validator\ValidatorInterface`, который проверяет конкретное значение. Он не зависит от имени свойства или класса. Например, `EmailValidator` проверяет, соответствует ли значение формату email. +- **Правило** — это PHP-атрибут, применяющийся к свойству или классу и определяющий: *какие валидаторы использовать*, *в каком контексте* и *с какими настройками*. -- централизованно обрабатывать ошибки. +Таким образом: +- валидатор — это механизм проверки, +- правило — это инструкция, где и как его использовать. -### Как создать правила в классе +{% note warning %} -1. Создайте класс. Например, `User` со свойствами `id`, `email` и `phone`. +Важные особенности работы системы: - ```php - final class User - { - private ?int $id; - private ?string $email; - private ?string $phone; - - // getters & setters ... - } - ``` - -2. Добавьте атрибуты валидации: `#[PositiveNumber]`, `#[Email]` и `#[Phone]`. - - ```php - 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... - } - ``` - -3. Проверьте валидацию. Объект можно проверить через `\Bitrix\Main\Validation\ValidationService` по ключу `main.validation.service`. - - `ValidationService` предоставляет метод `validate()`, который возвращает `ValidationResult`. Результат валидации содержит ошибки всех сработавших валидаторов. - - ```php - use Bitrix\Main\DI\ServiceLocator; - use Bitrix\Main\Result; - 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 ... - } - } - ``` - -{% note warning "" %} - -- Валидация работает через рефлексию, модификаторы доступа не учитываются. - -- Если свойство помечено как `nullable` и не заполнено, валидация пропускает его. +- Валидация работает через рефлексию: модификаторы доступа (private, protected) игнорируются. +- Если свойство помечено как `nullable` и не было явно установлено, оно пропускается при валидации. +- Если вы присвоили `null` явно — свойство считается инициализированным, и валидация к нему **применяется**. {% endnote %} -### Вложенные объекты +## Быстрый старт: от ручной проверки к атрибутам -Используйте атрибут `#[Validatable]` для вложенных объектов. +Рассмотрим задачу создания пользователя с полями email и телефон. Условия: +- Если указан email — он должен быть корректным. +- Если указан телефон — он должен быть корректным. +- Хотя бы одно из этих полей обязательно для заполнения. + +### Ручная проверка (устаревший подход) ```php -use Bitrix\Main\Validation\Rule\Recursive\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) +use Bitrix\Main\Result; +use Bitrix\Main\Error; + +class UserService { - echo $error->getCode() . ': ' . $error->getMessage(). PHP_EOL; -} -``` + public function create(array $userData): Result + { + $createResult = new Result(); + + $emailOrPhoneExist = false; + if ( + array_key_exists('email', $userData) + && !is_null($userData['email']) + && is_string($userData['email']) + ) + { + $emailOrPhoneExist = true; + if (!check_email($userData['email'])) + { + $createResult->addError(new Error( + 'E-mail заполнен некорректно' + )); + } + } + + if ( + array_key_exists('phone', $userData) + && !is_null($userData['phone']) + && is_string($userData['phone']) + ) + { + $emailOrPhoneExist = true; + // Обратите внимание: функции check_phone() в ядре Bitrix нет — + // это лишь условный пример ручной проверки. + if (!check_phone($userData['phone'])) + { + $createResult->addError(new Error( + 'Телефон заполнен некорректно' + )); + } + } -### Валидация массивов + if (!$emailOrPhoneExist) + { + $createResult->addError(new Error( + 'Телефон или email обязателен к заполнению' + )); + } -Атрибут `#[ElementsType]` проверяет, что все элементы массива соответствуют одному из типов перечисления `\Bitrix\Main\Validation\Rule\Enum\Type`. + if (!$createResult->isSuccess()) + { + return $createResult; + } -- `Type::Integer` -- целое число. + // other logic ... + } +} +``` -- `Type::String` -- строка. +Этот код работает, но содержит повторяющуюся логику, сложно расширяется и смешивает проверку данных с бизнес-логикой. -- `Type::Float` -- число с плавающей точкой. +### Валидация через атрибуты -- `Type::Numeric` -- число, целое или с плавающей точкой. +Система валидации Bitrix Framework работает с объектами. Нам потребуется создать класс для передачи данных (DTO — Data Transfer Object). ```php -use Bitrix\Main\Validation\Rule\ElementsType; -use Bitrix\Main\Validation\Rule\Enum\Type; -use Bitrix\Main\Validation\Rule\NotEmpty; +use Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty; +use Bitrix\Main\Validation\Rule\Email; +use Bitrix\Main\Validation\Rule\Phone; -final class UserSettingsDto +#[AtLeastOnePropertyNotEmpty(['email', 'phone'])] +class CreateUser { - public function __construct( - // Свойство должно быть непустым массивом - #[NotEmpty] - // Все элементы массива должны быть целыми числами - #[ElementsType(Type::Integer)] // Используем элемент перечисления - public array $favoriteIds = [] - ) - { - } -} + #[Email] + private ?string $email; -// Пример использования -$settings = new UserSettingsDto([1, 2, 3]); -$result = $validationService->validate($settings); // Успешно + #[Phone] + private ?string $phone; -$invalidSettings = new UserSettingsDto([1, 'текст', 3]); -$result = $validationService->validate($invalidSettings); // Ошибка -// Сообщение: "favoriteIds: Не все элементы массива соответствуют ожидаемому типу" + // getters & setters... +} ``` -{% note info "" %} - -Атрибут `#[ElementsType]` не проверяет, заполнен ли массив. Для этого требуется дополнительно использовать атрибут `#[NotEmpty]`. - -{% endnote %} - -Если элементы требуют сложных правил, создайте для элемента отдельный DTO. Система проверит каждый элемент массива как отдельный объект. +Теперь проверка сведется к передаче объекта сервису валидации: ```php -use Bitrix\Main\Validation\Rule\RegExp; -use Bitrix\Main\Validation\Rule\Length; +use Bitrix\Main\Result; +use Bitrix\Main\DI\ServiceLocator; +use Bitrix\Main\Validation\ValidationService; -// DTO для одного элемента (тега) -final class TagDto +class UserService { - public function __construct( - #[RegExp('/^[a-z0-9\-_]+$/')] - #[Length(max: 20)] - public string $name - ) + private ValidationService $validation; + + public function __construct() { + $this->validation = ServiceLocator::getInstance()->get('main.validation.service'); } -} -final class ArticleDto -{ - // Каждый элемент массива будет провалидирован как объект TagDto - public function __construct( - #[ElementsType(TagDto::class)] - public array $tags = [] - ) + public function create(CreateUser $userData): Result { - } -} + $result = $this->validation->validate($userData); -// Использование -$article = new ArticleDto(); -$article->tags = [ - new TagDto('Tag1'), - new TagDto('Tag2'), - new TagDto('Invalid Tag!'), // Вызовет ошибку: не соответствует RegExp -]; + if (!$result->isSuccess()) + { + return $result; + } -$result = $validationService->validate($article); -if (!$result->isSuccess()) { - foreach ($result->getErrors() as $error) { - // Путь к ошибке будет включать индекс элемента, например: "tags.2.name" - echo $error->getCode() . ': ' . $error->getMessage() . PHP_EOL; + // other logic ... } } ``` -Если проверка типа `Type` или валидация через вложенный DTO не решают задачу, измените структуру объекта. Массив как нетипизированное хранилище параметров сложно валидировать. - -1. **Преобразовать массив в свойства объекта.** Если массив содержит пары `ключ=>значение`, создайте для каждого параметра отдельное типизированное свойство класса с конкретными атрибутами валидации. - -2. **Вынести массив в отдельный объект.** Создайте новый класс для данных из массива и добавьте его как типизированное свойство в исходный DTO. - -### Валидация в контроллерах +{% cut "Примечание к переходу на валидацию с использованием сервиса" %} -В контроллерах валидация помогает убедиться в корректности данных из запроса. +Если вы не хотите или не можете изменять сигнатуру метода, можно создавать DTO внутри метода: ```php -use Bitrix\Main\Validation\Rule\NotEmpty; -use Bitrix\Main\Validation\Rule\PhoneOrEmail; - -final class CreateUserDto +public function create(array $userData): Result { - public function __construct( - #[PhoneOrEmail] - public ?string $login, - - #[NotEmpty] - public ?string $password, - - #[NotEmpty] - public ?string $passwordRepeat, - ) - { - } + $createUser = new CreateUser(); + $createUser->setEmail($userData['email']); + $createUser->setPhone($userData['phone']); + + // ... вызов валидации } ``` -В коде класс будет выглядеть следующим образом: +{% endcut %} + +## Использование в контроллерах + +Валидация входящих данных обязательна не только для слоя бизнес-функций, но и для обработки HTTP-запроса. Передача валидации на уровень контроллера позволяет избавиться от ручных проверок внутри методов действий. + +### Валидация простых типов данных + +Для проверки скалярных значений (чисел, строк) достаточно добавить атрибут валидации к аргументу метода действия. + +**Пример:** идентификатор пользователя должен быть положительным числом. ```php -use Bitrix\Main\DI\ServiceLocator; -use Bitrix\Main\Engine\Controller; -use Bitrix\Main\Result; -use Bitrix\Main\Validation\ValidationService; +use Bitrix\Main\Validation\Rule\PositiveNumber; +use Bitrix\Main\Controller; -class UserController extends Controller +class AwardController extends Controller { - private ValidationService $validation; - - protected function init() - { - parent::init(); - - $this->validation = ServiceLocator::getInstance()->get('main.validation.service'); - } - - public function createAction(): Result + public function getByUserIdAction( + #[PositiveNumber] + int $userId + ): array { - $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 ... + return []; } } ``` -Создайте фабричный метод в DTO, чтобы избежать повторения кода. +Если валидация не пройдет, метод не выполнится, а клиент получит ошибку в стандартном формате JSON. + +### Автоматическая валидация DTO через AutoWire + +Чтобы избежать ручного заполнения DTO из запроса, используется механизм `AutoWire` с параметром `ValidationParameter`. ```php use Bitrix\Main\HttpRequest; use Bitrix\Main\Validation\Rule\NotEmpty; use Bitrix\Main\Validation\Rule\PhoneOrEmail; +use Bitrix\Main\Controller; +use Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter; 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 + ) {} + + public static function createFromRequest(HttpRequest $request): self { return new static( - login: (string)$request->get('login'), - password: (string)$request->get('password'), - passwordRepeat: (string)$request->get('passwordRepeat'), + login: (string) $request->get('login'), + password: (string) $request->get('password'), + passwordRepeat: (string) $request->get('passwordRepeat'), ); } } -``` - -Класс `Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter` устранит повторяющуюся логику валидации. - -```php -use Bitrix\Main\Engine\Controller; -use Bitrix\Main\Result; class UserController extends Controller { public function getAutoWiredParameters() { return [ - new \Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter( + new ValidationParameter( CreateUserDto::class, fn() => CreateUserDto::createFromRequest($this->getRequest()), ), ]; } - - public function createAction(CreateUserDto $dto): Result + + public function createAction(CreateUserDto $dto): ?array { - // create logic ... + // Метод выполнится только если $dto прошел валидацию. + // Иначе контроллер автоматически вернет ошибку. + + // Логика создания пользователя... } } ``` -Если объект `CreateUserDto` невалиден, метод `createAction` не будет выполнен. Контроллер вернет ошибку. +## Рекурсивная валидация -```php -{ - data: null, - errors: - [ - { - code: "name", - customData: null, - message: "Значение поля не должно быть пустым", - }, - ], - status: "error" -} -``` - -### Валидаторы без атрибутов - -Валидаторы можно применять без атрибутов для разовой проверки данных, когда нет необходимости описывать правила в объекте. Это подходит для старого кода с массивами и нетипизированными переменными. - -```php -use Bitrix\Main\Validation\Validator\EmailValidator; -$email = 'bitrix@bitrix.ru'; -$validator = new EmailValidator(); -$result = $validator->validate($email); -if (!$result->isSuccess()) -{ - // ... -} -``` - -### Сообщение об ошибке после валидации - -Можно указать свой текст ошибки, который будет возвращен после валидации. +Используя атрибут `#[Validatable]`, можно добиться проверки вложенных объектов. ```php +use Bitrix\Main\Validation\Rule\Recursive\Validatable; +use Bitrix\Main\Validation\Rule\NotEmpty; 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) +class Buyer { - echo $error->getMessage(); + #[PositiveNumber] + public ?int $id; + #[Validatable] + public ?Order $order; } -// output: 'Invalid ID!' -``` - -Стандартный текст ошибки валидатора: -```php -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) +class Order { - echo $error->getMessage(); + #[PositiveNumber] + public int $id; + #[Validatable] + public ?Payment $payment; } -// output: 'Значение поля меньше допустимого' -``` - -### Получить сработавший валидатор -Результат валидации хранит ошибки `\Bitrix\Main\Validation\ValidationError`. Каждая ошибка содержит свойство `failedValidator`. - -```php -$errors = $service->validate($dto)->getErrors(); -foreach ($errors as $error) +class Payment { - $failedValidator = $error->getFailedValidator(); - // ... + #[NotEmpty] + public string $status; + #[NotEmpty(errorMessage: 'Custom message error')] + public string $systemCode; } ``` -### Доступные атрибуты и валидаторы - -Bitrix Framework предоставляет готовые атрибуты и валидаторы для самых частых сценариев проверки данных. - -Свойства: - -- `ElementsType` -- проверка типа элементов массива, - -- `Email` -- валидация email, - -- `InArray` -- значение входит в массив допустимых значений, - -- `Length` -- проверка длины строки, - -- `Max` -- максимальное значение, - -- `Min` -- минимальное значение, - -- `NotEmpty` -- не пустое значение, +При валидации объекта `Buyer` система автоматически проверит вложенные `Order` и `Payment`. Ошибки будут содержать путь к полю (например, `order.payment.status`). -- `Phone` -- валидация телефона, +## Справочник встроенных правил -- `PhoneOrEmail` -- телефон или email, +Bitrix Framework предоставляет готовые атрибуты для частых сценариев. -- `PositiveNumber` -- положительное число, +### Правила для классов -- `Range` -- значение в диапазоне, +Эти атрибуты навешиваются на класс DTO. -- `RegExp` -- регулярное выражение, +#### `AtLeastOnePropertyNotEmpty` +Проверяет заполненность хотя бы одного из указанных свойств. +- **Класс:** `Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty` +- **Параметры:** `propertyNames` (array), `allowZero` (bool), `allowEmptyString` (bool), `errorMessage`. -- `Url` -- валидный URL, - -- `Json` -- валидный JSON. - -Класс: - -`AtLeastOnePropertyNotEmpty` -- хотя бы одно свойство не пусто. +```php +#[AtLeastOnePropertyNotEmpty( + propertyNames: ['id', 'uuid'], + errorMessage: "Нельзя однозначно идентифицировать пользователя" +)] +class UpdateUserName { ... } +``` -Валидаторы: +#### `OnlyOneOfPropertyRequired` +Проверяет, что ровно одно из указанных свойств содержит непустое значение. +- **Класс:** `Bitrix\Main\Validation\Rule\OnlyOneOfPropertyRequired` +- **Параметры:** `propertyNames` (array), `errorMessage`. -- `EmailValidator` -- валидация email, +```php +#[OnlyOneOfPropertyRequired( + propertyNames: ['userId', 'email'], + errorMessage: 'Укажите ТОЛЬКО один идентификатор' +)] +class UserLookupRequest { ... } +``` -- `InArrayValidator` -- проверка вхождения в массив, +### Правила для свойств и параметров -- `LengthValidator` -- проверка длины строки, +Эти атрибуты навешиваются на свойства классов или аргументы методов. -- `MaxValidator` -- максимальное значение, +#### `ElementsType` +Проверяет, что все элементы в массиве соответствуют типу или классу. +- **Класс:** `Bitrix\Main\Validation\Rule\ElementsType` +- **Параметры:** `typeEnum` (Integer, String, Float, Numeric), `className`, `errorMessage`. -- `MinValidator` -- минимальное значение, +```php +#[ElementsType(typeEnum: Type::Integer, errorMessage: "ID должны быть числами")] +public array $roleIds; +``` -- `NotEmptyValidator` -- не пустое значение, +#### `Email` +Проверяет формат электронной почты. +- **Класс:** `Bitrix\Main\Validation\Rule\Email` +- **Параметры:** `strict` (bool), `domainCheck` (bool), `errorMessage`. +- **Опции:** `domainCheck` проверяет MX и A записи домена. -- `PhoneValidator` -- валидация телефона, +#### `InArray` +Проверяет вхождение значения в список допустимых. +- **Класс:** `Bitrix\Main\Validation\Rule\InArray` +- **Параметры:** `validValues` (array), `strict` (bool), `errorMessage`. -- `RegExpValidator` -- проверка по регулярному выражению, +#### `Json` +Проверяет, что строка является корректным JSON. +- **Класс:** `Bitrix\Main\Validation\Rule\Json` -- `UrlValidator` -- валидация URL, +#### `Length` +Проверяет длину строки (мин/макс). +- **Класс:** `Bitrix\Main\Validation\Rule\Length` +- **Параметры:** `min` (float), `max` (int), `errorMessage`. -- `JsonValidator` -- валидация JSON. +#### `Max` / `Min` +Проверяют числовые границы. +- **Классы:** `Bitrix\Main\Validation\Rule\Max`, `Bitrix\Main\Validation\Rule\Min` +- **Параметры:** `max`/`min` (int), `errorMessage`. -## Как создать собственные валидаторы +#### `NotEmpty` +Проверяет, что значение не пустое. +- **Класс:** `Bitrix\Main\Validation\Rule\NotEmpty` +- **Параметры:** `allowZero` (bool), `allowSpaces` (bool), `errorMessage`. -Каждый валидатор реализует интерфейс `\Bitrix\Main\Validation\Validator\ValidatorInterface` с методом `public function validate(mixed $value): ValidationResult`. +#### `Phone` / `PhoneOrEmail` +Проверяют формат телефона или комбинацию телефон/email. +- **Классы:** `Bitrix\Main\Validation\Rule\Phone`, `Bitrix\Main\Validation\Rule\PhoneOrEmail` +- **Параметры:** `strict` (bool), `domainCheck` (bool), `errorMessage`. -Валидатор выполняет простую задачу -- проверяет значение. Он не определяет, относится ли значение к свойству или классу, и не зависит от атрибутов. +#### `PositiveNumber` +Проверяет, что число строго больше нуля. +- **Класс:** `Bitrix\Main\Validation\Rule\PositiveNumber` -### Пример валидатора Min +#### `Range` +Проверяет диапазон чисел (от min до max включительно). +- **Класс:** `Bitrix\Main\Validation\Rule\Range` +- **Параметры:** `min`, `max`, `errorMessage`. -1. Класс `Min` реализует интерфейс `ValidatorInterface`. +#### `RegExp` +Проверяет соответствие регулярному выражению. +- **Класс:** `Bitrix\Main\Validation\Rule\RegExp` +- **Параметры:** `pattern`, `flags`, `offset`, `errorMessage`. -2. Конструктор принимает минимальное значение. +#### `Url` +Проверяет корректность URL-адреса. +- **Класс:** `Bitrix\Main\Validation\Rule\Url` -3. Метод `validate` создает объект `ValidationResult`, который хранит результаты проверки. +## Создание собственных валидаторов и правил - - Сначала он проверяет, является ли значение числом. Если нет, добавляется ошибка. +Если встроенных инструментов недостаточно, вы можете расширить систему. - - Затем проверяет, меньше ли значение заданного минимума. Если да, добавляется соответствующая ошибка. +### Создание валидатора -4. В конце метод возвращает объект `ValidationResult` с результатами проверки. +Валидатор выполняет простую задачу — проверяет значение. Он не зависит от атрибутов. +Необходимо реализовать интерфейс `\Bitrix\Main\Validation\Validator\ValidatorInterface`. ```php -namespace Bitrix\Main\Validation\Validator; - use Bitrix\Main\Localization\Loc; use Bitrix\Main\Validation\ValidationError; use Bitrix\Main\Validation\ValidationResult; use Bitrix\Main\Validation\Validator\ValidatorInterface; -final class Min implements ValidatorInterface +class UUIDv4Validator implements ValidatorInterface { - public function __construct( - private readonly int $min - ) - { - } public function validate(mixed $value): ValidationResult { $result = new ValidationResult(); - if (!is_numeric($value)) + + if ( + !is_string($value) + || preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', $value) !== 1 + ) { - $result->addError( - new ValidationError( - Loc::getMessage('MAIN_VALIDATION_MIN_NOT_A_NUMBER'), - failedValidator: $this - ) - ); + $result->addError(new ValidationError( + message: Loc::getMessage('FUSION_VALIDATION_VALIDATOR_UUIDV4_NOT_VALID'), + failedValidator: $this + )); return $result; } - if ($value < $this->min) - { - $result->addError( - new ValidationError( - Loc::getMessage('MAIN_VALIDATION_MIN_LESS_THAN_MIN'), - failedValidator: $this - ) - ); - } + return $result; } } ``` -## Как создать атрибуты валидации +> В разработке валидаторов старайтесь придерживаться правила fail fast: если критерий не соответствует, сразу добавляйте ошибку и возвращайте результат. -Атрибуты валидации разделены на два типа: для свойств и для классов. +### Создание правил (Атрибутов) -### Атрибуты свойств +Правила зависят от контекста применения: к свойствам или к классу. -Атрибуты свойств реализуют интерфейс `\Bitrix\Main\Validation\Rule\PropertyValidationAttributeInterface`. Они используют метод `validateProperty(mixed $propertyValue): ValidationResult` для проверки значений свойств. +#### Правило для свойства -Пример простого атрибута для проверки значения свойства: - -```php -use Bitrix\Main\Validation\Rule\PropertyValidationAttributeInterface; -use Bitrix\Main\Validation\ValidationError; -use Bitrix\Main\Validation\ValidationResult; - -#[Attribute(Attribute::TARGET_PROPERTY)] -class NotOne implements PropertyValidationAttributeInterface -{ - public function validateProperty(mixed $propertyValue): ValidationResult - { - $result = new ValidationResult(); - if ($propertyValue === 1) - { - $result->addError(new ValidationError('Значение не должно быть равно 1')); - } - return $result; - } -} -``` - -Этот атрибут проверяет, что значение свойства не равно 1. Если условие нарушено, возвращается ошибка. - -Для сложных проверок используйте абстрактный класс `\Bitrix\Main\Validation\Rule\AbstractPropertyValidationAttribute`. Реализуйте метод `getValidators(): array`, чтобы вернуть список валидаторов. - -Пример атрибута `Range`, который проверяет, что значение находится в заданном диапазоне: +Реализует интерфейс `PropertyValidationAttributeInterface`. Для удобства рекомендуется наследоваться от `AbstractPropertyValidationAttribute`. ```php use Attribute; use Bitrix\Main\Validation\Rule\AbstractPropertyValidationAttribute; -use Bitrix\Main\Validation\Validator\Implementation\Max; -use Bitrix\Main\Validation\Validator\Implementation\Min; +use Bitrix\Main\Localization\LocalizableMessageInterface; +use UUIDv4Validator; -#[Attribute(Attribute::TARGET_PROPERTY)] -final class Range extends AbstractPropertyValidationAttribute +#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)] +class UUIDv4 extends AbstractPropertyValidationAttribute { public function __construct( - private readonly int $min, - private readonly int $max, - protected ?string $errorMessage = null + protected string|LocalizableMessageInterface|null $errorMessage = null ) { } @@ -666,44 +460,130 @@ final class Range extends AbstractPropertyValidationAttribute protected function getValidators(): array { return [ - new Min($this->min), - new Max($this->max), + new UUIDv4Validator(), ]; } } ``` -### Атрибуты класса +Наследование `AbstractPropertyValidationAttribute` позволяет вернуть кастомное сообщение об ошибке `errorMessage` вместо стандартных ответов валидатора. -Атрибуты класса реализуют интерфейс `\Bitrix\Main\Validation\Rule\ClassValidationAttributeInterface`. Они используют метод `validateObject(object $object): ValidationResult` для проверки объектов. +#### Правило для класса -Пример атрибута для проверки количества свойств: +Реализует интерфейс `ClassValidationAttributeInterface`. Рекомендуется наследоваться от `AbstractClassValidationAttribute`. +Пример правила для проверки интервала дат: ```php -use Bitrix\Main\Validation\ValidationResult; -use Bitrix\Main\Validation\ValidationError; +use Attribute; use Bitrix\Main\Validation\Rule\AbstractClassValidationAttribute; +use Bitrix\Main\Localization\LocalizableMessage; +use Bitrix\Main\Validation\ValidationError; +use Bitrix\Main\Validation\ValidationResult; +use Bitrix\Main\Type\DateTime; use ReflectionClass; +use ReflectionProperty; #[Attribute(Attribute::TARGET_CLASS)] -class NotOne extends AbstractClassValidationAttribute +class DateInterval extends AbstractClassValidationAttribute { + public function __construct( + private readonly string $startDateProperty, + private readonly string $endDateProperty, + protected string|LocalizableMessageInterface|null $errorMessage = null + ) + { + } + public function validateObject(object $object): ValidationResult { $result = new ValidationResult(); - $properties = (new ReflectionClass($object))->getProperties(); - - if (count($properties) > 2) + // Логика получения значений через рефлексию + $properties = $this->getProperties($object); + $values = $this->getValues($object, ...$properties); + + $leftValue = $values[$this->startDateProperty] ?? null; + $rightValue = $values[$this->endDateProperty] ?? null; + + // Пример проверки логики дат + if ( + $leftValue + && $rightValue + && $rightValue < $leftValue + ) { - $result->addError(new ValidationError('Класс содержит слишком много свойств')); + $result->addError(new ValidationError( + new LocalizableMessage('Дата окончания не может быть раньше даты начала') + )); } - return $result; + + return $this->replaceWithCustomError($result); } + + // ... вспомогательные методы getProperties, getValues } ``` -Этот атрибут проверяет, что в классе не больше двух свойств. Если условие нарушено, метод вернет ошибку. +## Дополнительные возможности -### Сообщение об ошибке для атрибута +### Кастомизация сообщений об ошибках -Если вы наследуетесь от `AbstractClassValidationAttribute` или `AbstractPropertyValidationAttribute`, можно задать собственное сообщение об ошибке через свойство `$errorMessage`. Это позволит вернуть одну ошибку с вашим текстом вместо стандартных ошибок валидаторов. \ No newline at end of file +Можно указать свой текст ошибки прямо в атрибуте. + +```php +use Bitrix\Main\Validation\Rule\PositiveNumber; + +class User +{ + public function __construct( + #[PositiveNumber(errorMessage: 'Invalid ID!')] + public readonly int $id, + ) + {} +} +``` + +Если требуется указать языкозависимый текст, сделать это можно указав вместо конкретного текста объект реализующий языкозависимый интерфейс `Bitrix\Main\Localization\LocalizableMessageInterface`. + +```php +use Bitrix\Main\Validation\Rule\PositiveNumber; +use Bitrix\Main\Localization\LocalizableMessage; + +class User +{ + public function __construct( + #[PositiveNumber( + errorMessage: new LocalizableMessage('FUSION_USER_ERROR_PARAMETER_ID') + )] + public readonly int $id, + ) + {} +} +``` + +### Получение сработавшего валидатора + +Результат валидации хранит ошибки `\Bitrix\Main\Validation\ValidationError`. Каждая ошибка содержит свойство `failedValidator`. + +```php +$errors = $service->validate($dto)->getErrors(); +foreach ($errors as $error) { + $failedValidator = $error->getFailedValidator(); + // ... +} +``` + +### Валидаторы без атрибутов + +Валидаторы можно применять без атрибутов для разовой проверки данных, когда нет необходимости описывать правила в объекте (например, для старого кода с массивами). + +```php +use Bitrix\Main\Validation\Validator\EmailValidator; + +$email = 'bitrix@bitrix.ru'; + +$validator = new EmailValidator(); +$result = $validator->validate($email); +if (!$result->isSuccess()) { + // Обработка ошибки +} +``` \ No newline at end of file diff --git a/pages/framework/validation/controller.md b/pages/framework/validation/controller.md deleted file mode 100644 index ddcf470..0000000 --- a/pages/framework/validation/controller.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: Валидация в контроллерах -description: 'Валидация. Документация по Bitrix Framework: использование в контроллерах' ---- - -Валидация входящих данных — обязательный этап не только для слоя бизнес-функций, но и обработки HTTP-запроса. Она гарантирует, что запрос с несоответствующими ожидаемым форматам и правилам параметры будет остановлен с ошибкой раньше. - -Передача валидации на уровень контроллера (с использованием PHP-атрибутов и встроенного механизма) позволяет избавиться от ручных проверок внутри методов. Это делает код чище, снижает риск ошибок и гарантирует унифицированный формат ответов с ошибками для клиентской части. - -## Валидация простых типов данных - -Для проверки скалярных значений (чисел, строк) достаточно добавить атрибут валидации к аргументу метода действия. -Рассмотрим пример, где идентификатор пользователя должен быть положительным числом. - -**Ручная проверка (устаревший подход):** - -```php -class AwardController extends Controller -{ - public function getByUserIdAction(int $userId): array - { - $awards = []; - - if ($userId <= 0) { - throw new Exception("Неправильно указан идентификатор пользователя"); - } - - // Логика получения данных... - - return $awards; - } -} -``` - -**Валидация через атрибуты:** - -Используйте атрибут `#[PositiveNumber]`. Контроллер автоматически проверит значение до выполнения метода. - -```php -use Bitrix\Main\Validation\Rule\PositiveNumber; - -class AwardController extends Controller -{ - public function getByUserIdAction( - #[PositiveNumber] - int $userId - ): array - { - $awards = []; - - // Логика получения данных... - - return $awards; - } -} -``` - -Если валидация не пройдет, метод не выполнится, а клиент получит ошибку в стандартном формате. - -Пример AJAX-запроса с невалидными данными: - -```js -BX.ajax.runAction( - 'fusion:badge.Award.getByUserId', - { - data: { - userId: -1 - } - } -); -``` - -Ответ сервера: - -```json -{ - "status":"error", - "data":null, - "errors":[ - { - "message":"Invalid value to match parameter: [userId] Значение поля должно быть не меньше, чем 1.", - "code":100, - "customData":null - } - ] -} -``` - -## Автоматическая валидация через AutoWire - -Чтобы избежать ручного заполнения DTO (Data Transfer Object) из запроса в каждом действии, используется механизм `AutoWire` с параметром `ValidationParameter`. - -```php -use Bitrix\Main\HttpRequest; -use Bitrix\Main\Validation\Rule\NotEmpty; -use Bitrix\Main\Validation\Rule\PhoneOrEmail; - -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(HttpRequest $request): self - { - return new static( - login: (string) $request->get('login'), - password: (string) $request->get('password'), - passwordRepeat: (string) $request->get('passwordRepeat'), - ); - } -} -``` - -Затем подключите фабричный метод в контроллере через `getAutoWiredParameters`: - -```php -use Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter; - -class UserController extends Controller -{ - public function getAutoWiredParameters() - { - return [ - new ValidationParameter( - CreateUserDto::class, - fn() => CreateUserDto::createFromRequest($this->getRequest()), - ), - ]; - } - - public function createAction(CreateUserDto $dto): ?array - { - // Метод выполнится только если $dto прошел валидацию. - // Иначе контроллер автоматически вернет ошибку. - - // Логика создания пользователя... - } -} -``` - -Если данные в запросе не соответствуют правилам валидации, указанным в DTO, действие `createAction` не будет вызвано, а клиенту сразу вернется JSON с перечнем ошибок. \ No newline at end of file diff --git a/pages/framework/validation/exist.md b/pages/framework/validation/exist.md deleted file mode 100644 index 39b5291..0000000 --- a/pages/framework/validation/exist.md +++ /dev/null @@ -1,588 +0,0 @@ ---- -title: Валидация. Существующие правила -description: 'Валидация. Документация по Bitrix Framework: собственные правила валидации' ---- - -Bitrix Framework предоставляет готовые атрибуты и валидаторы для самых частых сценариев проверки данных. - -## Для классов - -### `AtLeastOnePropertyNotEmpty` - -Проверяет заполненность хотя бы одного аттрибута. - -Полный класс правила: `Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\AtLeastOneNotEmptyValidator` - -Сигнатура метода: -```php -__construct( - private readonly array $propertyNames, - private readonly bool $allowZero = false, - private readonly bool $allowEmptyString = false, - protected string|LocalizableMessageInterface|null $errorMessage = null, -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty; -use Bitrix\Main\Validation\Rule\PositiveNumber; - -#[AtLeastOnePropertyNotEmpty( - propertyNames: ['id', 'uuid'], - errorMessage: "Нельзя однозначно идентифицировать пользователя" -)] -class UpdateUserName -{ - public function __construct( - #[PositiveNumber] - public ?int $id = null, - public ?string $uuid = null, - public string $fullName - ) {} -} -``` - -### `OnlyOneOfPropertyRequired` - -Проверяет, что ровно одно из указанных свойств объекта содержит непустое значение (пустыми считаются: `null`, `0`, `''`, `false` и пустые массивы). - -Полный класс правила: `Bitrix\Main\Validation\Rule\OnlyOneOfPropertyRequired` -Используемые валидаторы: Отсутствуют (валидация реализована внутри правила) - -Сигнатура метода: -```php -__construct( - private readonly array $propertyNames, - protected string|LocalizableMessageInterface|null $errorMessage = null, -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\OnlyOneOfPropertyRequired; -use Bitrix\Main\Validation\Rule\PositiveNumber; -use Bitrix\Main\Validation\Rule\Email; - -#[OnlyOneOfPropertyRequired( - propertyNames: ['userId', 'email'], - errorMessage: 'Укажите ТОЛЬКО один идентификатор: либо userId, либо email' -)] -class UserLookupRequest -{ - #[PositiveNumber] - public ?int $userId = null; - - #[Email] - public ?string $email = null; - - public ?string $newEmail = null; -} -``` - -Поле `newEmail` не участвует в проверке, так как не указано в `propertyNames`. - - -## Для свойств и параметров - -### `ElementsType` - -Проверяет, что все элементы в массиве (или итерируемом объекте) соответствуют указанному скалярному типу данных или являются экземплярами определенного класса. - -Полный класс правила: `Bitrix\Main\Validation\Rule\ElementsType` -Используемые валидаторы: -- Встроенная логика проверки (без подключения отдельных классов валидаторов) - -Сигнатура метода: -```php -__construct( - private readonly ?Type $typeEnum = null, - private readonly ?string $className = null, - string|LocalizableMessageInterface|null $errorMessage = null -) -``` - -Доступные типы: -- `Type::Integer` - проверка осуществляется php функцией `is_int` -- `Type::String` - проверка осуществляется php функцией `is_string` -- `Type::Float` - проверка осуществляется php функцией `is_float` -- `Type::Numeric` - проверка осуществляется php функцией `is_numeric` - -Пример использования (проверка скалярного типа): -```php -use Bitrix\Main\Validation\Rule\ElementsType; -use Bitrix\Main\Validation\Rule\Enum\Type; - -class UpdateUserRolesDto -{ - public function __construct( - public int $userId, - - #[ElementsType( - typeEnum: Type::Integer, - errorMessage: "ID ролей должны быть целыми числами" - )] - public array $roleIds - ) {} -} -``` - -Пример использования (проверка класса): -```php -use Bitrix\Main\Validation\Rule\ElementsType; -use Bitrix\Tasks\Item\Task; - -class ProcessTasksDto -{ - public function __construct( - #[ElementsType( - className: Task::class, - errorMessage: "Все элементы должны быть объектами Task" - )] - public array $tasks - ) {} -} -``` - -### `Email` - -Проверяет, что значение является корректным адресом электронной почты. - -Полный класс правила: `Bitrix\Main\Validation\Rule\Email` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\EmailValidator` - -Сигнатура метода: -```php -__construct( - private readonly bool $strict = false, - private readonly bool $domainCheck = false, - protected string|LocalizableMessageInterface|null $errorMessage = null -) -``` - -Параметры: -- `strict` (bool) - проверяет что значение содержит только почтовый адрес. Например "Ivan Ivanov " при нестрогой проверке успешно ее пройдет, а при строгой проверке даже пробел перед адресом электронной почты будет ошибкой. -- `domainCheck` (bool) - дополнительно проверят MX и A запись для почтового домена при помощи php функции `checkdnsrr` - - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\Email; - -class UserProfileDto -{ - public function __construct( - public string $name, - - #[Email( - errorMessage: "Пожалуйста, укажите корректный email" - )] - public string $email - ) {} -} -``` - -### `InArray` - -Проверяет, что значение присутствует в списке допустимых значений. - -Полный класс правила: `Bitrix\Main\Validation\Rule\InArray` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\InArrayValidator` - -Сигнатура метода: -```php -__construct( - private readonly array $validValues, - private readonly bool $strict = false, - protected string|LocalizableMessageInterface|null $errorMessage = null, -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\InArray; - -class TaskDto -{ - public function __construct( - public string $title, - - #[InArray( - validValues: ['low', 'medium', 'high'], - errorMessage: "Укажите корректный приоритет задачи" - )] - public string $priority - ) {} -} -``` - -### `Json` - -Проверяет, что значение является строкой, содержащей корректный JSON. - -Полный класс правила: `Bitrix\Main\Validation\Rule\Json` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\JsonValidator` - -Сигнатура метода: -```php -__construct() -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\Json; - -class WidgetSettingsDto -{ - public function __construct( - public string $widgetCode, - - #[Json] - public string $settings - ) {} -} -``` - -### `Length` - -Проверяет, что длина строки находится в заданном диапазоне. Можно задать только минимальную, только максимальную границу или обе сразу. - -Полный класс правила: `Bitrix\Main\Validation\Rule\Length` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\LengthValidator` - -Сигнатура метода: -```php -__construct( - private readonly ?int $min = null, - private readonly ?int $max = null, - protected string|LocalizableMessageInterface|null $errorMessage = null, -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\Length; - -class ChangePasswordDto -{ - public function __construct( - #[Length( - min: 8, - max: 50, - errorMessage: "Пароль должен содержать от 8 до 50 символов" - )] - public string $newPassword - ) {} -} -``` - -### `Max` - -Проверяет, что числовое значение меньше или равно указанному максимуму. - -Полный класс правила: `Bitrix\Main\Validation\Rule\Max` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\MaxValidator` - -Сигнатура метода: -```php -__construct( - private readonly int $max, - protected string|LocalizableMessageInterface|null $errorMessage = null -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\Max; - -class DiscountDto -{ - public function __construct( - public string $name, - - #[Max( - max: 100, - errorMessage: "Скидка не может превышать 100%" - )] - public int $discountPercent - ) {} -} -``` - -### `Min` - -Проверяет, что числовое значение больше или равно указанному минимуму. - -Полный класс правила: `Bitrix\Main\Validation\Rule\Min` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\MinValidator` - -Сигнатура метода: -```php -__construct( - private readonly int $min, - protected string|LocalizableMessageInterface|null $errorMessage = null -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\Min; - -class OrderDto -{ - public function __construct( - public string $itemName, - - #[Min( - min: 10, - errorMessage: "Минимальная сумма заказа должна быть не меньше 10" - )] - public int $totalAmount - ) {} -} -``` - -### `NotEmpty` - -Проверяет, что значение не является пустым. Позволяет настраивать допустимость нулевого значения и строк, содержащих только пробелы. - -Полный класс правила: `Bitrix\Main\Validation\Rule\NotEmpty` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\NotEmptyValidator` - -Сигнатура метода: -```php -__construct( - private readonly bool $allowZero = false, - private readonly bool $allowSpaces = false, - protected string|LocalizableMessageInterface|null $errorMessage = null -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\NotEmpty; - -class TaskDto -{ - public function __construct( - #[NotEmpty( - allowSpaces: false, - errorMessage: "Название задачи не может быть пустым") - ] - public string $title - ) {} -} -``` - -### `Phone` - -Проверяет, что значение является корректным номером телефона. - -Полный класс правила: `Bitrix\Main\Validation\Rule\Phone` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\PhoneValidator` - -Сигнатура метода: -```php -__construct( - protected string|LocalizableMessageInterface|null $errorMessage = null -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\Phone; - -class UserContactDto -{ - public function __construct( - public string $name, - - #[Phone(errorMessage: "Пожалуйста, укажите корректный номер телефона")] - public string $workPhone - ) {} -} -``` - -### `PhoneOrEmail` - -Проверяет, что значение является корректным номером телефона или адресом электронной почты. Валидация проходит успешно, если значение соответствует хотя бы одному из двух форматов. - -Полный класс правила: `Bitrix\Main\Validation\Rule\PhoneOrEmail` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\PhoneValidator` -- `Bitrix\Main\Validation\Validator\EmailValidator` - -Сигнатура метода: -```php -__construct( - private readonly bool $strict = false, - private readonly bool $domainCheck = false, - string|LocalizableMessageInterface|null $errorMessage = null -) -``` - -Параметры: -- `strict` (bool) - проверяет что значение содержит только почтовый адрес. Например "Ivan Ivanov " при нестрогой проверке успешно ее пройдет, а при строгой проверке даже пробел перед адресом электронной почты будет ошибкой. -- `domainCheck` (bool) - дополнительно проверят MX и A запись для почтового домена при помощи php функции `checkdnsrr` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\PhoneOrEmail; - -class UserContactDto -{ - public function __construct( - public string $name, - - #[PhoneOrEmail( - errorMessage: "Укажите корректный номер телефона или email" - )] - public string $contact, - ) {} -} -``` - -### `PositiveNumber` - -Проверяет, что числовое значение является строго положительным (больше нуля). - -Полный класс правила: `Bitrix\Main\Validation\Rule\PositiveNumber` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\MinValidator` - -Сигнатура метода: -```php -__construct( - protected string|LocalizableMessageInterface|null $errorMessage = null -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\PositiveNumber; - -class AwardController extends Controller -{ - public function getByUserIdAction( - #[PositiveNumber(errorMessage: "Идентификатор пользователя должен быть положительным числом")] - int $userId - ): array { - // ... - } -} -``` - -### `Range` - -Проверяет, что числовое значение находится в заданном диапазоне (от `min` до `max` включительно). - -Полный класс правила: `Bitrix\Main\Validation\Rule\Range` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\MinValidator` -- `Bitrix\Main\Validation\Validator\MaxValidator` - -Сигнатура метода: -```php -__construct( - private readonly int $min, - private readonly int $max, - protected string|LocalizableMessageInterface|null $errorMessage = null -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\Range; - -class OrderDto -{ - public function __construct( - public string $itemName, - - #[Range( - min: 1, - max: 100, - errorMessage: "Количество товара должно быть от 1 до 100" - )] - public int $quantity - ) {} -} -``` - -### `RegExp` - -Проверяет, что значение строки соответствует указанному регулярному выражению. - -Полный класс правила: `Bitrix\Main\Validation\Rule\RegExp` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\RegExpValidator` - -Сигнатура метода: -```php -__construct( - private readonly string $pattern, - private readonly int $flags = 0, - private readonly int $offset = 0, - protected string|LocalizableMessageInterface|null $errorMessage = null -) -``` - -Параметры `flags` и `offset` передаются в php-функицю `preg_match`. - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\RegExp; - -class ProductDto -{ - public function __construct( - public string $name, - - #[RegExp( - pattern: '/^[A-Z]{2}\d{4}$/', - errorMessage: "Артикул должен быть в формате: две заглавные буквы и четыре цифры" - )] - public string $sku - ) {} -} -``` - -### `Url` - -Проверяет, что значение является корректным URL-адресом. - -Полный класс правила: `Bitrix\Main\Validation\Rule\Url` -Используемые валидаторы: -- `Bitrix\Main\Validation\Validator\UrlValidator` - -Сигнатура метода: -```php -__construct( - protected string|LocalizableMessageInterface|null $errorMessage = null, -) -``` - -Пример использования: -```php -use Bitrix\Main\Validation\Rule\Url; - -class SiteFeedbackDto -{ - public function __construct( - public string $userName, - - #[Url(errorMessage: "Пожалуйста, укажите корректный адрес сайта")] - public string $userSite, - ) {} -} -``` diff --git a/pages/framework/validation/main.md b/pages/framework/validation/main.md deleted file mode 100644 index bfdb07e..0000000 --- a/pages/framework/validation/main.md +++ /dev/null @@ -1,300 +0,0 @@ ---- -title: Валидация. Основное -description: 'Валидация. Документация по Bitrix Framework: принципы работы, архитектура и примеры использования.' ---- - -Валидация данных — это проверка входной информации на соответствие ожидаемым правилам. Например, числовой идентификатор должен быть положительным, а email соответствовать формату адреса. - -В Bitrix Framework валидацию можно выполнять вручную, но такой подход быстро приводит к дублированию кода и усложняет поддержку. Гораздо удобнее использовать встроенную систему валидации на основе php-атрибутов: она позволяет описать правила прямо в классе и централизованно их проверить. - -## Разбираемся на примере - -Представим, что нужно создать пользователя с полями email и телефон, при этом должны выполняться условия: -- Если указан email — он должен быть корректным. -- Если указан телефон — он должен быть корректным. -- Хотя бы одно из этих полей обязательно для заполнения. - -Это может выглядть следующим образом: -```php -use Bitrix\Main\Result; -use Bitrix\Main\Error; - -class UserService -{ - public function create(array $userData): Result - { - $createResult = new Result(); - - $emailOrPhoneExist = false; - if ( - array_key_exists('email', $userData) - && !is_null($userData['email']) - && is_string($userData['email']) - )) { - $emailOrPhoneExist = true; - if (!check_email($userData['email'])) { - $createResult->addError(new Error( - "E-mail заполнен некорректно" - )); - } - } - - if ( - array_key_exists('phone', $userData) - && !is_null($userData['phone']) - && is_string($userData['phone']) - )) { - $emailOrPhoneExist = true; - // Обратите внимание: функции check_phone() в ядре Bitrix нет — - // это лишь условный пример ручной проверки. - if (!check_phone($userData['phone'])) { - $createResult->addError(new Error( - "Телефон заполнен некорректно" - )); - } - } - - if (!$emailOrPhoneExist) { - $createResult->addError(new Error( - "Телефон или email обязателен к заполнению" - )); - } - - if (!$createResult->isSuccess()) { - return $createResult; - } - - // other logic ... - } -} -``` - -Этот код работает, но: -- содержит повторяющуюся логику, -- сложно расширять, -- смешивает проверку данных и бизнес-логику. - -Перепишем этот код с использованием валидаторов. - -{% note warning %} - -Система валидации Bitrix Framework работает с объектами, а не с массивами, нам придется создать и использовать специальный класс для передачи данных — его называют DTO (Data Transfer Object). - -{% endnote %} - -Создадим простую структуру для передачи данных в нашу функцию: - -```php -class CreateUser -{ - private ?string $email; - private ?string $phone; - - // getters & setters ... -} -``` - -Теперь добавим атрибуты с правилами валидации: `#[AtLeastOnePropertyNotEmpty]`, `#[Email]` и `#[Phone]`. -В системе существуют и другие правила валидации, но для демонстрации нашего примера достаточно и этих. -```php -use Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty; -use Bitrix\Main\Validation\Rule\Email; -use Bitrix\Main\Validation\Rule\Phone; - -#[AtLeastOnePropertyNotEmpty(['email', 'phone'])] -class CreateUser -{ - #[Email] - private ?string $email; - - #[Phone] - private ?string $phone; - - // getters & setters... -} -``` - -Теперь вся наша проверка сведется к передачи значения сервису валидации: -```php -use Bitrix\Main\Result; -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(CreateUser $userData): Result - { - $result = $this->validation->validate($userData); - if (!$result->isSuccess()) { - return $result; - } - - // other logic ... - } - -} -``` - -{% cut "Примечание к переходу на валидацию с использованием сервиса" %} - -Если вы не хотите или не можете изменять сигнатуру метода, можно создавать DTO внутри: -```php -public function create(array $userData): Result -{ - $createUser = new CreateUser(); - $createUser->setEmail($userData['email']); - $createUser->setPhone($userData['phone']); - - // ... -} -``` -{% endcut %} - - -## Как это работает? - -В системе валидации используются два ключевых понятия: - -- **Валидатор** — это объект реализующий интерфейс `\Bitrix\Main\Validation\Validator\ValidatorInterface`, который проверяет конкретное значение. Он не зависит от имени свойства или класса. Например, `EmailValidator` проверяет, соответствует ли значение формату email. -- **Правило** — это php-аттрибут, применяющийся к свойству или классу и определяющий: *какие валидаторы использовать*, *в каком контексте* и *с какими настройками*. - -Таким образом: -- валидатор — это механизм проверки, -- правило — это инструкция, где и как его использовать. - -{% note warning %} - -- Валидация работает через рефлексию: модификаторы доступа игнорируются. - -- Если свойство помечено как `nullable` и не было явно установлено, оно пропускается при валидации. - -- Если вы присвоили `null` явно — свойство считается инициализированным, и валидация к нему **применяется**. - -{% endnote %} - -## Рекурсивная валидация - -Используйя атрибут `#[Validatable]` можно так же добиться проверки вложенных объектов. - -```php -use Bitrix\Main\Validation\Rule\Recursive\Validatable; -use Bitrix\Main\Validation\Rule\NotEmpty; -use Bitrix\Main\Validation\Rule\PositiveNumber; -use Bitrix\Main\DI\ServiceLocator; -use Bitrix\Main\Validation\ValidationService; - -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; -} - -$payment = new Payment(); -$payment->status = ''; -$payment->systemCode = ''; - -$order = new Order(); -$order->id = -1; -$order->payment = $payment; - -$buyer = new Buyer(); -$buyer->id = 0; -$buyer->order = $order; - -$result = ServiceLocator::getInstance() - ->get('main.validation.service') - ->validate($buyer); - -// id: Значение поля должно быть не меньше, чем 1 -// order.id: Значение поля должно быть не меньше, чем 1 -// order.payment.status: Значение поля не может быть пустым -// order.payment.systemCode: Custom message error -foreach ($result->getErrors() as $error) { - echo $error->getCode() . ': ' . $error->getMessage(). PHP_EOL; -} -``` - -## Валидаторы без атрибутов - -Валидаторы можно применять без атрибутов для разовой проверки данных, когда нет необходимости описывать правила в объекте. Это подходит для старого кода с массивами и нетипизированными переменными. - -```php -use Bitrix\Main\Validation\Validator\EmailValidator; - -$email = 'bitrix@bitrix.ru'; - -$validator = new EmailValidator(); -$result = $validator->validate($email); -if (!$result->isSuccess()) { - // ... -} -``` - -## Сообщение об ошибке после валидации - -Можно указать свой текст ошибки, который будет возвращен после валидации. - -```php -use Bitrix\Main\Validation\Rule\PositiveNumber; - -class User -{ - public function __construct( - #[PositiveNumber(errorMessage: 'Invalid ID!')] - public readonly int $id, - #[PositiveNumber] - public readonly int $departmentId - ) - {} -} - -$user = new User( - id: -150, - departmentId: -1 -); - -/** @var \Bitrix\Main\Validation\ValidationService $service */ -$result = $service->validate($user); -foreach ($result->getErrors() as $error) { - echo $error->getMessage(); -} -// output: 'Invalid ID!' -// output: 'Значение поля меньше допустимого' -``` - -## Получить сработавший валидатор - -Результат валидации хранит ошибки `\Bitrix\Main\Validation\ValidationError`. Каждая ошибка содержит свойство `failedValidator`. - -```php -$errors = $service->validate($dto)->getErrors(); -foreach ($errors as $error) { - $failedValidator = $error->getFailedValidator(); - // ... -} -``` \ No newline at end of file diff --git a/pages/framework/validation/own.md b/pages/framework/validation/own.md deleted file mode 100644 index 50a0fa8..0000000 --- a/pages/framework/validation/own.md +++ /dev/null @@ -1,256 +0,0 @@ ---- -title: Валидация. Собственные правила и валидаторы -description: 'Валидация. Документация по Bitrix Framework: собственные правила валидации' ---- - -Иногда встроенных валидаторов и правил недостаточно для реализации специфической бизнес-логики. В таких случаях Bitrix Framework позволяет расширять возможности системы создавая собственные валидаторы и правила. - -## Валидатор - -Валидатор выполняет простую задачу - проверяет значение. Он не определяет, относится ли значение к свойству или классу, и не зависит от атрибутов. - -Создать валидатор очень просто: создайте класс реализующий интерфейс `\Bitrix\Main\Validation\Validator\ValidatorInterface` с публичный методом `validate(mixed $value): ValidationResult`. -Вы можете добавить в конструктор класса необходимые параметры, если ваш валидатор это подразумевает. -В методе `validate` создать объект `ValidationResult`, который будет хранить результаты проверки. -Выполнить необходимые проверки и добавить в `ValidationResult` ошибки. - -Пример валидатора для определения является ли значение UUID версии 4: - -```php -use Bitrix\Main\Localization\Loc; -use Bitrix\Main\Validation\ValidationError; -use Bitrix\Main\Validation\ValidationResult; -use Bitrix\Main\Validation\Validator\ValidatorInterface; - -class UUIDv4Validator implements ValidatorInterface -{ - public function validate(mixed $value): ValidationResult - { - $result = new ValidationResult(); - - if ( - !is_string($value) - || preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', $value) !== 1 - ) { - $result->addError(new ValidationError( - message: Loc::getMessage('FUSION_VALIDATION_VALIDATOR_UUIDV4_NOT_VALID'), - failedValidator: $this - )); - return $result; - } - - return $result; - } -} -``` - -> В разработке валидаторов старайтесь придерживаться правила fail fast - не ждите пока будут выполнены все проверки, если хотя бы один из критериев не соответствует добавляйте ошибку и возвращайте не успешный результат валидации. - - -## Правила - -В отличии от валидаторов правила не так универсальны и просты, поскольку зависят от применения: к свойствам и к классу. В Bitrix Framework для этого требуется реализация разных интерфейсов. - -### Правила для свойства - -Правило для свойства реализует интерфейс `Bitrix\Main\Validation\Rule\PropertyValidationAttributeInterface` с публичным методом `validateProperty(mixed $propertyValue): ValidationResult;`. - -Пример простого правила для свойства объекта: -```php -use Attribute; -use Bitrix\Main\Validation\Rule\PropertyValidationAttributeInterface; -use Bitrix\Main\Validation\ValidationError; -use Bitrix\Main\Validation\ValidationResult; - -#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)] -class UUIDv4 implements PropertyValidationAttributeInterface -{ - public function validateProperty(mixed $propertyValue): ValidationResult - { - $result = new ValidationResult(); - - if ( - !is_string($propertyValue) - || preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', $propertyValue) !== 1 - ) { - $result->addError(new ValidationError('Значение должно быть корректным UUID v4')); - return $result; - } - - return $result; - } -} -``` - -Что происходит в этом коде? -Мы описывает класс `UUIDv4`, реализующий интерфейрс `PropertyValidationAttributeInterface` (валидация свойства), который проверяет что указанное значение явялется UUID версии 4. Мы указали что данный класс может являться php-атрибутом чтобы была возможность использовать его в качестве атрибута на проверяемом объекте. - -И хотя этого кода вполне достаточно для расширения системы использовать его все равно не очень удобно. -Что не так с этим правилом? -1. Отсутствие валидаторов. Без валидаторов мы переносим проблему дублирования кода контроллеров/сервисов в правила. -2. Отсутствие возможности изменить текст ошибки. - -Для удобства разработчиков был создан абстрактный класс `Bitrix\Main\Validation\Rule\AbstractPropertyValidationAttribute` позволяющий легко избавиться от этих недостатков. - -```php -use Attribute; -use Bitrix\Main\Localization\LocalizableMessageInterface; -use UUIDv4Validator; - -#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)] -class UUIDv4 extends AbstractPropertyValidationAttribute -{ - public function __construct( - protected string|LocalizableMessageInterface|null $errorMessage = null - ) { - } - - protected function getValidators(): array - { - return [ - new UUIDv4Validator(), - ]; - } -} - -``` - -> Наследование `AbstractPropertyValidationAttribute` позволяет вам вернуть одно сообщение об ошибке errorMessage, вместо стандартных ответов валидатора - -### Правила для класса - -Подобно атрибуту, правило для класса так же имеет свой интерфейс `Bitrix\Main\Validation\Rule\ClassValidationAttributeInterface` с похожим методом `public function validateObject(object $object): ValidationResult;` и так же имеет абстрактный класс для упрощения создания своих правил - `Bitrix\Main\Validation\Rule\AbstractClassValidationAttribute`. - -Рассмотрим пример создания правила для класса реализующего логику интервала дат - -```php -use Attribute; -use Bitrix\Main\Validation\Rule\AbstractClassValidationAttribute; -use Bitrix\Main\Localization\LocalizableMessage; -use Bitrix\Main\Localization\LocalizableMessageInterface; -use Bitrix\Main\Validation\ValidationError; -use Bitrix\Main\Validation\ValidationResult; -use Bitrix\Main\Type\DateTime; -use ReflectionClass; -use ReflectionProperty; -use Bitrix\Main\Localization\Loc; - -Loc::loadMessages(__FILE__); - -#[Attribute(Attribute::TARGET_CLASS)] -class DateInterval extends AbstractClassValidationAttribute -{ - public function __construct( - private readonly string $startDateProperty, - private readonly string $endDateProperty, - private readonly bool $allowSameDate = false, - private readonly bool $allowNullDate = false, - protected string|LocalizableMessageInterface|null $errorMessage = null - ) {} - - public function validateObject(object $object): ValidationResult - { - $result = new ValidationResult(); - - $properties = $this->getProperties($object); - - if (empty($properties)) { - $result->addError(new ValidationError( - new LocalizableMessage( - "FUSION_INTRANET_VALIDATION_RULE_DATE_INTERVAL_EMPTY_PROPERTIES", - ), - )); - - return $this->replaceWithCustomError($result); - } - - $values = $this->getValues($object, ...$properties); - - $leftValue = $values[$this->startDateProperty] ?? null; - $rightValue = $values[$this->endDateProperty] ?? null; - - if ($leftValue === null) { - if ($this->allowNullDate) { - $leftValue = DateTime::createFromTimestamp(0); - } else { - $result->addError(new ValidationError( - new LocalizableMessage( - "FUSION_INTRANET_VALIDATION_RULE_DATE_INTERVAL_LEFT_BORDER_REQUIRED", - ), - )); - } - } - - if ($rightValue === null) { - if ($this->allowNullDate) { - $rightValue = DateTime::createFromTimestamp( - strtotime("9999-12-31 23:59:59"), - ); - } else { - $result->addError(new ValidationError( - new LocalizableMessage( - "FUSION_INTRANET_VALIDATION_RULE_DATE_INTERVAL_RIGHT_BORDER_REQUIRED", - ), - )); - } - } - - if (!$result->isSuccess()) { - return $this->replaceWithCustomError($result); - } - - $cmp = $rightValue <=> $leftValue; - if ($cmp < 0) { - $result->addError(new ValidationError( - new LocalizableMessage( - "FUSION_INTRANET_VALIDATION_RULE_DATE_INTERVAL_DATE_INTERVAL_INVALID", - ), - )); - } elseif ($cmp === 0 && !$this->allowSameDate) { - $result->addError(new ValidationError( - new LocalizableMessage( - "FUSION_INTRANET_VALIDATION_RULE_DATE_INTERVAL_DATE_INTERVAL_SAME_NOT_ALLOWED", - ), - )); - } - - return $this->replaceWithCustomError($result); - } - - private function getProperties(object $object): array - { - $reflection = new ReflectionClass($object); - - return array_filter( - $reflection->getProperties(), - fn(ReflectionProperty $property): bool => in_array( - $property->getName(), - [$this->startDateProperty, $this->endDateProperty], - true, - ), - ); - } - - private function getValues( - object $object, - ReflectionProperty ...$properties, - ): array { - $values = []; - foreach ($properties as $property) { - if ($property->isInitialized($object)) { - $values[$property->getName()] = $property->getValue($object); - } else { - $values[$property->getName()] = null; - } - } - - return $values; - } -} - - -``` - -> Наследование `AbstractClassValidationAttribute` позволяет вам вернуть одно сообщение об ошибке `errorMessage`, вместо стандартных ответов валидатора - - diff --git a/toc.yaml b/toc.yaml index 734aecf..5f23e8f 100644 --- a/toc.yaml +++ b/toc.yaml @@ -98,15 +98,7 @@ items: - name: Cookie-файлы href: /pages/framework/cookies.md - name: Валидация - items: - - name: Основное - href: /pages/framework/validation/main.md - - name: Существующие правила - href: /pages/framework/validation/exist.md - - name: Контроллеры - href: /pages/framework/validation/controller.md - - name: Свои правила - href: /pages/framework/validation/own.md + href: /pages/framework/validation.md - name: Результат работы и ошибки href: /pages/framework/results-and-errors.md - name: Компоненты