Валидация
Валидация данных — это проверка информации на соответствие заданным правилам. Например, числовой идентификатор должен быть положительным, а email — соответствовать формату адреса.
Как использовать валидацию
В Bitrix Framework данные можно проверять вручную в конструкторе или методах. Ручные проверки приводят к дублированию кода и усложняют его поддержку.
public function __construct(int $userId)
{
if ($userId <= 0)
{
throw new \Exception();
}
$this->userId = $userId;
}
Чтобы сократить код, используйте систему валидации на основе атрибутов. Она позволяет:
-
задавать правила в классах,
-
проверять объекты через единый сервис,
-
проверять параметры действий контроллера до вызова действия,
-
централизованно обрабатывать ошибки.
Правило и валидатор выполняют разные задачи. Правило — PHP-атрибут, который связывает свойство, параметр или класс с проверкой. Валидатор реализует интерфейс ValidatorInterface, проверяет переданное значение и возвращает ValidationResult.
Как добавить правила в класс
-
Создайте класс. Например,
Userсо свойствамиid,emailиphone.final class User { private ?int $id; private ?string $email; private ?string $phone; // getters & setters ... } -
Добавьте атрибуты валидации:
#[PositiveNumber],#[Email]и#[Phone].use Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty; use Bitrix\Main\Validation\Rule\Email; use Bitrix\Main\Validation\Rule\Phone; use Bitrix\Main\Validation\Rule\PositiveNumber; #[AtLeastOnePropertyNotEmpty(['email', 'phone'])] final class User { #[PositiveNumber] private ?int $id; #[Email] private ?string $email; #[Phone] private ?string $phone; // getters & setters... } -
Проверьте валидацию. Объект можно проверить через
\Bitrix\Main\Validation\ValidationServiceпо ключуmain.validation.service.ValidationServiceпредоставляет методvalidate(), который возвращаетValidationResult. Результат валидации содержит ошибки всех сработавших валидаторов.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(); if ($email !== null) { $user->setEmail($email); } if ($phone !== null) { $user->setPhone($phone); } $result = $this->validation->validate($user); if (!$result->isSuccess()) { return $result; } // save logic ... return $result; } }
Валидация работает через рефлексию, поэтому модификаторы доступа не влияют на проверку свойств.
-
Если свойство с типом
nullableне инициализировано, валидация пропускает его. -
Если свойству явно присвоено значение
null, оно считается инициализированным. Сервис передаетnullвалидаторам этого свойства.
Вложенные объекты
Используйте атрибут #[Validatable] для вложенных объектов.
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: Значение поля должно быть не меньше, чем 1"
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: Значение поля должно быть не меньше, чем 1"
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;
}
Валидация массивов
Атрибут #[ElementsType] проверяет, что все элементы массива соответствуют одному из типов перечисления \Bitrix\Main\Validation\Rule\Enum\Type.
-
Type::Integer— целое число. -
Type::String— строка. -
Type::Float— число с плавающей точкой. -
Type::Numeric— число или строка с числом, для которойis_numeric()возвращаетtrue.
use Bitrix\Main\Validation\Rule\ElementsType;
use Bitrix\Main\Validation\Rule\Enum\Type;
use Bitrix\Main\Validation\Rule\NotEmpty;
final class UserSettingsDto
{
public function __construct(
// Свойство должно быть непустым массивом
#[NotEmpty]
// Все элементы массива должны быть целыми числами
#[ElementsType(Type::Integer)] // Используем элемент перечисления
public array $favoriteIds = []
)
{}
}
// Пример использования
$settings = new UserSettingsDto([1, 2, 3]);
$result = $validationService->validate($settings); // Успешно
$invalidSettings = new UserSettingsDto([1, 'текст', 3]);
$result = $validationService->validate($invalidSettings); // Ошибка
// Сообщение: "favoriteIds: Неправильный тип аргумента"
Атрибут #[ElementsType] не проверяет, заполнен ли массив. Для этого требуется дополнительно использовать атрибут #[NotEmpty].
Если элементы требуют нескольких правил, создайте для элемента отдельный объект передачи данных (DTO). Атрибут #[ElementsType] проверит класс каждого элемента, а #[Validatable(iterable: true)] запустит валидацию вложенных объектов.
use Bitrix\Main\Validation\Rule\ElementsType;
use Bitrix\Main\Validation\Rule\Length;
use Bitrix\Main\Validation\Rule\Recursive\Validatable;
use Bitrix\Main\Validation\Rule\RegExp;
// DTO для одного элемента (тега)
final class TagDto
{
public function __construct(
#[RegExp('/^[a-z0-9\-_]+$/')]
#[Length(max: 20)]
public string $name
)
{}
}
final class ArticleDto
{
public function __construct(
#[ElementsType(className: TagDto::class)]
#[Validatable(iterable: true)]
public array $tags = []
)
{}
}
// Использование
$article = new ArticleDto();
$article->tags = [
new TagDto('tag1'),
new TagDto('tag2'),
new TagDto('Invalid Tag!'), // Вызовет ошибку: не соответствует RegExp
];
$result = $validationService->validate($article);
if (!$result->isSuccess())
{
foreach ($result->getErrors() as $error)
{
// Путь к ошибке будет включать индекс элемента, например: "tags.2.name"
echo $error->getCode() . ': ' . $error->getMessage() . PHP_EOL;
}
}
Массив с разнородными параметрами не задает тип каждого значения. Измените структуру объекта одним из способов:
-
Преобразуйте массив в свойства объекта. Если массив содержит пары
ключ => значение, создайте для каждого параметра отдельное типизированное свойство класса с конкретными атрибутами валидации. -
Вынесите массив в отдельный объект. Создайте класс для данных из массива и добавьте его как типизированное свойство в исходный DTO.
Валидация в контроллерах
В контроллерах система валидации проверяет данные из запроса до их обработки.
Для скалярного значения добавьте правило непосредственно к параметру действия. Механизм привязки аргументов проверяет значение до вызова действия.
use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Validation\Rule\PositiveNumber;
class UserController extends Controller
{
public function getAction(#[PositiveNumber] int $id): array
{
return ['id' => $id];
}
}
Для набора связанных значений создайте DTO.
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 в действии контроллера.
use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Engine\Controller;
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(): ?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 null;
}
// create logic ...
return [];
}
}
Создайте фабричный метод в DTO, чтобы избежать повторения кода.
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'),
);
}
}
Класс Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter создает DTO через указанную фабрику и проверяет его перед передачей в действие.
use Bitrix\Main\Engine\Controller;
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): array
{
// create logic ...
return [];
}
}
При ошибке валидации контроллер не вызывает метод createAction и возвращает ошибку.
{
"data": null,
"errors": [
{
"code": "password",
"customData": null,
"message": "Значение поля не может быть пустым"
}
],
"status": "error"
}
Валидаторы без атрибутов
Применяйте валидаторы без атрибутов для разовой проверки данных, когда нет необходимости описывать правила в объекте. Такой вариант подходит для существующего кода с массивами и нетипизированными переменными.
use Bitrix\Main\Validation\Validator\EmailValidator;
$email = 'bitrix@bitrix.ru';
$validator = new EmailValidator();
$result = $validator->validate($email);
if (!$result->isSuccess())
{
// ...
}
Сообщение об ошибке после валидации
В параметре errorMessage можно указать текст, который валидатор вернет при ошибке.
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!'
Если сообщение должно зависеть от языка, передайте в параметр errorMessage объект LocalizableMessage. Параметр phraseSrcFile задает PHP-файл, которому принадлежит фраза. Создайте для него языковой файл в каталоге lang/<код языка> с сохранением относительного пути и определите в нем элемент массива $MESS с указанным кодом.
use Bitrix\Main\Localization\LocalizableMessage;
use Bitrix\Main\Validation\Rule\PositiveNumber;
class User
{
public function __construct(
#[PositiveNumber(errorMessage: new LocalizableMessage(
'MY_MODULE_INVALID_ID',
phraseSrcFile: __FILE__
))]
public readonly int $id
)
{}
}
Пример. Без параметра errorMessage валидатор возвращает стандартное сообщение:
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: 'Значение поля должно быть не меньше, чем 1'
Получить сработавший валидатор
Результат валидации хранит ошибки \Bitrix\Main\Validation\ValidationError. Каждая ошибка содержит свойство failedValidator.
$errors = $service->validate($dto)->getErrors();
foreach ($errors as $error)
{
$failedValidator = $error->getFailedValidator();
// ...
}
Доступные атрибуты и валидаторы
Bitrix Framework предоставляет готовые атрибуты и валидаторы для самых частых сценариев проверки данных.
Атрибуты свойств:
-
ElementsType— проверка типа элементов массива, -
Email— валидация email с отключением предварительной обработки адреса через параметрstrictи проверкой домена через параметрdomainCheck, -
InArray— значение входит в массив допустимых значений, -
Length— проверка длины строки, -
Max— максимальное значение, -
Min— минимальное значение, -
NotEmpty— проверка непустого значения с разрешением нуля через параметрallowZeroи строки из пробелов через параметрallowSpaces, -
Phone— валидация телефона, -
PhoneOrEmail— телефон или email, -
PositiveNumber— положительное число, -
Range— значение в диапазоне, -
RegExp— проверка по регулярному выражению с параметрамиflagsиoffset, которые передаются вpreg_match(), -
Url— валидный URL, -
Json— валидный JSON.
Атрибуты классов:
-
AtLeastOnePropertyNotEmpty— хотя бы одно свойство не пусто, -
OnlyOneOfPropertyRequired— ровно одно из перечисленных свойств не пусто.
Валидаторы:
-
EmailValidator— валидация email, -
InArrayValidator— проверка вхождения в массив, -
LengthValidator— проверка длины строки, -
MaxValidator— максимальное значение, -
MinValidator— минимальное значение, -
NotEmptyValidator— не пустое значение, -
PhoneValidator— валидация телефона, -
RegExpValidator— проверка по регулярному выражению, -
UrlValidator— валидация URL, -
JsonValidator— валидация JSON.
Как создать собственные валидаторы
Каждый валидатор реализует интерфейс \Bitrix\Main\Validation\Validator\ValidatorInterface с методом public function validate(mixed $value): ValidationResult.
Валидатор проверяет значение. Он не определяет, относится ли значение к свойству или классу, и не зависит от атрибутов.
Пример валидатора MinValueValidator
-
Класс
MinValueValidatorреализует интерфейсValidatorInterface. -
Конструктор принимает минимальное значение.
-
Метод
validate()создает объектValidationResult, проверяет тип и минимальное значение, добавляет найденные ошибки и возвращает результат.
namespace Vendor\Module\Validation\Validator;
use Bitrix\Main\Validation\ValidationError;
use Bitrix\Main\Validation\ValidationResult;
use Bitrix\Main\Validation\Validator\ValidatorInterface;
final class MinValueValidator 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(
'Значение должно быть числом',
failedValidator: $this
)
);
return $result;
}
if ($value < $this->min)
{
$result->addError(
new ValidationError(
'Значение меньше допустимого минимума',
failedValidator: $this
)
);
}
return $result;
}
}
Как создать атрибуты валидации
Система поддерживает два типа атрибутов валидации: для свойств и для классов.
Атрибуты свойств
Атрибуты свойств реализуют интерфейс \Bitrix\Main\Validation\Rule\PropertyValidationAttributeInterface. Они используют метод validateProperty(mixed $propertyValue): ValidationResult для проверки значений свойств.
Пример атрибута для проверки значения свойства:
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, который проверяет, что значение находится в заданном диапазоне:
use Attribute;
use Bitrix\Main\Localization\LocalizableMessageInterface;
use Bitrix\Main\Validation\Rule\AbstractPropertyValidationAttribute;
use Bitrix\Main\Validation\Validator\MaxValidator;
use Bitrix\Main\Validation\Validator\MinValidator;
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Range extends AbstractPropertyValidationAttribute
{
public function __construct(
private readonly int $min,
private readonly int $max,
protected string|LocalizableMessageInterface|null $errorMessage = null
)
{}
protected function getValidators(): array
{
return [
new MinValidator($this->min),
new MaxValidator($this->max),
];
}
}
Атрибуты класса
Атрибуты класса реализуют интерфейс \Bitrix\Main\Validation\Rule\ClassValidationAttributeInterface. Они используют метод validateObject(object $object): ValidationResult для проверки объектов.
Пример атрибута MaximumProperties для проверки количества свойств:
use Bitrix\Main\Validation\ValidationResult;
use Bitrix\Main\Validation\ValidationError;
use Bitrix\Main\Validation\Rule\AbstractClassValidationAttribute;
use ReflectionClass;
#[Attribute(Attribute::TARGET_CLASS)]
class MaximumProperties 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. Абстрактный класс заменяет стандартные ошибки валидаторов одной ошибкой с указанным текстом.
Пример. Передайте сообщение в конструктор атрибута Range:
final class ProductDto
{
public function __construct(
#[Range(1, 10, errorMessage: 'Количество должно быть от 1 до 10')]
public int $quantity
)
{}
}
Используйте атрибут свойства для проверки одного значения, атрибут класса — для связи между несколькими свойствами, а #[Validatable] — для перехода к вложенному объекту.