Формы и валидация данных

Рейтинг: 61% · 6 голосов
Курс по Laravel: маршруты, Eloquent, Blade, миграции, очереди и API. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
oleg_php
Сообщения: 25
Зарегистрирован: 14 май 2026, 08:06

Формы и валидация данных

Сообщение oleg_php »

АкадемияLaravel с нуляГлава 7 из 18
Оглавление курса (18)
  1. Знакомство с Laravel и установка окружения
  2. Маршруты и контроллеры
  3. Blade: шаблоны и вёрстка страниц
  4. Миграции и структура базы данных
  5. Eloquent ORM: модели и CRUD
  6. Связи в Eloquent: hasMany, belongsTo и другие
  7. Формы и валидация данных (вы здесь)
  8. Аутентификация пользователей
  9. Middleware и защита маршрутов
  10. Очереди и фоновые задачи
  11. Отправка почты и уведомления
  12. Строим REST API на Laravel
  13. Авторизация: Gates и Policies
  14. Работа с файлами: загрузка, Storage, диски local и S3
  15. Тестирование: Pest и PHPUnit, фабрики, сидеры, RefreshDatabase
  16. Сервис-контейнер, провайдеры, свои artisan-команды и планировщик
  17. Деплой в продакшен и обзор современного фронтенда (Vite, Livewire, Inertia)
  18. События и слушатели, кеширование, логирование
Данным из формы доверять нельзя: их отправляет чужой браузер, и прилететь оттуда может что угодно. В этой главе соберём форму создания поста для блога, который ведём с пятой главы, защитим её от CSRF и научимся проверять ввод: сначала прямо в контроллере, потом через отдельный класс Form Request.

Форма и CSRF:

За защиту от CSRF для маршрутов из группы web (всё, что лежит в routes/web.php) отвечает middleware Illuminate\Foundation\Http\Middleware\PreventRequestForgery. Если в статьях встретите имя ValidateCsrfToken, это он же: так класс назывался в Laravel 11 и 12, а в 13-й версии старое имя оставлено пустым классом-алиасом с пометкой @deprecated. Проверка идёт в два шага. Сначала middleware смотрит заголовок Sec-Fetch-Site, который современные браузеры выставляют сами: запрос со значением same-origin проходит без токена вообще. Если заголовка нет (curl, старые браузеры, часть HTTP-клиентов) или запрос пришёл с чужого сайта, нужен валидный CSRF-токен, иначе Laravel ответит ошибкой 419. На API-маршруты проверка не распространяется, а отдельные URI можно вывести из-под неё через preventRequestForgery(except: [...]) в bootstrap/app.php. Старый метод validateCsrfTokens(except: [...]) из 11/12 ещё работает, но в 13-й помечен @deprecated и просто оборачивает preventRequestForgery, в новом коде используйте актуальное имя. Токен добавляет директива @csrf, она рендерит скрытое поле _token. И да, @csrf по-прежнему ставим в каждую форму: токен нужен, чтобы легитимные клиенты без заголовка Sec-Fetch-Site не ловили 419. Создаём resources/views/posts/create.blade.php:

Код: Выделить всё

<form method="POST" action="{{ route('posts.store') }}">
    @csrf

    <input type="text" name="title" value="{{ old('title') }}" placeholder="Заголовок">
    @error('title') <p class="error">{{ $message }}</p> @enderror

    <textarea name="body" placeholder="Текст поста">{{ old('body') }}</textarea>
    @error('body') <p class="error">{{ $message }}</p> @enderror

    <input type="date" name="published_at" value="{{ old('published_at') }}">
    @error('published_at') <p class="error">{{ $message }}</p> @enderror

    <button type="submit">Опубликовать</button>
</form>
Здесь две новые вещи. Хелпер old('title') возвращает то, что пользователь ввёл в прошлый раз. Без него после ошибки валидации форма очистится, и человек, набравший длинный текст, закроет вкладку и не вернётся. Директива @error('title') выводит первое сообщение об ошибке для поля. Переменная $errors со всеми ошибками доступна в любом шаблоне, отдельно передавать её из контроллера не нужно.

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

