Validating a Request

$request->validate() checks input against a rules array, returns the data that passed, and fails into a redirect or a 422 response.

Syntax$validated = $request->validate(array $rules, array $messages = [], array $attributes = []);

Validation is the gate between a request and your code. $request->validate() takes an array of rules keyed by field name, runs them against the request, and returns an array of the data that passed. Only fields that had a rule come back, which is what makes the return value safer to hand to a model than the raw input.

What is doing the work

validate() is not declared on the Request class. Illuminate\Foundation\Providers\FoundationServiceProvider registers it as a macro that builds a validator from validator($this->all(), $rules, ...$params) and calls validate() on it. That call throws Illuminate\Validation\ValidationException as soon as any rule fails, so the lines after it run only on success.

A web request against an API one

You do not catch that exception. The framework's exception handler converts it, and picks the shape using $request->expectsJson():

  • No JSON expected: a 302 back to url()->previous(), with the submitted input flashed to the session and the messages flashed as an error bag.
  • JSON expected: a 422 whose body is message plus errors, where each field holds an array of messages.

The flashed messages reach templates as $errors because the web middleware group includes ShareErrorsFromSession. A route outside that group has no session, so nothing is flashed.

The trap

A client that does not send Accept: application/json is a browser as far as expectsJson() is concerned, even on a route in routes/api.php. A hand-written curl call then gets a 302 with an empty body. You can decide for yourself in bootstrap/app.php with $exceptions->shouldRenderJsonWhen(...) inside withExceptions().

Example

Example · php
<?php
// app/Http/Controllers/PostController.php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function store(Request $request): RedirectResponse
    {
        // Throws ValidationException if anything fails, so nothing below
        // this call needs an error branch. Returns the three keys listed
        // here and nothing else, whatever the form actually posted.
        $validated = $request->validate([
            'title' => ['required', 'string', 'max:120'],
            'body' => ['required', 'string', 'min:20'],
            'published_at' => ['nullable', 'date'],
        ]);

        $post = Post::create($validated);

        return redirect()
            ->route('posts.show', $post)
            ->with('status', 'Post created.');
    }
}

When to use it

  • Reject a bad post body in a small controller before writing a row.
  • Return field-level errors as 422 JSON to a JavaScript front end.
  • Check a query-string filter before using it to build a query.
  • Keep one endpoint's rules inline while the project is still small.

More examples

The longhand the macro runs for you

Validator::make() plus validate() is what the macro does. Reach for the longhand when you need the validator instance itself, for example to call fails() and return your own response, or to validate an array that did not come from a request at all.

Example · php
<?php
// app/Http/Controllers/PostController.php

declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class PostController extends Controller
{
    public function store(Request $request): RedirectResponse
    {
        $validator = Validator::make($request->all(), [
            'title' => ['required', 'string', 'max:120'],
        ]);

        // $request->validate($rules) is shorthand for exactly this pair
        // of lines: build the validator, then call validate() on it.
        //
        //     $validated = $validator->validate();
        //
        // fails() is the alternative when you want to build the response
        // yourself instead of letting the exception do it.
        if ($validator->fails()) {
            return back()
                ->withErrors($validator)   // flashes the messages
                ->withInput();             // flashes the old input
        }

        $validated = $validator->validated();

        return back();
    }
}

The 422 body an API client receives

Every value under errors is an array, even when there is one message, so client code can loop without a type check. message is built by ValidationException::summarize(): the first message plus a count of the rest. Treat message as text for a toast and errors as the per-field source of truth.

Example · json
{
    "message": "The title field is required. (and 1 more error)",
    "errors": {
        "title": [
            "The title field is required."
        ],
        "body": [
            "The body field must be at least 20 characters."
        ]
    }
}

Why the return value is safer than the input

Plain Arr work, no framework needed, to show the property that matters: is_admin was posted but is not in the result because no rule mentioned it. This is the reason to pass $validated to a model rather than $request->all().

Example · php
use Illuminate\Support\Arr;

$posted = ['title' => 'Hello', 'body' => 'World', 'is_admin' => '1'];
$rules = ['title' => ['required'], 'body' => ['required']];

// Roughly what validate() hands back: the keys that had rules.
$validated = Arr::only($posted, array_keys($rules));

print_r($validated);
// Output:
// Array
// (
//     [title] => Hello
//     [body] => World
// )

Discussion

  • Be the first to comment on this lesson.