skip to content

In a Laravel controller that receives a form request, why should you persist $request->validated() rather than $request->all()?

level: seniorimportance: must knowfreq 70%

answer

  1. only keys that had rules
  2. unexpected keys like is_paid slip through all()
  3. second line of defence behind $fillable
  4. safe()->only() / except() / merge()
  5. #[FailOnUnknownFields] rejects extras (13.4)

basics

~20 s

validated() returns only the input keys that had rules and passed, so extra fields a client adds never reach create() or update(). all() returns everything sent, which turns any mass-assignment gap into a way to write columns the form never offered.

solid answer

~40 s

`$request->validated()` is built from the validator's rules: for each rule key present in the input, the value is copied into the result, and nothing else is. `$request->all()` returns every submitted key, including ones like `is_paid` or `user_id` that the form never rendered. Passing `all()` to `Registration::create()` makes `$fillable` the only barrier, and a model with `$guarded = []` has none. With `validated()`, an unknown key never reaches Eloquent. `safe()` returns a `ValidatedInput`, so `safe()->only(['ticket_type', 'dietary_notes'])` picks a subset and `safe()->merge(['user_id' => $request->user()->id])` adds server-set values. Since Laravel 13.4, `#[FailOnUnknownFields]` on the form request goes further and fails validation when an unexpected key is sent.

code

php · 22 lines
php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreRegistrationRequest;
use App\Models\Event;
use Illuminate\Http\RedirectResponse;

class RegistrationController extends Controller
{
    public function store(StoreRegistrationRequest $request, Event $event): RedirectResponse
    {
        // $request->all() could also carry is_paid=1 or user_id=42.
        $data = $request->safe()
            ->merge(['user_id' => $request->user()->id])
            ->all();

        $event->registrations()->create($data);

        return redirect()->route('events.show', $event);
    }
}

go deeper

for a junior

Remember that validated() returns only fields that have rules, while all() returns everything the client sent.

for a middle

Explain how validated() is built from the rule keys, and how safe()->only(), except() and merge() shape it.

for a senior

Show the mass-assignment failure when all() meets a broad $fillable or $guarded = [], and when to add #[FailOnUnknownFields].

for a principal

Set the team rule that only validated or explicitly server-set data reaches persistence, and decide whether APIs reject unknown fields globally.

## Two methods, two very different arrays On a resolved form request, two methods look interchangeable but are not: - **`$request->all()`** — every key in the query string, body and uploaded files, exactly as the client sent it. - **`$request->validated()`** — the result of the validator's own `validated()`, which walks the **rule keys** and copies only those present in the data. `validated()` also accepts a key and a default (`$request->validated('ticket_type')`), because the form request wraps the result in `data_get()`. The mechanism, simplified: 1. For each key in the rules (`ticket_type`, `dietary_notes`, `attendees.*.name`...), look the value up in the validated data. 2. If the key was present, copy it into the result; if it was missing, leave it out. 3. Keys that exist in the input but have **no rule** are never copied. So `validated()` is an allowlist derived from `rules()`. A field with no rule — even a harmless one — does not appear in it. ## The mass-assignment connection **Mass assignment** means passing an array straight to `create()`, `update()` or `fill()`. Eloquent protects against it with `$fillable` / `$guarded`, but that protection is a separate layer: | Model setup | `create($request->all())` with an extra `is_paid=1` | |---|---| | `$fillable` omits `is_paid` | `is_paid` is silently dropped (unless the app turned on `preventSilentlyDiscardingAttributes`) | | `$guarded = []` | `is_paid` is written — the client just marked their own ticket as paid | | `$fillable` lists `is_paid` because an admin form needs it | `is_paid` is written from the public form too | The third row is the realistic failure: a column becomes fillable for one legitimate path and every other path that passes `all()` inherits it. With `validated()`, the public registration request has no rule for `is_paid`, so the key never reaches Eloquent no matter how the model is configured. ## Shaping the validated data: safe() `safe()` returns an `Illuminate\Support\ValidatedInput`, which behaves like a small read-only view of the validated array: - `safe()->only(['ticket_type', 'dietary_notes'])` — a subset, as an array. - `safe()->except(['terms_accepted'])` — everything validated except confirmation-style fields that have no column. - `safe()->merge(['user_id' => $request->user()->id])` — add **server-decided** values without trusting input for them. - `safe()->collect()` — the validated data as a collection. - `safe()['ticket_type']` or iterating over `safe()` also work. `$request->safe(['ticket_type'])` is a shorthand that returns the same array as `safe()->only([...])`. ## Rejecting unknown fields outright (Laravel 13.4+) `validated()` quietly ignores extra keys. Laravel 13.4 added the `#[FailOnUnknownFields]` class attribute for form requests: any input key that does not match a rule key (wildcards and `_confirmation` companions are understood) becomes a validation error using the `prohibited` message. It can be enabled globally with `FormRequest::failOnUnknownFields()` in a service provider and switched off per class with `#[FailOnUnknownFields(false)]`. It is stricter, useful for APIs with published contracts, and still not a replacement for sensible `$fillable` settings. ## Traps that catch experienced developers - **Normalised fields without rules vanish.** If `prepareForValidation()` merges a computed `slug`, it only appears in `validated()` when `rules()` has a `slug` key. - **Missing optional fields are absent, not null.** An optional `dietary_notes` that was not sent is simply not in the array, so code reading `$data['dietary_notes']` needs a default. - **`input()` after validation is still raw input.** Reading `$request->input('ticket_type')` works, but mixing raw reads with validated writes invites the unvalidated key back in. - **`passedValidation()` replacements do not show up.** The validator holds its own copy of the data, so `replace()` or `merge()` in `passedValidation()` changes the request's input but not what `validated()` returns. ## How to explain it in one breath "`all()` is what the client sent; `validated()` is what my rules agreed to. Only the second may touch the database, and anything the server decides I add myself." Interviewers are listening for the allowlist idea and for the admission that `$fillable` alone is not enough once a column is fillable for some other path. ## Rule of thumb Treat `validated()` (or a `safe()` view of it) as the only array that may flow into persistence from a form request. Values the server decides — the owner, the event, a price — are added explicitly, never read from input.

  • A field merged in prepareForValidation() is missing from validated(). Why?
    `validated()` copies only keys that appear in `rules()`. Merging the value makes it part of the input, but without a rule for that key it is never collected. Add a rule for it, even a permissive one such as `['string']`, if it should be persisted.
  • What does #[FailOnUnknownFields] add on top of validated()?
    `validated()` ignores unexpected keys; the attribute, added in Laravel 13.4, turns each input key without a matching rule into a validation error, so the client learns the payload was wrong. It understands wildcard rule keys and `_confirmation` companions.
  • Is $request->safe()->only(['a']) different from $request->only(['a'])?
    Yes. `safe()->only()` selects from the validated array, so a key without a rule is never returned. `$request->only()` selects from raw input, so it returns whatever the client sent under that key, validated or not.

saying these in an interview costs you the question

  • validated() and all() return the same array once validation has passed
  • $guarded = [] is safe as long as the form request validated the data
  • validated() includes every input key, just with values checked
  • A field merged in prepareForValidation() always appears in validated()
  • Optional fields that were not sent appear in validated() as null