In Laravel, what is the difference between $request->merge() and mergeIfMissing(), and why can mergeIfMissing() leave a blank field unset?
answer
- writes into the input source bag
- merge overwrites, mergeIfMissing does not
- missing means absent, not empty
- dot keys set nested values
basics
~10 smerge() writes keys into the request's input, overwriting existing values; mergeIfMissing() writes only keys that are absent. A field submitted blank is present as null, so mergeIfMissing() does not replace it with the default.
solid answer
~30 s`$request->merge(['sort' => 'newest'])` sets values in the request's **input source**: the JSON payload for JSON requests, the query bag for `GET`/`HEAD`, otherwise the form body. Existing keys are overwritten, and dot keys such as `filters.remote` set nested values. `mergeIfMissing()` filters the array with `$request->missing($key)` first, so only absent keys are written. Because `ConvertEmptyStringsToNull` turns a blank `sort=` into a present `null`, `mergeIfMissing(['sort' => 'newest'])` leaves it `null`. When blank should also get the default, merge conditionally on `! $request->filled('sort')`. Every later reader, including `input()`, validation and downstream middleware, sees the merged values.
code
php · 19 lines<?php
use Illuminate\Http\Request;
// GET /jobs?sort=&q=laravel
public function index(Request $request)
{
$request->mergeIfMissing(['sort' => 'newest']); // no-op: sort is present as null
if (! $request->filled('sort')) { // absent OR blank
$request->merge(['sort' => 'newest']);
}
if ($request->has('q')) { // legacy parameter name
$request->merge(['keyword' => $request->input('q')]);
}
return $request->only(['keyword', 'sort']); // ['keyword' => 'laravel', 'sort' => 'newest']
}go deeper
Remember that merge() adds or overwrites input values and mergeIfMissing() only adds keys the request does not have.
Explain which bag merge() writes to, why a blank field counts as present after ConvertEmptyStringsToNull, and how to default blanks with filled().
Place normalisation in middleware or before validation deliberately, and avoid merging server-derived values that later code mistakes for client input.
Set a team convention for defaults and normalisation, choosing between request mutation, typed readers and data objects so behaviour stays predictable.
## Mutating the request's input Most code only reads a request, but `Illuminate\Http\Request` also lets you **add or override input** before later code runs. The two methods interviewers ask about are `merge()` and `mergeIfMissing()`. Typical uses on a job-board search endpoint are filling in a default sort order, normalising a legacy parameter name, or adding a server-derived value before validation. ## What merge() actually changes `merge(array $input)` does not keep a separate "extra input" array. It reads the current **input source**, applies each key with `Arr::set`, and replaces the source bag with the result. The input source depends on the request: | Request | Input source that merge() writes to | |---|---| | `Content-Type` contains `/json` or `+json` | the decoded JSON payload | | `GET` or `HEAD` | the query bag | | any other form request (`POST`, `PUT`...) | the form body bag | Consequences worth naming: - **Overwrites existing keys.** `merge(['sort' => 'newest'])` replaces whatever the client sent. - **Dot keys set nested values.** `merge(['filters.remote' => true])` produces `filters[remote]`. - **The change is visible to every later reader.** `input()`, `all()`, `only()`, `has()`, validation and any downstream middleware see the merged value, because they read the same bags. - **On a POST form, `query()` does not see it.** The value lands in the body bag, so `$request->query('sort')` still returns the URL value while `input('sort')` returns the merged one. - **It returns the request**, so calls chain. `replace(array $input)` is the blunt sibling: it replaces the entire input source with the given array. ## mergeIfMissing() and the blank-field trap `mergeIfMissing(array $input)` is implemented as a filtered `merge()`: keep only the pairs where `$this->missing($key)` is true, then merge those. `missing()` is the negation of `has()`, and `has()` is about **presence**, not emptiness. Walk through a search form: 1. The user opens `/jobs` with no `sort` parameter. `missing('sort')` is true, so `mergeIfMissing(['sort' => 'newest'])` sets it. 2. The user picks "Relevance", then clears the select to its blank option and resubmits `?sort=`. 3. `TrimStrings` and `ConvertEmptyStringsToNull` turn the blank into `null`, but the key is still present. 4. `missing('sort')` is false, so `mergeIfMissing()` does nothing, and the controller sorts by `null`. The fix depends on intent: - **"Default when absent or blank"**: `if (! $request->filled('sort')) { $request->merge(['sort' => 'newest']); }`. - **"Default only when never sent"**: `mergeIfMissing()` is exactly right, for example in PATCH-style APIs where a sent `null` is meaningful. - **"I just need a value here"**: skip mutation and read `$request->input('sort') ?? 'newest'`. ## Where merging belongs - **Middleware** is a natural home for cross-cutting normalisation, such as mapping a legacy `q` parameter to `keyword` for every search route. - **Just before validation** in a controller, when the rules should see a default. - **Form request classes** have their own pre-validation hook for this; it uses the same `merge()` underneath. Avoid merging values that later code will mistake for user input without saying so. A server-derived field such as `user_id` merged into the input can end up mass-assigned or logged as if the client sent it; passing it as a separate argument is often clearer. ## Interview checklist When asked to compare the two methods, a complete answer covers four points: 1. **Where the value goes**: into the input source bag for this request, so everything downstream sees it; nothing persists to the next request or the session. 2. **Overwrite semantics**: `merge()` always writes; `mergeIfMissing()` writes only absent keys. 3. **Presence versus emptiness**: `missing()` means absent, so blank fields are untouched by `mergeIfMissing()` once the default middleware has turned them into `null`. 4. **The alternative**: for a single read, `input('sort') ?? 'newest'` avoids mutating shared request state at all, which keeps controllers easier to reason about and to test.
- After $request->merge(['sort' => 'newest']) on a POST form, why does $request->query('sort') still return the URL value?For a non-JSON `POST`, the input source is the form body bag, so `merge()` writes there. `query()` reads only the query bag, which `merge()` did not touch. `input('sort')` returns `newest` because it reads the body first. On a `GET` request the input source is the query bag itself, so both would agree.
saying these in an interview costs you the question
- mergeIfMissing() fills fields that were submitted blank
- merge() keeps a separate array that only input() reads
- merge() never overwrites values the client sent
- Merged values are invisible to later validation