Правила валидации Laravel 13: справочник

Laravel содержит десятки встроенных правил валидации, и запомнить, чем одно отличается от другого, с первого раза непросто. В этом справочнике разобраны ключевые правила с примерами и рекомендациями по выбору между похожими правилами. Правила, для которых есть отдельная статья с детальным разбором, описаны кратко со ссылкой.

Три способа задать правила

Прежде чем перейти к конкретным правилам, разберёмся с синтаксисом. Laravel позволяет записывать правила тремя способами, и выбор между ними влияет на читаемость кода.

Строковый синтаксис через вертикальную черту – самый компактный вариант:

$request->validate([
    'title' => 'required|string|max:200',
    'category_id' => 'required|integer|exists:categories,id',
]);

Массив правил – обязательный формат, если одно из правил содержит запятые или использует объект Rule:

use Illuminate\Validation\Rule;

$request->validate([
    'email' => ['required', 'email:rfc,dns', Rule::unique('users')->ignore($user->id)],
    'role' => ['required', Rule::in(['editor', 'viewer'])],
]);

Fluent-билдер через класс Rule – появился в Laravel 11 (Rule::date(), Rule::string() и другие). Позволяет собирать сложные правила цепочкой вызовов:

use Illuminate\Validation\Rule;

$request->validate([
    'username' => [
        'required',
        Rule::string()->min(3)->max(30)->alphaDash(ascii: true),
    ],
    'start_date' => [
        'required',
        Rule::date()->afterToday(),
    ],
]);

Все три способа можно комбинировать в рамках одного вызова validate. Строковый формат подходит для простых правил, массивы – для составных, а fluent-билдер – когда нужна условная логика через when().

Обязательность и присутствие полей

Эта группа правил отвечает за то, должно ли поле существовать в запросе и может ли оно быть пустым. Путаница между ними – одна из самых частых проблем при валидации.

required

Поле должно присутствовать в данных и не быть пустым. Пустыми считаются: null, пустая строка "", пустой массив (или пустой Countable-объект), загруженный файл без пути.

$request->validate([
    'name' => 'required|string|max:100',
    'items' => 'required|array|min:1',
]);

filled

Если поле присутствует в запросе – оно не должно быть пустым. Но если поля нет вообще – валидация пройдёт. Правило полезно для API, где клиент может не отправлять поле, но если отправил – значение обязательно.

$request->validate([
    'nickname' => 'filled|string|max:50',
]);

// { } – ok (поля нет)
// { "nickname": "alex" } – ok
// { "nickname": "" } – ошибка

present

Поле обязано быть в запросе, но может содержать что угодно, включая null и пустую строку. Используется, когда фронтенд должен явно передать ключ, даже если значение пустое.

$request->validate([
    'comment' => 'present|nullable|string',
]);

// { "comment": null } – ok
// { "comment": "" } – ok
// { } – ошибка (ключ отсутствует)

У present есть условные варианты – present_if, present_unless, present_with, present_with_all – для случаев, когда обязательность ключа зависит от других полей.

nullable

Разрешает значение null. Без этого правила null провалит проверку любым типовым правилом вроде string или integer.

$request->validate([
    'middle_name' => 'nullable|string|max:50',
    'deleted_at' => 'nullable|date',
]);

Частое заблуждение: nullable не делает поле необязательным. Если перед nullable стоит required – поле обязано быть в запросе, но может прийти как null.

sometimes

Правило sometimes говорит валидатору: проверяй остальные правила только если поле реально присутствует в данных. Если ключа нет – пропусти его целиком.

$validator = Validator::make($data, [
    'phone' => 'sometimes|required|string|min:10',
]);

В этом примере, если ключ phone отсутствует в $data, ни одно правило для него не сработает. Но если ключ есть – дальше идёт required, и пустая строка не пройдёт.

sometimes vs nullable – в чём разница

Это один из самых задаваемых вопросов. Разница принципиальная:

// Поле можно не отправлять. Если отправили – проверяем формат
'bio' => 'sometimes|nullable|string|max:1000',

// Поле обязательно, но может быть null
'supervisor_id' => 'required|nullable|integer|exists:users,id',

Первый вариант (sometimes|nullable) подходит для PATCH-запросов, где клиент отправляет только изменённые поля. Второй – для полей вроде “руководитель”, которые всегда должны быть в запросе, но у кого-то руководителя нет.

Необязательные поля

В Laravel нет отдельного правила optional. Необязательность поля достигается комбинацией sometimes|nullable. Middleware ConvertEmptyStringsToNull (включён по умолчанию) превращает пустые строки в null, поэтому nullable обычно нужен вместе с sometimes:

$request->validate([
    'website' => 'sometimes|nullable|url',
    'notes' => 'sometimes|nullable|string|max:2000',
]);

Поля, не перечисленные в правилах вообще, не валидируются и не попадают в результат validated(). Если поле нужно в результате, но оно необязательное – его придётся явно прописать с sometimes|nullable.

missing

Обратное present – ключ обязан отсутствовать в данных. Полезно для API-версионирования или когда поле удалено из формы, а клиент продолжает его отправлять:

$request->validate([
    'legacy_field' => 'missing',
]);

Условные варианты – missing_if, missing_unless, missing_with, missing_with_all – позволяют требовать отсутствие ключа при определённых условиях.

bail и остановка при первой ошибке

По умолчанию Laravel проверяет все правила для каждого поля и собирает полный список ошибок. Правило bail меняет поведение: при первой же ошибке в правилах данного поля проверка останавливается.

$request->validate([
    'email' => 'bail|required|email:rfc|unique:users',
    'password' => 'required|min:8',
]);

Здесь, если email не прошёл проверку required, правила email:rfc и unique:users не выполнятся. Для password же проверка всё равно пройдёт полностью – bail действует только на своё поле.

По соглашению bail ставят первым в списке правил для читаемости – сразу видно, что поле остановится при первой ошибке.

Зачем это нужно на практике: правило unique выполняет SQL-запрос. Если поле вообще пустое, нет смысла лезть в базу – bail предотвращает лишний запрос.

Когда bail мешает: если форма должна показать все ошибки разом (стандартное поведение), bail ухудшит UX – пользователь будет исправлять ошибки по одной. Используйте bail точечно для тяжёлых правил, а не для каждого поля.

Для глобальной остановки на уровне всех полей существует метод stopOnFirstFailure:

$validator = Validator::make($data, $rules);

if ($validator->stopOnFirstFailure()->fails()) {
    // только первая ошибка первого проваленного поля
}

Ограничение допустимых значений

in и not_in

Правило in проверяет, что значение входит в заданный список. Это основной инструмент для полей, где допустим только конкретный набор вариантов.

$request->validate([
    'status' => 'required|in:draft,published,archived',
    'priority' => ['required', Rule::in([1, 2, 3, 4, 5])],
]);

Есть нюанс с типами. Правило in при строковом синтаксисе (in:1,2,3) сравнивает как строки. Если нужна проверка типов, передавайте числа через Rule::in, а не через строку:

// '1' пройдёт, потому что 'in:1,2' сравнивает строки
'level' => 'required|in:1,2,3',

// здесь '1' тоже пройдёт – Rule::in сравнивает нестрого
'level' => ['required', Rule::in([1, 2, 3])],

Если критична строгость типов, добавьте integer или numeric перед in – тогда до проверки in значение уже будет гарантированно числовым.

not_in работает наоборот – значение не должно входить в список:

$request->validate([
    'username' => ['required', 'string', Rule::notIn(['admin', 'root', 'system'])],
]);

enum

Для PHP-перечислений (backed enums) есть отдельное правило Rule::enum. Оно проверяет, что входное значение соответствует одному из значений enum:

enum OrderStatus: string
{
    case Pending = 'pending';
    case Paid = 'paid';
    case Shipped = 'shipped';
    case Cancelled = 'cancelled';
}

