data_fill
Write a nested value only where one is missing, leaving existing values untouched.
data_fill(mixed &$target, string|array $key, mixed $value): mixedParameters
| Parameter | Type | Description |
|---|---|---|
$target | mixed | The array or object to write into, passed by reference and mutated in place. |
$key | string|array | The dot-notation path to fill. An * segment fills every element at that level that is currently missing the key. |
$value | mixed | The value to store wherever the path does not already resolve. |
Return value
mixedThe modified target. The target is also mutated in place because it is taken by reference.
data_fill() is data_set() with overwrite permanently pinned to false. It writes the value at the given dot path only where nothing is there yet, and leaves every existing value alone. Like data_set(), it takes the target by reference and mutates it in place.
When to reach for it
Use it to backfill defaults into data you did not create — a decoded payload, a config array assembled from several sources, a row that only sometimes carries an optional field. The point is that repeated calls are idempotent: running the same fill twice changes nothing on the second pass.
Wildcards
An * segment fills each element at that level that is missing the key, and skips the ones that already have it. This is the concise way to give every row in a list a default without a loop and an isset() check.
Presence, not truthiness
"Missing" means the key does not exist. It does not mean empty, falsy or blank. A key holding null, '', 0 or false already exists and is therefore never filled — that is the behaviour that surprises people who expected it to work like ??= or blank().
Example
When to use it
- Backfill default fields into a decoded webhook payload before validating it.
- Give every row in a nested list a default attribute with a single wildcard call.
- Merge fallback settings into a config array without clobbering values already set.
Frequently asked questions
What is the difference between data_fill and data_set?
data_fill never overwrites; it is exactly data_set($target, $key, $value, overwrite: false). Use data_set when you want the value written unconditionally, and data_fill when you are supplying a default.
Why did data_fill not replace my null value?
Because the key already exists, and data_fill only writes to paths that do not resolve at all. Null, empty string, 0 and false all count as present — check with blank() and call data_set() yourself if you want those replaced.
Can data_fill create keys that do not exist yet on a list?
Yes for ordinary path segments, which are created as needed, but a * wildcard only visits elements that already exist. Filling 'items.*.qty' on an empty items array writes nothing.
More examples
Filling one key across a list
Desk 1 keeps its real price of 100 while Desk 2 receives the default, and both pick up the currency because neither had one. One call replaces a foreach with two isset() guards.
<?php
$catalog = [
'products' => [
['name' => 'Desk 1', 'price' => 100],
['name' => 'Desk 2'],
],
];
data_fill($catalog, 'products.*.price', 0);
data_fill($catalog, 'products.*.currency', 'EUR');
print_r($catalog['products']);
/*
Array
(
[0] => Array ( [name] => Desk 1 [price] => 100 [currency] => EUR )
[1] => Array ( [name] => Desk 2 [price] => 0 [currency] => EUR )
)
*/Idempotent defaults
The user's explicit dark theme survives both passes while the two unset keys get their defaults. Because fills are idempotent, the loop is safe to run at several points in a request without tracking what has already been applied.
<?php
$settings = ['ui' => ['theme' => 'dark']];
$defaults = [
'ui.theme' => 'light',
'ui.density' => 'comfortable',
'ui.sidebar' => 'expanded',
];
foreach ($defaults as $path => $value) {
data_fill($settings, $path, $value);
}
// Running the same loop again changes nothing.
foreach ($defaults as $path => $value) {
data_fill($settings, $path, $value);
}
print_r($settings);
// Array ( [ui] => Array ( [theme] => dark [density] => comfortable [sidebar] => expanded ) )Normalising an inbound payload
Middleware is the natural home for this: every downstream validator and job can assume the optional fields exist, and any value the client did send is preserved untouched. Needs a booted application for the Request.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class NormalizeOrderPayload
{
public function handle(Request $request, Closure $next)
{
$payload = $request->all();
data_fill($payload, 'order.currency', 'EUR');
data_fill($payload, 'order.lines.*.quantity', 1);
data_fill($payload, 'meta.channel', 'api');
$request->replace($payload);
return $next($request);
}
}
Discussion