Form Request: валидация в отдельном классе
Form Request выносит правила валидации и авторизацию в отдельный класс, оставляя контроллеры тонкими и переиспользуя логику проверки.
Суть: класс-наследник
FormRequestсодержит методrules()с правилами иauthorize()с проверкой прав. Достаточно указать его типом в методе контроллера — валидация выполнится сама.
Когда форма усложняется, правила валидации в контроллере разрастаются и начинают повторяться в методах store и update. Form Request решает это: он переносит правила и авторизацию в отдельный, специально предназначенный класс. Контроллер становится чистым, а правила — переиспользуемыми и удобными для тестирования.
Магия в том, что вам не нужно вручную вызывать валидацию. Достаточно объявить Form Request как тип аргумента метода контроллера — Laravel автоматически создаст его, проверит права через authorize(), прогонит правила из rules() и, при успехе, отдаст вам уже проверенные данные.
Создание Form Request
php artisan make:request StoreProductRequest<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreProductRequest extends FormRequest
{
// проверка прав: может ли пользователь делать этот запрос
public function authorize(): bool
{
return $this->user()?->is_admin ?? false;
}
// правила валидации
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'price' => 'required|numeric|min:0',
];
}
// свои тексты ошибок (необязательно)
public function messages(): array
{
return ['name.required' => 'Укажите название товара'];
}
}Контроллер становится предельно чистым — никакой валидации в теле метода:
<?php
use App\Http\Requests\StoreProductRequest;
public function store(StoreProductRequest $request)
{
// сюда попадём только если authorize() = true
// и все правила rules() пройдены
Product::create($request->validated());
return redirect('/products');
}Как работает под капотом
Когда Laravel видит в сигнатуре метода тип StoreProductRequest, он через контейнер создаёт объект и ещё до входа в метод запускает проверку. Сначала вызывается authorize(): если он вернул false — ответ 403, метод не выполняется. Затем прогоняются rules(): при ошибке — редирект назад (как у обычного validate). И только если оба этапа пройдены, ваш код начинает работать.
Запрос -> метод store(StoreProductRequest $r)
|
1. authorize() --false--> 403 Forbidden
| true
2. rules() --ошибки--> redirect + $errors
| ок
3. тело контроллера ($r->validated())
Смоделируем двухэтапную проверку «авторизация, затем правила» на Python.
Попробуй сам ▶
# Form Request: сначала authorize(), потом rules()
def authorize(user):
return user.get('is_admin', False)
def rules(data):
errors = {}
if not data.get('name'):
errors['name'] = 'обязательно'
return errors
def handle(user, data):
if not authorize(user):
return '403 Forbidden'
errors = rules(data)
if errors:
return f'redirect + ошибки: {errors}'
return '200 OK — товар создан'
print(handle({'is_admin': False}, {'name': 'Книга'}))
print(handle({'is_admin': True}, {'name': ''}))
print(handle({'is_admin': True}, {'name': 'Книга'}))
Частые ошибки
- Оставить
authorize()сreturn false. Сгенерированный класс по умолчанию запрещает всё — не забудьте задать логику или вернутьtrue. - Снова валидировать в контроллере. Form Request уже всё проверил — дублировать не нужно.
- Использовать
$request->all()вместоvalidated(). Это теряет смысл проверки и открывает mass assignment.
Best practices
- Заводите отдельные Form Request на создание и обновление (
Store...иUpdate...). - Берите данные только через
$request->validated(). - Сложную авторизацию выносите в политики (Policy) и вызывайте из
authorize().
Итог: Form Request инкапсулирует правила и авторизацию, делая контроллеры тонкими и переиспользуя проверки. Дальше переходим к большой теме — аутентификации пользователей.