Маршруты posts.create и posts.store вы напишете сами, это материал второй главы. Метод store в PostController:

Код: Выделить всё

public function store(Request $request)
{
    $validated = $request->validate([
        'title' => ['required', 'string', 'max:255'],
        'body' => ['required', 'string', 'min:50'],
        'published_at' => ['nullable', 'date'],
    ]);

    Post::create($validated);

    return redirect()->route('posts.index')->with('status', 'Пост сохранён');
}
Если проверка провалилась, validate выбрасывает исключение, и Laravel сам редиректит назад, положив ошибки и введённые данные во flash-сессию. До строки Post::create выполнение просто не дойдёт. Так это работает для обычных HTML-форм. Если же запрос ожидает JSON (заголовок Accept: application/json, так шлют fetch и SPA-фронтенды), редиректа не будет: Laravel вернёт статус 422 и положит ошибки в тело JSON-ответа, в поле errors. Если проверка прошла, validate возвращает массив только из проверенных полей. Именно поэтому в create уходит $validated, а не $request->all(): в all() лежит всё, что прислал браузер, включая поля, дописанные через devtools.

Form Request:

Когда правил десяток и больше, контроллер распухает. Выносим проверку в класс командой

Код: Выделить всё

php artisan make:request StorePostRequest
, файл появится в app/Http/Requests:

Код: Выделить всё

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body' => ['required', 'string', 'min:50'],
            'published_at' => ['nullable', 'date'],
        ];
    }

    public function messages(): array
    {
        return [
            'body.min' => 'Текст слишком короткий, нужно хотя бы :min символов.',
        ];
    }
}
В контроллере меняем тип аргумента с Request на StorePostRequest, валидация отработает ещё до входа в метод store, а данные забираем через $request->validated(). Метод authorize отвечает, имеет ли пользователь право на сам запрос. Пока возвращаем true, после восьмой главы про аутентификацию сюда встанет проверка владельца.

Сообщения по умолчанию на английском. Поставьте пакет переводов:

Код: Выделить всё

composer require laravel-lang/common --dev
, затем

Код: Выделить всё

php artisan lang:add ru
и пропишите APP_LOCALE=ru в .env. Точечные правки, как body.min в примере выше, делаются через метод messages.

Ручная валидация и свои правила:

Иногда validate не подходит: при ошибке нужно не редиректить, а сделать что-то своё, залогировать, ответить нестандартно. Тогда собираем валидатор руками:

Код: Выделить всё

use Illuminate\Support\Facades\Validator;

$validator = Validator::make($request->all(), [
    'title' => ['required', 'string', 'max:255'],
    'body' => ['required', 'string', 'min:50'],
]);

if ($validator->fails()) {
    // свой сценарий: лог, кастомный ответ, другой редирект
}

$validated = $validator->validated();
Когда встроенных правил не хватает, пишем своё. Быстрый вариант, замыкание прямо в массиве правил:

Код: Выделить всё

'title' => ['required', 'string', 'max:255', function ($attribute, $value, $fail) {
    if (str_contains(mb_strtolower($value), 'спам')) {
        $fail('Заголовок содержит запрещённое слово.');
    }
}],
Если правило нужно в нескольких формах, выносим его в класс:

Код: Выделить всё

php artisan make:rule NoSpamWords
создаст заготовку в app/Rules с методом validate, дальше просто кладём new NoSpamWords в массив правил.

Ещё два инструмента управления проверкой. Правило bail останавливает проверку конкретного поля на первой ошибке: в ['bail', 'required', 'string', 'max:255'] при провале required правила string и max уже не запустятся. А метод stopOnFirstFailure() на валидаторе (или свойство $stopOnFirstFailure = true в Form Request) обрывает всю проверку целиком после первой ошибки любого поля.

В шаблоне, кроме @error по одному полю, можно вывести все ошибки списком:

Код: Выделить всё

@if ($errors->any())
    <ul>
        @foreach ($errors->all() as $error)
            <li>{{ $error }}</li>
        @endforeach
    </ul>
@endif
Типичные грабли:

