skip to content

In Laravel, how do $request->has(), filled() and missing() differ for a search field submitted empty or not at all?

level: middleimportance: must knowfreq 58%

answer

  1. presence versus non-empty value
  2. unchecked checkbox is never sent
  3. empty string becomes null by default
  4. missing() is the negation of has()
  5. whenFilled runs a closure with the value

basics

~20 s

has() is true when the key is present, even with an empty value; filled() also requires a non-blank value; missing() means the key is absent. A blank text field is present but not filled; an unchecked checkbox is missing.

solid answer

~40 s

`$request->has('location')` checks presence: the key exists in the input or uploaded files, whatever its value, and an array of keys requires all of them. `filled('location')` additionally requires a value that is not an empty string after trimming; `null` counts as empty, while booleans and arrays count as filled. `missing()` is simply `! has()`. A blank text input is sent as `location=`, the default `ConvertEmptyStringsToNull` middleware makes it `null`, so it is present but not filled. An unchecked checkbox is not sent at all, so it is missing. For optional search filters the right test is usually `filled()` or `whenFilled('location', fn ($v) => ...)`.

code

php · 23 lines
php
<?php

use App\Models\Job;
use Illuminate\Http\Request;

public function index(Request $request)
{
    // keyword=php&location=&filters[salary_min]=   (remote checkbox unticked)
    $jobs = Job::query();

    $request->whenFilled('keyword', fn (string $kw) => $jobs->where('title', 'like', "%{$kw}%"));
    $request->whenFilled('location', fn (string $loc) => $jobs->where('location', $loc)); // skipped: null

    if ($request->has('remote')) {          // false: an unchecked box is never sent
        $jobs->where('remote', true);
    }

    if ($request->filled('filters.salary_min')) {   // dot path into filters[salary_min]
        $jobs->where('salary_max', '>=', $request->integer('filters.salary_min'));
    }

    return view('jobs.index', ['jobs' => $jobs->paginate()]);
}

go deeper

for a junior

Remember the three answers: has() means sent, filled() means sent with a non-blank value, missing() means not sent. Unticked checkboxes are never sent.

for a middle

Explain how TrimStrings and ConvertEmptyStringsToNull turn blanks into present nulls, why that keeps has() true, and how whenFilled and whenHas return values.

for a senior

Use presence deliberately in PATCH-style endpoints, where a sent null means clear the column and an absent key means leave it, and catch filters that silently match nothing.

for a principal

Push teams toward declarative presence rules in validation and typed readers, so branching on has() and filled() stays rare and consistent across controllers.

## Presence and emptiness are different questions Every optional filter on a search form raises two questions: *did the client send this key at all?* and *did it send a usable value?* Laravel's `Illuminate\Http\Request` answers them with separate methods, inherited from the `InteractsWithData` trait. Confusing them is the classic source of filters that apply when the user left a field blank, or never apply when they should. Picture a job-board form with a `keyword` text input, a `location` text input, a `remote` checkbox and a `filters[salary_min]` number field. The user types "php", leaves location blank and does not tick remote. The browser submits `keyword=php&location=&filters[salary_min]=` and **no `remote` key at all**, because HTML never submits an unchecked checkbox. ## What each method returns | Call | `keyword=php` | `location=` (blank) | `remote` (unchecked) | |---|---|---|---| | `has()` | true | true | false | | `filled()` | true | false | false | | `missing()` | false | false | true | | `isNotFilled()` | false | true | true | The mechanics behind the table: - **`has($key)`** runs `Arr::has` against `all()`, the input merged with uploaded files, so it follows dot paths such as `filters.salary_min`. Passed an array or several arguments, it requires *every* key. - **`hasAny($keys)`** is true when at least one key is present. - **`filled($key)`** treats a value as empty when it is not a boolean, not an array, and `trim((string) $value) === ''`. So `null`, `''` and `' '` are empty; `false` and `[]` are filled. Several keys require all to be filled. - **`anyFilled($keys)`** and **`isNotFilled($keys)`** cover the "at least one" and "all empty or missing" cases. - **`missing($key)`** is exactly `! has($key)`; it says nothing about emptiness. ## Why the default middleware matters Laravel 13's global middleware stack includes `TrimStrings` and `ConvertEmptyStringsToNull`. The blank `location` therefore arrives as `null`, and whitespace-only input is trimmed to `''` and then converted to `null`. None of that removes the key, so `has('location')` stays true. Developers who expect `has()` to mean "the user typed something" get a filter like `WHERE location = NULL` that matches nothing. ## The conditional helpers Each check has a callback form that keeps controllers flat: 1. `whenHas($key, $callback, $default = null)` runs the callback with the value when the key is present. 2. `whenFilled($key, $callback, $default = null)` runs it only when the value is non-empty. 3. `whenMissing($key, $callback, $default = null)` runs it when the key is absent. The optional second closure runs in the opposite case. Each returns the callback's result, or the request itself when that result is falsy or when no closure ran, so several calls can be chained on `$request`. ## Choosing the check for a search form - **Text filters** (`keyword`, `location`): use `filled()` or `whenFilled()`; blank means "no filter". - **Checkboxes** (`remote`): presence is the signal, so `has()` works, but `$request->boolean('remote')` is clearer because it also handles a hidden `0` fallback input placed before the checkbox. - **Required-together fields** (`salary_min` and `salary_max`): `filled(['filters.salary_min', 'filters.salary_max'])` checks both in one call. - **Distinguishing "cleared" from "never sent"**: when a PATCH endpoint must null a column only if the client sent the key, use `has()` for that decision and read the value with `input()`. These methods do not validate. Rules such as `required`, `sometimes` and `nullable` express the same presence-versus-emptiness distinction declaratively, and belong in the validation layer; `has()` and `filled()` are for branching in code after or instead of validation. ## Common interview traps Interviewers like to probe the edges with short scenarios: 1. **"The user typed three spaces."** `TrimStrings` reduces it to `''`, `ConvertEmptyStringsToNull` makes it `null`, and `filled()` is false. Even with those middleware disabled, `filled()` trims before comparing, so the answer is the same. 2. **"The client sent `remote=0`."** The key is present and `'0'` is not a blank string, so `has()` and `filled()` are both true; only `boolean('remote')` tells you it means "no". 3. **"The form has no fields at all."** `has()` returns false for any key, because `Arr::has` returns false on an empty array before looking. 4. **"A file input named `cv`."** `has('cv')` is true when a file was uploaded, since `has()` checks `all()`, which merges uploaded files into the input.

  • A PATCH endpoint should set a job's location to NULL only when the client explicitly sends location: null. Which check do you use?
    `has('location')`, not `filled()`. The client signals intent by sending the key, and `has()` is true for a present `null`. `filled()` would be false for that value and wrongly skip the update, while a request that omits the key entirely returns `missing('location')` true and leaves the column untouched.
  • Why does $request->filled('tags') return true when tags is sent as an empty array?
    `filled()` only treats a value as empty when it is not a boolean, not an array, and trims to an empty string. An array, even `[]`, therefore counts as filled. To check for at least one element, read `$request->array('tags')` and test `count()`, or validate with a rule that requires a non-empty array.

saying these in an interview costs you the question

  • has() returns false when a text field was left blank
  • An unchecked checkbox is submitted with an empty value
  • missing() is true when a field is present but empty
  • filled() treats a whitespace-only value as filled
  • has(['a', 'b']) is true if either key is present