$request->validate([
    'status' => ['required', Rule::enum(OrderStatus::class)],
]);

Методы only и except ограничивают допустимые варианты:

// Клиент может выбрать только эти статусы
Rule::enum(OrderStatus::class)->only([
    OrderStatus::Pending,
    OrderStatus::Cancelled,
]);

// Все, кроме отменённого
Rule::enum(OrderStatus::class)->except([OrderStatus::Cancelled]);

Условная логика через when:

Rule::enum(OrderStatus::class)->when(
    $user->isManager(),
    fn ($rule) => $rule->only([OrderStatus::Shipped, OrderStatus::Cancelled]),
    fn ($rule) => $rule->except([OrderStatus::Shipped]),
);

Если в проекте нет backed enum, а значения хранятся в конфиге или БД, используйте Rule::in вместо Rule::enum – они решают одну задачу разными способами.

accepted и declined

Правило accepted проверяет, что значение – одно из: "yes", "on", 1, "1", true, "true". Предназначено для чекбоксов согласия с условиями:

$request->validate([
    'terms' => 'accepted',
    'privacy_policy' => 'accepted',
]);

declined – обратное: "no", "off", 0, "0", false, "false". Обе формы имеют условные варианты: accepted_if:field,value и declined_if:field,value.

boolean

Проверяет, что значение приводится к булевому: true, false, 1, 0, "1", "0". Строковый параметр strict (документирован начиная с Laravel 11) допускает только true и false:

$request->validate([
    'is_active' => 'boolean',
    'notifications' => 'boolean:strict',
]);

Подробнее про валидацию булевых значений и чекбоксов – в статье про валидацию строк и чисел.

Сравнение полей и подтверждение

confirmed

Правило confirmed ожидает, что в данных есть второе поле с суффиксом _confirmation. Типичное применение – подтверждение пароля или email:

$request->validate([
    'password' => 'required|min:8|confirmed',
]);
// в запросе должно быть поле password_confirmation

Можно указать другое имя поля для подтверждения:

$request->validate([
    'email' => 'required|email|confirmed:email_repeat',
]);
// сравнивает email с полем email_repeat

same и different

Правило same проверяет совпадение значений двух полей, different – наоборот:

$request->validate([
    'new_password' => 'required|min:8|different:current_password',
    'new_password_confirmation' => 'required|same:new_password',
]);

На первый взгляд same и confirmed делают одно и то же, но механика отличается:

// confirmed: правило на исходном поле, суффикс _confirmation подразумевается
'password' => 'required|confirmed',
// Laravel сам найдёт password_confirmation и сравнит

// same: правило на втором поле, имя первого указывается явно
'password_repeat' => 'required|same:password',
// сравнивает password_repeat с password

confirmed удобнее для стандартных форм с суффиксом _confirmation. same нужен, когда имена полей произвольные.

Проверка на точное значение

Для проверки, что поле равно конкретному значению, Laravel не имеет отдельного правила equals. Вместо этого используется in с одним значением или same для сравнения с другим полем:

$request->validate([
    // значение должно быть ровно "premium"
    'plan' => ['required', Rule::in(['premium'])],
    // числовое значение равно 100 (size для integer – это значение, не длина)
    'percentage' => 'required|integer|size:100',
]);

Правило size для числовых полей проверяет точное соответствие значения, а не длину. С правилом integer запись size:10 означает “значение равно 10”.

between

Правило between проверяет, что значение попадает в диапазон (включительно). Поведение зависит от типа поля:

$request->validate([
    'age' => 'required|integer|between:18,120',
    'title' => 'required|string|between:5,200',
    'attachments' => 'array|between:1,5',
    'avatar' => 'file|between:10,2048',
]);

Для чисел – сравнивается значение, для строк – длина в символах, для массивов – количество элементов, для файлов – размер в килобайтах. Детальнее – в статье про валидацию строк и чисел.

Строковые и символьные правила

