Reading Request Data Safely

input, query, boolean and date read the request with the type you want, and all() is the one that feeds mass assignment.

Syntax$request->input(string $key = null, mixed $default = null): mixed

Every accessor here reads the same request, but they disagree about where to look and what type to hand back. Choosing the right one removes a cast or a null check from the controller.

Where the value comes from

input('key') looks in the request body and the query string together, and accepts dot notation for nested keys, such as input('address.postcode'). query('key') reads the query string only, post('key') the body only. When both carry the same name, the body wins inside input(). all() returns the input merged with the uploaded files.

Typed accessors

These come from the InteractsWithData trait, shared with the ValidatedInput object that safe() returns.

CallReturns
string('name')A Stringable, so the Str methods chain off it.
integer('per_page')int, 0 by default.
boolean('active')true for "1", "true", "on" and "yes"; false for everything else, including "off" and absent.
date('from')A Carbon instance, or null when the key is not filled. A second argument is an expected format.

only(['title', 'body']) returns a subset and omits keys that were never sent rather than filling them with null. except() is the inverse.

Why all() is the dangerous one

Post::create($request->all()) hands every posted key to the model's fill(). The only thing stopping is_admin=1 from reaching the row is the model's $fillable list, and keys outside it are dropped in silence, so neither the attack nor your own typo announces itself. Pass validated() or safe()->only([...]) instead: a field with no rule cannot reach the model at all.

Example

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

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\Order;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;

class OrderReportController extends Controller
{
    public function index(Request $request): View
    {
        // Validate first, so date() is never handed an unparsable string.
        $request->validate([
            'from' => ['nullable', 'date'],
            'per_page' => ['nullable', 'integer', 'between:1,100'],
            'unpaid_only' => ['nullable', 'boolean'],
        ]);

        // Carbon or null, no parsing in the controller.
        $from = $request->date('from') ?? now()->subMonth();

        // int with a default, not the string "25".
        $perPage = $request->integer('per_page', 25);

        // "on" from an unchecked-or-checked box becomes a real bool.
        $unpaidOnly = $request->boolean('unpaid_only');

        $orders = Order::query()
            ->where('created_at', '>=', $from)
            ->when($unpaidOnly, fn ($query) => $query->whereNull('paid_at'))
            ->orderByDesc('created_at')
            ->limit($perPage)
            ->get();

        return view('reports.orders', [
            'orders' => $orders,
            'from' => $from,
        ]);
    }
}

When to use it

  • Read a filtered, paginated index page's query string without casting by hand.
  • Turn an HTML checkbox into a real boolean before storing it.
  • Keep an unexpected posted field out of an Eloquent create.
  • Pull one nested key out of a JSON body with dot notation.

More examples

Mass assignment, and the two lines that decide it

The contrast is the whole lesson. $fillable is a safety net, not a plan: it is silent, and it is edited by whoever adds the next column. Passing validated() makes the rules array the single list of what a request may write, and that list is reviewed every time someone changes validation. Model::preventSilentlyDiscardingAttributes() in a service provider turns the silent drop into an exception during development.

Example · php
<?php
// app/Models/Post.php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;

// Laravel 13 shorthand for the property below. Same list, same effect.
#[Fillable(['title', 'body', 'published_at'])]
class Post extends Model
{
    // The longhand:
    // protected $fillable = ['title', 'body', 'published_at'];
    //
    // Laravel's default is $guarded = ['*'] with an empty $fillable,
    // which means a model with neither set throws MassAssignmentException
    // on the first create(). is_admin or user_id reaching a row is
    // always a list that was too generous, never a missing default.
}

// In a controller:
//
// Everything posted goes to fill(). Extra keys are dropped without a
// word, so a column added to $fillable later becomes writable by anyone
// who can guess its name.
// Post::create($request->all());
//
// Only the keys that had rules exist here at all.
// Post::create($request->validated());
//
// Validated, minus the ones handled separately.
// Post::create($request->safe()->except(['tag_ids']));

The same string read four ways

Worth keeping nearby when writing a filtered index page. boolean() is the one that pays off most often, because an HTML checkbox sends "on" and sends nothing at all when unticked, and both cases come out as a correct bool. The query()/post() split matters when a form posts to a URL that also carries parameters, since input() lets the body shadow the query string.

Example · php
// Query string: ?from=2026-03-01&active=on&per_page=50&tags[]=php&tags[]=sql

$request->input('from');          // string "2026-03-01"
$request->date('from');           // Carbon instance for 2026-03-01
$request->date('from', 'Y-m-d');  // same, parsed with an exact format
$request->date('missing');        // null, because the key is not filled

$request->input('active');        // string "on"
$request->boolean('active');      // true
$request->boolean('off_switch');  // false, no exception, key absent

$request->integer('per_page', 25);          // int 50
$request->string('from')->before('-');      // Stringable "2026"
$request->collect('tags')->map('strtoupper'); // Collection of PHP, SQL

// Where it looks:
$request->query('from');  // query string only
$request->post('from');   // request body only
$request->input('from');  // body first, then the query string

only() omits what was not sent

This is why only() cannot be used to clear a field. A form that omits a key, as an unticked checkbox does, produces an array without that key, so an update built from only() leaves the old value in place. When an absent key has to mean false or null, add it yourself with mergeIfMissing() before validating, or read it with boolean().

Example · php
use Illuminate\Support\Arr;

$input = ['name' => 'Ada', 'role' => 'admin'];

// What $request->only(['name', 'email']) does to the same data:
// an absent key is left out, not set to null.
$subset = Arr::only($input, ['name', 'email']);

echo implode(', ', array_keys($subset)), "\n";
echo count($subset), "\n";
// Output:
// name
// 1

Discussion

  • Be the first to comment on this lesson.