Документация для разработчиков
Темная тема

Использование валидации

Введение

Иногда необходимо проверить, что данные корректны, не привязываясь к бизнес-логике. Например, идентификатор пользователя не должен быть меньше 1.

public function __construct(int $userId)
{
    if ($userId <= 0)
    {
        throw new \Exception();
    }
    
    $this->userId = $userId;
}

Такие проверки могут увеличивать объем кода, поэтому для упрощения используется валидация через атрибуты.


Установка правил валидации

Рассмотрим пример класса пользователя.

final class User
{
    private ?int $id;
    private ?string $email;
    private ?string $phone;
        
    // getters & setters ...
}

Ограничения:

  • id больше 0,
  • email — валидный адрес,
  • phone — валидный телефон,
  • заполнен email или phone.

Добавим атрибуты валидации.

use Bitrix\Main\Validation\Rule\AtLeastOnePropertyNotEmpty;
use Bitrix\Main\Validation\Rule\Email;
use Bitrix\Main\Validation\Rule\Phone;
use Bitrix\Main\Validation\Rule\PositiveNumber;

#[AtLeastOnePropertyNotEmpty(['email', 'phone'])]
final class User
{
    #[PositiveNumber]
    private ?int $id;
    
    #[Email]
    private ?string $email;
    
    #[Phone]
    private ?string $phone;
    
    // getters & setters ...
}

Теперь можно валидировать через \Bitrix\Main\Validation\ValidationService, который доступен через локатор по ключу main.validation.service. Это позволяет валидировать класс в том месте, где нужно. Например, при сохранении в базу данных.

use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Validation\ValidationService;

class UserService
{
    private ValidationService $validation;
    
    public function __construct()
    {
        $this->validation = ServiceLocator::getInstance()->get('main.validation.service');
    }
    
    public function create(?string $email, ?string $phone): Result
    {
        $user = new User();
        $user->setEmail($email);
        $user->setPhone($phone);
        
        $result = $this->validation->validate($user);
        if (!$result->isSuccess())
        {
            return $result;
        }
        
        // save logic ...
    }
}

ValidationService предоставляет метод validate(), возвращающий ValidationResult. Результат валидации содержит ошибки всех сработавших валидаторов.

Важно:
  • Модификаторы доступа у свойств не учитываются, валидация происходит через рефлексию.
  • Если атрибут nullable и его значение не установлено, он будет пропущен при валидации.

Валидация вложенных объектов

Если объект сложный и содержит вложенные объекты, можно использовать атрибут \Bitrix\Main\Validation\Rule\Recursive\Validatable. Это укажет, что объект также должен быть провалидирован.

use Bitrix\Main\Validation\Rule\Composite\Validatable;
use Bitrix\Main\Validation\Rule\NotEmpty;
use Bitrix\Main\Validation\Rule\PositiveNumber;

class Buyer
{
    #[PositiveNumber]
    public ?int $id;

    #[Validatable]
    public ?Order $order;
}

class Order
{
    #[PositiveNumber]
    public int $id;

    #[Validatable]
    public ?Payment $payment;
}

class Payment
{
    #[NotEmpty]
    public string $status;

    #[NotEmpty(errorMessage: 'Custom message error')]
    public string $systemCode;
}

// validation

/** @var \Bitrix\Main\Validation\ValidationService $validationService */
$validationService = \Bitrix\Main\DI\ServiceLocator::getInstance()->get('main.validation.service');

$buyer = new Buyer();
$buyer->id = 0;
$result1 = $validationService->validate($buyer);

// "id: Значение поля меньше допустимого"
foreach ($result1->getErrors() as $error)
{
    echo $error->getCode() . ': ' . $error->getMessage(). PHP_EOL;
}

echo PHP_EOL;

$buyer->id = 1;

$order = new Order();
$order->id = -1;
$buyer->order = $order;

$result2 = $validationService->validate($buyer);

// "order.id: Значение поля меньше допустимого"
foreach ($result2->getErrors() as $error)
{
    echo $error->getCode() . ': ' . $error->getMessage(). PHP_EOL;
}

echo PHP_EOL;

$buyer->order->id = 123;

$payment = new Payment();
$payment->status = '';
$payment->systemCode = '';

$buyer->order->payment = $payment;
$result3 = $validationService->validate($buyer);

// "order.payment.status: Значение поля не может быть пустым"
// "order.payment.systemCode: Custom message error"
foreach ($result3->getErrors() as $error)
{
    echo $error->getCode() . ': ' . $error->getMessage(). PHP_EOL;
}

Валидация в контроллерах

Пример валидации в контроллере.

use Bitrix\Main\Validation\Rule\NotEmpty;
use Bitrix\Main\Validation\Rule\PhoneOrEmail;

final class CreateUserDto
{
    public function __construct(
        #[PhoneOrEmail]
        public ?string $login,
        
        #[NotEmpty]
        public ?string $password,
        
        #[NotEmpty]
        public ?string $passwordRepeat,
    )
    {}
}