Правило alpha по умолчанию работает с Unicode – кириллица, иероглифы и диакритические знаки пройдут проверку. Если нужна только латиница, укажите параметр ascii:

$request->validate([
    // "Иван" пройдёт – Unicode-буквы допустимы
    'first_name' => 'required|alpha',
    // "Иван" не пройдёт – только a-z, A-Z
    'username' => 'required|alpha_dash:ascii',
    // буквы + цифры, строго 6 символов
    'code' => 'required|alpha_num|size:6',
]);

Семейство alpha не допускает пробелов. Для имён вроде “Анна Мария” alpha не подходит – используйте regex или string с ограничением длины.

alpha_dash расширяет alpha дефисом и подчёркиванием (но не точкой и не пробелом). alpha_num допускает буквы и цифры без спецсимволов.

Отдельно про regex – частый источник ошибок. Если регулярное выражение содержит символ |, строковый синтаксис через вертикальную черту сломается, потому что Laravel воспримет | как разделитель правил:

// Сломается: Laravel распарсит "regex:/^(foo" как одно правило, "bar)$/" как другое
'code' => 'required|regex:/^(foo|bar)$/',

// Правильно: массив правил, где regex – отдельный элемент
'code' => ['required', 'regex:/^(foo|bar)$/'],

Это касается и not_regex. Если в паттерне есть | – только массив.

Полный список строковых правил – string, email, url, uuid, regex, lowercase, uppercase, starts_with, ends_with – описан в справочнике по валидации строк и чисел.

Правила для массивов

Правило distinct гарантирует отсутствие дубликатов в массиве. Типичный сценарий – форма, где пользователь добавляет несколько email-адресов для рассылки:

$request->validate([
    'recipients' => 'required|array|min:1|max:20',
    'recipients.*.email' => 'required|email|distinct',
    'recipients.*.role' => 'required|in:to,cc,bcc',
]);

Без distinct два одинаковых email пройдут валидацию, и письмо уйдёт дважды.

По умолчанию distinct использует нестрогое сравнение. Параметр strict включает строгую проверку (полезно для массива ID, где "1" и 1 – разные значения), а ignore_case игнорирует регистр:

'options.*.id' => 'distinct:strict',
'categories.*' => 'distinct:ignore_case',

Правила array, list, in_array, contains, required_array_keys и валидация вложенных структур через wildcard * подробно описаны в статье про валидацию массивов и JSON.

prohibited и запрет значений

Правило prohibited требует, чтобы поле было пустым или отсутствовало. Пустым считаются: null, пустая строка, пустой массив, файл без пути.

$request->validate([
    'role' => 'prohibited',
]);

На практике чаще используются условные варианты. prohibited_if запрещает поле, когда другое поле имеет определённое значение:

$request->validate([
    'is_company' => 'required|boolean',
    'company_name' => 'prohibited_if:is_company,false',
    'personal_id' => 'prohibited_if:is_company,true',
]);

prohibited_unless работает наоборот – поле запрещено, если условие не выполнено. prohibits запрещает другие поля при наличии текущего:

$request->validate([
    // если задан custom_message – template_id запрещён
    'custom_message' => 'sometimes|string|max:500|prohibits:template_id',
    'template_id'    => 'required_without:custom_message|integer|exists:templates,id',
]);

Важно: prohibits использует ту же концепцию «пустоты», что и required (null, "", [], файл с пустым путём). Логическое false под это правило не попадает, поэтому 'flag' => 'sometimes|boolean|prohibits:other' забанит other даже на flag: false, что обычно не то, что задумано. Для булевых флагов используйте prohibited_if:flag,true или варианты для чекбоксов, которые описаны ниже.

Для чекбоксов удобны prohibited_if_accepted и prohibited_if_declined – они не требуют перечисления значений, а ориентируются на “включённость” поля:

$request->validate([
    'use_default_address' => 'required|boolean',
    // если чекбокс включён – кастомный адрес запрещён
    'custom_address' => 'prohibited_if_accepted:use_default_address|string|max:500',
]);