Ошибка 419 Page Expired при отправке. В Laravel 13 из обычного браузера на своём же домене её почти не увидеть: same-origin запрос проходит по заголовку Sec-Fetch-Site даже с забытым @csrf. Ловится 419 там, где заголовка нет или запрос cross-site: curl и HTTP-клиенты, старые браузеры, отправка с чужого домена. Для таких клиентов причины классические: забытый @csrf или страница, провисевшая открытой дольше жизни сессии (токен протух). На Laravel 11/12 заголовок не проверяется, и там 419 это по-прежнему почти всегда забытый @csrf.

Пустое необязательное поле роняет валидацию. Браузер шлёт пустую строку, middleware ConvertEmptyStringsToNull превращает её в null, и правило date на null падает. Лекарство: добавить nullable первым правилом, как у published_at выше.

Невыбранный чекбокс браузер не отправляет вовсе, а выбранный без явного value уходит со значением "on". Правило required для него бессмысленно, а связка ['sometimes', 'boolean'] на "on" провалится: boolean принимает только true, false, 1, 0, "1" и "0". Рабочих варианта три. Первый: задать в разметке value="1", тогда ['sometimes', 'boolean'] отработает. Второй: для чекбокса, который обязан быть отмечен (согласие с правилами), взять правило accepted, оно понимает и "on", и "yes". Третий: нормализовать значение в Form Request до запуска проверки:

Код: Выделить всё

protected function prepareForValidation(): void
{
    $this->merge(['is_draft' => $this->boolean('is_draft')]);
}
В контроллере значение удобно читать через $request->boolean('is_draft'), этот хелпер тоже считает "on" за true.

MassAssignmentException при сохранении. Значит, в модели Post не заполнен $fillable, вернитесь к пятой главе.

Что усвоили:

Форма с @csrf и old(), validate в контроллере, Form Request для крупных форм, @error и $errors в шаблоне, validated() вместо all(), а для нестандартных случаев Validator::make и собственные правила. На этом фундаменте стоит вся восьмая глава: логин и регистрация, это те же формы с той же валидацией.
👍4 ❤️ 🔥 😄 🤔
Аватара пользователя
tonka1
Сообщения: 2
Зарегистрирован: 11 май 2026, 00:55

Re: Формы и валидация данных

Сообщение tonka1 »

oleg_php писал(а):Правило required для него бессмысленно
а как тогда сделать обязательную галочку "согласен с правилами"? без required юзер ее просто не поставит и форма спокойно пройдет, получается тупик какой-то
👍2 ❤️2 🔥 😄 🤔
Аватара пользователя
akiya
Сообщения: 3
Зарегистрирован: 14 май 2026, 06:12

Re: Формы и валидация данных

Сообщение akiya »

выше про галочку спрашивали, для этого есть правило accepted, оно специально под согласия с условиями. отсутствие поля оно само считает провалом, required не нужен. мы так в проде согласие на обработку персданных проверяем
👍2 ❤️2 🔥 😄 🤔
Аватара пользователя
mybenz
Сообщения: 2
Зарегистрирован: 15 май 2026, 07:22

Re: Формы и валидация данных

Сообщение mybenz »

спасибо за пункт про nullable, на прошлой неделе убил вечер ровно на этом. поле даты пустое, а валидатор ругается must be a valid date. оказалось как раз этот ConvertEmptyStringsToNull, добавил nullable и все взлетело
👍1 ❤️ 🔥 😄 🤔
Аватара пользователя
tdf1175
Сообщения: 3
Зарегистрирован: 14 май 2026, 02:04

Re: Формы и валидация данных

Сообщение tdf1175 »

а картинку к посту как прикрутить? в rules что-то типа image и max по килобайтам писать? и вроде форме еще enctype=multipart/form-data нужен, без него файл не уходит. было бы здорово хоть пару слов про это
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Связи в Eloquent: hasMany, belongsTo и другие
Следующая глава →
Аутентификация пользователей

Все главы курса «Laravel с нуля»

Поделиться темой: ✈ Telegram VK

Вернуться в «Laravel с нуля»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость