0

Validation basics — $request->validate(), Validator::make()

Beginner5 min read·lv-11-001

Concept

Laravel's validation system has two primary entry points: $request->validate() on the incoming Illuminate\Http\Request object, and the static factory Validator::make() from the Illuminate\Validation\Factory class. Under the hood both produce the same Illuminate\Validation\Validator instance — the difference is purely ergonomic.

When you call $request->validate(['email' => 'required|email']), the Request trait ValidatesWhenResolvedTrait delegates to $this->getValidatorInstance(), which calls app('validator')->make($data, $rules). If validation fails, Validator::validate() throws an Illuminate\Validation\ValidationException, and the HTTP Kernel's exception handler converts that into a 422 Unprocessable Entity response (JSON for API requests, a redirect with flashed errors for HTML forms).

Validator::make() is the manual path. It gives you a Validator instance you can interrogate before deciding what to do: $v->fails(), $v->errors() (an Illuminate\Support\MessageBag), or $v->validate() to throw on failure. This is the right choice when validation happens outside a controller, such as in a console command or a job.

The MessageBag returned by errors() is keyed by field name and supports dot-notation for nested fields. Each key maps to an array of human-readable error strings. You can retrieve all errors with $v->errors()->all(), or errors for a single field with $v->errors()->get('email'). This object is automatically bound into the view as $errors when validation fails on a form submission.

One important nuance: $request->validate() uses the request's current input as the data array, automatically merging route parameters via $request->all() plus $request->route()->parameters(). Validator::make() requires you to supply the data array explicitly, giving you the flexibility to validate arbitrary arrays — API payloads decoded from JSON, data read from a file, queued job payloads, and so on.

Code Example

php
<?php

namespace App\Http\Controllers;

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

class UserController extends Controller
{
    // Path 1: shorthand on Request — throws ValidationException automatically
    public function store(Request $request): \Illuminate\Http\RedirectResponse
    {
        $validated = $request->validate([
            'name'  => ['required', 'string', 'max:255'],
            'email' => ['required', 'email:rfc,dns', 'max:255'],
            'age'   => ['required', 'integer', 'min:18'],
        ]);

        // $validated only contains the keys declared in the rules array
        User::create($validated);

        return redirect()->route('users.index');
    }

    // Path 2: Validator::make() — manual control over failure handling
    public function apiStore(Request $request): \Illuminate\Http\JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'name'  => ['required', 'string', 'max:255'],
            'email' => ['required', 'email'],
        ]);

        if ($validator->fails()) {
            return response()->json([
                'errors' => $validator->errors(),
            ], 422);
        }

        $user = User::create($validator->validated());

        return response()->json($user, 201);
    }
}

Interview Q&A

Q: What is the difference between $request->validate() and Validator::make(), and when would you choose one over the other?

Both ultimately produce an Illuminate\Validation\Validator instance via the ValidationFactory. $request->validate() is a convenience wrapper that automatically uses request input as the data source and throws a ValidationException on failure, which the framework converts to the appropriate HTTP response (redirect with flashed errors for web, 422 JSON for API). Validator::make() gives you a raw Validator you can inspect with fails(), errors(), or validated() before deciding what to do. Choose Validator::make() when validation occurs outside the HTTP layer — in Artisan commands, queued jobs, or service classes that shouldn't depend on the request cycle.


Q: What does $request->validate() return, and how is the $validated array different from $request->all()?

$request->validate() returns only the keys that were declared in the rules array — this is called "safe" data. If the client sends extra fields not present in the rules (e.g., is_admin), they are silently dropped from the returned array. This behavior protects against mass-assignment exploits when you pass the validated data directly to Model::create(). $request->all() returns every key the client sent, including fields with no rules, so never pass it to create() without explicit $fillable protection on the model.


Q: How does Laravel convert a ValidationException into an HTTP response for API consumers?

ValidationException carries a $response property that, when null, triggers the framework's default rendering logic in Illuminate\Foundation\Exceptions\Handler::invalidJson(). The handler checks $request->expectsJson() (truthy when the Accept: application/json header is present or the route is in the api middleware group). If true, it returns a 422 JSON response with a message key summarising the error and an errors key containing the full MessageBag array. For non-JSON requests it returns a redirect to the previous URL with the errors and old input flashed to the session.