Для сложной логики подходит Rule::prohibitedIf с замыканием:

$request->validate([
    'discount_code' => Rule::prohibitedIf(fn () => $order->isPaid()),
]);

Условные правила: зависимость от других полей

Правила required_if, required_with, required_without и другие делают поле обязательным в зависимости от значений соседних полей. Это механизм для форм, где набор полей меняется динамически.

$request->validate([
    'delivery_type' => 'required|in:pickup,courier',
    // адрес обязателен при курьерской доставке
    'address' => 'required_if:delivery_type,courier|string|max:500',
    // время самовывоза обязательно, если выбран самовывоз
    'pickup_time' => 'required_if:delivery_type,pickup|date',
]);

required_with делает поле обязательным, если любое из перечисленных полей присутствует:

$request->validate([
    'latitude' => 'required_with:longitude|numeric|between:-90,90',
    'longitude' => 'required_with:latitude|numeric|between:-180,180',
]);

Детальный разбор всех условных правил – required_if, required_unless, required_with, required_with_all, required_without, required_without_all, а также метод $validator->sometimes() для программной логики – в статье про условную валидацию.

anyOf: OR-логика в правилах

Метод Rule::anyOf позволяет задать альтернативные наборы правил. Поле считается валидным, если проходит хотя бы один из вариантов:

$request->validate([
    'contact' => [
        'required',
        Rule::anyOf([
            ['email'],
            ['string', 'regex:/^\+\d{10,15}$/'],
        ]),
    ],
]);

В этом примере поле contact может быть либо email-адресом, либо телефонным номером в международном формате. Без anyOf пришлось бы писать кастомное правило или разделять поле на два.

Ещё пример – поле, которое принимает либо UUID, либо числовой ID:

$request->validate([
    'reference' => [
        'required',
        Rule::anyOf([
            ['uuid'],
            ['integer', 'min:1'],
        ]),
    ],
]);

Валидация по базе данных

Правила unique и exists выполняют SQL-запросы для проверки значений. Оба поддерживают fluent-синтаксис через Rule::unique и Rule::exists с фильтрами, мягким удалением и ссылками на Eloquent-модели.

Базовый пример:

$request->validate([
    'email' => ['required', 'email', Rule::unique('users')->ignore($user->id)],
    'category_id' => ['required', Rule::exists('categories', 'id')->where('active', true)],
]);

Подробный разбор с мультиколоночной уникальностью, soft deletes, кастомным соединением и продвинутыми where-условиями – в статье про unique и exists.

Порядок правил и их взаимодействие

Правила выполняются слева направо. Это важно, потому что некоторые правила останавливают цепочку.

nullable – если значение null, дальнейшие правила не проверяются. Поле считается прошедшим валидацию:

// null пройдёт, и email:rfc не выполнится
'contact_email' => 'nullable|email:rfc|unique:users',

sometimes работает ещё раньше – если ключа нет в данных, вся цепочка пропускается. Это не то же самое, что “правило не сработало” – поле просто не участвует в валидации и не попадает в validated().

Типовое правило (string, integer, array) определяет, как будут работать min, max, size, between. Без типового правила Laravel попробует угадать тип из входных данных, что приводит к неожиданностям:

// min:3 проверит длину строки (>= 3 символов)
'name' => 'required|string|min:3',

// min:3 проверит значение числа (>= 3)
'quantity' => 'required|integer|min:3',

// без типового правила – если придёт "10", min:3 проверит длину строки "10" (2 символа – ошибка!)
'ambiguous' => 'required|min:3',

Всегда указывайте типовое правило перед размерными.

Правила по типам данных

Для каждого типа данных есть отдельная статья с полным разбором. Ниже – краткая навигация с подсказкой, какое правило выбрать.

Полная таблица правил

Для быстрого поиска – все встроенные правила, сгруппированные по назначению.

Присутствие и обязательность

