Symfony/Validator для PHP: полное руководство с примерами
1. Что это и зачем нужна
Библиотека Symfony/Validator — это мощный и гибкий компонент экосистемы Symfony, предназначенный для валидации данных. В отличие от простых проверок "на лету", этот компонент предлагает декларативный подход: вы описываете правила валидации (ограничения) с помощью аннотаций, атрибутов PHP 8+, YAML, XML или PHP-массивов, а затем передаёте данные специальному сервису — ValidatorInterface. Это позволяет отделить логику проверки от бизнес-логики приложения, делая код более чистым, тестируемым и переиспользуемым.
Зачем это нужно? В любом веб-приложении (и не только) данные поступают из ненадёжных источников: формы на сайте, API-запросы, импорт из CSV-файлов. Без валидации вы рискуете получить SQL-инъекции, XSS-атаки, некорректные расчёты или просто "мусор" в базе данных. Symfony/Validator решает эти проблемы комплексно: он проверяет типы данных, длины строк, диапазоны чисел, уникальность email, соответствие регулярным выражениям и многое другое. Более того, он умеет собирать все ошибки в единый объект ConstraintViolationList, который можно легко преобразовать в JSON-ответ для API или отобразить в виде списка ошибок на форме.
Компонент полностью независим от фреймворка Symfony. Вы можете использовать его в любом PHP-проекте (Laravel, Yii, собственный фреймворк или даже "чистый" PHP). Он активно поддерживается, имеет отличную документацию и огромное сообщество. Это "золотой стандарт" валидации в мире PHP.
2. Установка
Установка производится через Composer. Выполните в терминале в корне вашего проекта:
composer require symfony/validator
Также рекомендуется установить компонент symfony/property-access для корректной работы с вложенными объектами и массивами:
composer require symfony/property-access
Если вы планируете использовать аннотации/атрибуты, убедитесь, что у вас установлен doctrine/annotations (для PHP 7.x) или вы используете PHP 8+ с нативными атрибутами.
3. Быстрый старт — минимальный рабочий пример
Создадим простой класс User с атрибутами валидации и проверим его.
<?php
// 1. Подключаем автозагрузку Composer
require_once 'vendor/autoload.php';
// 2. Импортируем необходимые классы
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints as Assert;
// 3. Создаём класс с атрибутами валидации (PHP 8+)
class User {
#[Assert\NotBlank(message: "Имя не может быть пустым")]
#[Assert\Length(min: 2, max: 50, minMessage: "Имя должно содержать минимум {{ limit }} символа")]
public string $name;
#[Assert\NotBlank]
#[Assert\Email(message: "Email '{{ value }}' не является валидным.")]
public string $email;
#[Assert\PositiveOrZero]
public int $age;
}
// 4. Создаём валидатор
$validator = Validation::createValidatorBuilder()
->enableAttributeMapping() // Включаем поддержку атрибутов
->getValidator();
// 5. Создаём объект с некорректными данными
$user = new User();
$user->name = 'A'; // Слишком короткое имя
$user->email = 'not-an-email';
$user->age = -5;
// 6. Валидируем
$violations = $validator->validate($user);
// 7. Обрабатываем ошибки
if (count($violations) > 0) {
foreach ($violations as $violation) {
echo $violation->getPropertyPath() . ': ' . $violation->getMessage() . "\n";
}
} else {
echo "Данные валидны!";
}
Этот пример выведет:
name: Имя должно содержать минимум 2 символа
email: Email 'not-an-email' не является валидным.
age: Это значение должно быть положительным или равным нулю.
4. Основные методы/функции/классы
4.1. Validation::createValidatorBuilder()
Сигнатура: public static function createValidatorBuilder(): ValidatorBuilder
Что делает: Создаёт и возвращает строитель (builder) для конфигурации валидатора. Это отправная точка для настройки всех параметров.
$builder = Validation::createValidatorBuilder();
$validator = $builder->getValidator(); // Получаем готовый валидатор
4.2. ValidatorBuilder::getValidator()
Сигнатура: public function getValidator(): ValidatorInterface
Что делает: Собирает и возвращает готовый к использованию объект валидатора на основе настроек, переданных в builder.
$validator = Validation::createValidatorBuilder()
->enableAttributeMapping()
->getValidator();
4.3. ValidatorInterface::validate()
Сигнатура: public function validate(mixed $value, Constraint|array|null $constraints = null, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface
Что делает: Основной метод для валидации. Принимает значение (объект, массив, скаляр) и возвращает список нарушений (ConstraintViolationListInterface). Если нарушений нет, список будет пустым.
$user = new User();
$user->email = 'test@example.com';
$violations = $validator->validate($user);
if (0 === count($violations)) {
echo "OK";
}
4.4. ConstraintViolationListInterface
Сигнатура: Это интерфейс, реализуемый классом ConstraintViolationList. Основные методы: count(), get(int $offset), getIterator().
Что делает: Представляет собой коллекцию объектов ConstraintViolationInterface. Позволяет итерироваться по ошибкам и получать их количество.
$violations = $validator->validate($data);
echo "Найдено ошибок: " . count($violations);
foreach ($violations as $violation) {
// $violation - объект ConstraintViolationInterface
}
4.5. ConstraintViolationInterface::getMessage()
Сигнатура: public function getMessage(): string
Что делает: Возвращает сообщение об ошибке, которое было задано в ограничении (или стандартное сообщение).
foreach ($violations as $violation) {
echo $violation->getMessage(); // "Это значение не должно быть пустым."
}
4.6. ConstraintViolationInterface::getPropertyPath()
Сигнатура: public function getPropertyPath(): string
Что делает: Возвращает путь к свойству, которое вызвало ошибку. Для вложенных объектов путь может быть составным (например, address.city).
foreach ($violations as $violation) {
echo $violation->getPropertyPath(); // "email" или "address.street"
}
4.7. ConstraintViolationInterface::getInvalidValue()
Сигнатура: public function getInvalidValue(): mixed
Что делает: Возвращает то самое некорректное значение, которое было передано и не прошло валидацию. Полезно для логирования или отладки.
foreach ($violations as $violation) {
$badValue = $violation->getInvalidValue();
error_log("Некорректное значение: " . print_r($badValue, true));
}
4.8. ValidatorBuilder::enableAnnotationMapping() / enableAttributeMapping()
Сигнатура: public function enableAnnotationMapping(): static (и аналогично для атрибутов)
Что делает: Включает чтение ограничений из аннотаций (Doctrine) или нативных атрибутов PHP 8. Без этого вызова валидатор не увидит атрибуты в ваших классах.
$validator = Validation::createValidatorBuilder()
->enableAttributeMapping() // Для PHP 8+
->getValidator();
4.9. Validation::createValidator()
Сигнатура: public static function createValidator(): ValidatorInterface
Что делает: Быстрый способ получить валидатор "из коробки" без какой-либо дополнительной конфигурации. Подходит для простых случаев, когда не нужно загружать метаданные из аннотаций.
$validator = Validation::createValidator();
$violations = $validator->validate('some_value', new Assert\Email());
4.10. ValidatorInterface::validateProperty()
Сигнатура: public function validateProperty(object $object, string $propertyName, string|GroupSequence|array|null $groups = null): ConstraintViolationListInterface
Что делает: Позволяет валидировать только одно конкретное свойство объекта. Это эффективнее, чем валидировать весь объект, если вы знаете, какое поле изменилось (например, при валидации на стороне клиента через AJAX).
$user = new User();
$user->email = 'invalid';
$violations = $validator->validateProperty($user, 'email');
// Будут проверены только ограничения для свойства $email
5. Реальный практический пример
Представьте, что мы создаём REST API для регистрации пользователей. Нам нужно провалидировать входящий JSON-запрос. В этом примере мы покажем, как использовать группы валидации и кастомные ограничения.
<?php
require_once 'vendor/autoload.php';
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
// --- Шаг 1: Создаём кастомное ограничение для проверки сложности пароля ---
class StrongPassword extends Constraint {
public string $message = 'Пароль должен содержать минимум 8 символов, одну заглавную букву и одну цифру.';
}
class StrongPasswordValidator extends ConstraintValidator {
public function validate(mixed $value, Constraint $constraint): void {
if (!preg_match('/^(?=.*[A-Z])(?=.*\d).{8,}$/', $value)) {
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
// --- Шаг 2: Создаём DTO (Data Transfer Object) с группами валидации ---
class RegistrationRequest {
#[Assert\NotBlank(groups: ['registration'])]
#[Assert\Length(min: 2, max: 50, groups: ['registration'])]
public string $username;
#[Assert\NotBlank(groups: ['registration'])]
#[Assert\Email(groups: ['registration'])]
public string $email;
#[Assert\NotBlank(groups: ['registration'])]
#[StrongPassword(groups: ['registration'])]
public string $password;
#[Assert\NotBlank(groups: ['registration'])]
#[Assert\EqualTo(propertyPath: 'password', message: 'Пароли не совпадают', groups: ['registration'])]
public string $confirmPassword;
#[Assert\Type(type: 'bool', groups: ['optional'])]
public bool $subscribeToNewsletter = false;
}
// --- Шаг 3: Настраиваем валидатор и регистрируем кастомный валидатор ---
$validator = Validation::createValidatorBuilder()
->enableAttributeMapping()
->addMethodMapping(function (ExecutionContextInterface $context) {
// Можно добавить кастомную логику на уровне класса
})
->getValidator();
// --- Шаг 4: Имитируем входящий JSON-запрос ---
$jsonPayload = '{
"username": "jo",
"email": "bad-email",
"password": "weak",
"confirmPassword": "weak1"
}';
$data = json_decode($jsonPayload, false); // Преобразуем в объект
// Создаём объект запроса и заполняем его
$request = new RegistrationRequest();
$request->username = $data->username ?? '';
$request->email = $data->email ?? '';
$request->password = $data->password ?? '';
$request->confirmPassword = $data->confirmPassword ?? '';
// --- Шаг 5: Валидируем с использованием группы 'registration' ---
$violations = $validator->validate($request, null, ['registration']);
// --- Шаг 6: Формируем ответ для API ---
if (count($violations) > 0) {
$errors = [];
foreach ($violations as $violation) {
$errors[] = [
'field' => $violation->getPropertyPath(),
'message' => $violation->getMessage(),
];
}
http_response_code(422);
echo json_encode(['status' => 'error', 'errors' => $errors], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} else {
http_response_code(201);
echo json_encode(['status' => 'success', 'message' => 'Пользователь зарегистрирован!'], JSON_UNESCAPED_UNICODE);
}
// --- Ожидаемый вывод (из-за некорректных данных) ---
// {
// "status": "error",
// "errors": [
// {
// "field": "username",
// "message": "Имя пользователя должно содержать минимум 2 символа"
// },
// {
// "field": "email",
// "message": "Значение \"bad-email\" не является валидным адресом электронной почты."
// },
// {
// "field": "password",
// "message": "Пароль должен Также в библиотеке
Bull для JavaScript (Node.js): полное руководство с примерами
GraphQL библиотека JavaScript (Node.js): Полное руководство по Apollo Server и инструментам
Валидация данных в Swift: библиотека Validator
Кэширование в Rust: библиотека `cached`
Crypto-js для JavaScript (Node.js): полное руководство с примерами