В коде класс будет выглядеть так:

class UserController extends Controller
{
    private ValidationService $validation;
    
    protected function init()
    {
        parent::init();
        
        $this->validation = ServiceLocator::getInstance()->get('main.validation.service');
    }
    
    public function createAction(): Result
    {
        $dto = new CreateUserDto();
        $dto->login = (string)$this->getRequest()->get('login');
        $dto->password = (string)$this->getRequest()->get('password');
        $dto->passwordRepeat = (string)$this->getRequest()->get('passwordRepeat');
        
        $result = $this->validation->validate($dto);
        if (!$result->isSuccess())
        {
            $this->addErrors($result->getErrors());
            
            return false;
        }
        
        // create logic ...
    }
}

Чтобы избежать повторения кода, создайте фабричный метод в DTO:

final class CreateUserDto
{
    public function __construct(
        #[PhoneOrEmail]
        public ?string $login = null,
        
        #[NotEmpty]
        public ?string $password = null,
        
        #[NotEmpty]
        public ?string $passwordRepeat = null,
    )
    {}
    
    public static function createFromRequest(\Bitrix\Main\HttpRequest $request): self
    {
        return new static(
            login: (string)$request->getRequest()->get('login'),
            password: (string)$request->getRequest()->get('password'),
            passwordRepeat: (string)$request->getRequest()->get('passwordRepeat'),
        );
    }
}

Воспользуемся автоварингом контроллера и специальным классом Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter, который спрячет повторяющуюся логику валидации.

class UserController extends Controller
{
    public function getAutoWiredParameters()
    {
        return [
            new \Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter(
                CreateUserDto::class,
                fn() => CreateUserDto::createFromRequest($this->getRequest()),
            ),
        ];
    }
    
    public function createAction(CreateUserDto $dto): Result
    {
        // create logic ...
    }
}

Если объект CreateUserDto не валиден, метод createAction не выполнится. Контроллер вернет ошибку.

{
    data: null,
    errors:
    [
        {
            code: "name",
            customData: null,
            message: "Значение поля не должно быть пустым",
        },
    ],
    status: "error"
}

Валидаторы без атрибутов

Валидаторы можно использовать и без атрибутов.

use Bitrix\Main\Validation\Validator\EmailValidator;

$email = 'bitrix@bitrix.com';
$validator = new EmailValidator();
$result = $validator->validate($email);
if (!$result->isSuccess())
{
    // ...
}

Собственное сообщение об ошибке

Можно указать свой текст ошибки, который будет возвращен после валидации.

use Bitrix\Main\Validation\Rule\PositiveNumber;

class User
{
    public function __construct(
        #[PositiveNumber(errorMessage: 'Invalid ID!')]
        public readonly int $id
    )
    {
    }
}

$user = new User(-150);

/** @var \Bitrix\Main\Validation\ValidationService $service */
$result = $service->validate($user);

foreach ($result->getErrors() as $error)
{
    echo $error->getMessage();
}

// output: 'Invalid ID!'

Стандартная ошибка валидатора:

use Bitrix\Main\Validation\Rule\PositiveNumber;

class User
{
    public function __construct(
        #[PositiveNumber]
        public readonly int $id
    )
    {
    }
}

$user = new User(-150);

/** @var \Bitrix\Main\Validation\ValidationService $service */
$result = $service->validate($user);

foreach ($result->getErrors() as $error)
{
    echo $error->getMessage();
}

// output: 'Значение поля меньше допустимого'

Получить сработавший валидатор

Результат валидации хранит ошибки \Bitrix\Main\Validation\ValidationError, которые содержат свойство $this->failedValidator.

$errors = $service->validate($dto)->getErrors();
foreach ($errors as $error)
{
    $failedValidator = $error->getFailedValidator();
    // ...
}

Список доступных атрибутов

Свойства:

  • ElementsType — все элементы перечисляемого свойства должны быть заданного типа
  • Email
  • InArray — значение свойства является одним из элементов массива (для случаев, когда по какой-то причине не удалось использовать Enum)
  • Length
  • Max
  • Min
  • NotEmpty
  • Phone
  • PhoneOrEmail — свойство является либо телефоном, либо почтой
  • PositiveNumber
  • Range
  • RegExp
  • Url
  • Json — свойство (строка) является Json

Классы:

  • AtLeastOnePropertyNotEmpty — проверяет, что хотя бы одно свойство из заданных не пустое (названия свойств прокидываются в конструктор)

Список доступных валидаторов

  • AtLeastOnePropertyNotEmpty — проверяет, что хотя бы одно свойство из заданных не пустое (названия свойств прокидываются в конструктор)
  • Email
  • InArray — значение свойства является одним из элементов массива (для случаев, когда по какой-то причине не удалось использовать Enum)
  • Length
  • Max
  • Min
  • NotEmpty
  • Phone
  • RegExp
  • Url
  • Json — значение переменной (строка) является Json


© «Битрикс», 2001-2025, «1С-Битрикс», 2025