ПравилоНазначение
requiredПоле обязательно и не пустое
filledНе пустое, если присутствует
presentКлюч обязан быть в данных
nullableРазрешает null
sometimesПроверять, только если ключ есть
missingКлюч обязан отсутствовать
prohibitedПоле пусто или отсутствует

Условные

ПравилоНазначение
required_ifОбязательно при значении другого поля
required_unlessОбязательно, если другое поле не равно
required_withОбязательно при наличии других полей
required_withoutОбязательно при отсутствии других
required_if_acceptedОбязательно, если другое поле accepted
prohibited_ifЗапрещено при значении другого поля
prohibited_unlessЗапрещено, кроме определённого случая
exclude_ifИсключить из validated(), если условие
exclude_unlessВключить в validated(), если условие

Тип значения

ПравилоНазначение
stringСтрока
integerЦелое число
numericЧисло (включая float)
booleanБулево или приводимое к нему
arrayPHP-массив
listМассив с последовательными индексами
jsonВалидная JSON-строка
fileЗагруженный файл
dateВалидная дата

Сравнение

ПравилоНазначение
confirmedСовпадает с полем {field}_confirmation
same:fieldСовпадает с указанным полем
different:fieldОтличается от указанного поля
gt:fieldБольше другого поля
gte:fieldБольше или равно
lt:fieldМеньше другого поля
lte:fieldМеньше или равно

Ограничение значений

ПравилоНазначение
in:a,b,cОдно из перечисленных значений
not_in:a,b,cНе входит в список
Rule::enum()Значение PHP enum
acceptedЗначение вроде yes/on/true/1
declinedЗначение вроде no/off/false/0
Rule::anyOf()Проходит хотя бы один набор правил

Размер и диапазон

ПравилоНазначение
min:nМинимум (символов, значения, элементов, КБ)
max:nМаксимум
size:nТочное значение
between:min,maxДиапазон (включительно)
digits:nТочное количество цифр
digits_between:min,maxДиапазон цифр (legacy, используйте min_digits/max_digits)
min_digits:nМинимум цифр
max_digits:nМаксимум цифр

Строковые

ПравилоНазначение
alphaТолько буквы
alpha_dashБуквы, цифры, дефис, подчёркивание
alpha_numБуквы и цифры
emailФормат email
urlФормат URL
uuidФормат UUID
regex:patternСоответствие регулярному выражению
starts_with:a,bНачинается с одного из значений
ends_with:a,bЗаканчивается одним из значений
lowercaseВ нижнем регистре
uppercaseВ верхнем регистре

Управление потоком

ПравилоНазначение
bailОстановить проверку поля при первой ошибке
excludeИсключить из результата validated()

Практические советы

Несколько правил, которые упрощают повседневную работу с валидацией.

Для PATCH-запросов, где клиент отправляет только изменённые поля, комбинируйте sometimes с нужными правилами:

// UpdateProfileRequest
public function rules(): array
{
    return [
        'name' => 'sometimes|required|string|max:100',
        'email' => ['sometimes', 'required', 'email', Rule::unique('users')->ignore($this->user()->id)],
        'bio' => 'sometimes|nullable|string|max:500',
    ];
}

Для полей, которые зависят от типа сущности, используйте required_if в сочетании с prohibited_if:

$request->validate([
    'type' => 'required|in:individual,company',
    'inn' => 'required_if:type,company|prohibited_if:type,individual|string|size:10',
    'passport' => 'required_if:type,individual|prohibited_if:type,company|string',
]);

bail разумно ставить перед правилами, выполняющими запросы к БД или внешним сервисам:

$request->validate([
    'email' => 'bail|required|email:rfc|unique:users',
    'invite_code' => 'bail|required|string|exists:invites,code',
]);

Если правила растут и перестают помещаться в контроллере, переносите их в Form Request. А когда встроенных правил не хватает, создавайте собственные. Работа с текстами ошибок разобрана в отдельном справочнике, а быстрый старт поможет разобраться с основами.