data_set
Write a value into a nested array or object by dot path, creating missing levels.
data_set(mixed &$target, string|array $key, mixed $value, bool $overwrite = true): 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 write. An * segment applies the write to every element at that level. |
$value | mixed | The value to store at the resolved path. |
$overwriteoptional | bool= true | When false, an existing value at the path is left untouched and only missing paths are written. |
Return value
mixedThe modified target. The target is also mutated in place because it is taken by reference.
data_set() writes into a nested structure by dot path and creates any missing intermediate levels on the way. The target is taken by reference, so the variable you pass is mutated; the return value is the same structure and is usually ignored.
Creating versus overwriting
By default an existing value is replaced. Pass overwrite: false and the write becomes a no-op wherever something already lives — which is precisely what data_fill() does with a shorter signature.
Wildcards
An * segment applies the write to every element that already exists at that level. It never creates elements: products.*.price stamps a price onto each product currently in the array, but on an empty products array it writes nothing.
Objects get array children
Dot paths traverse public object properties happily, but any level that data_set() has to create is created as an array, never as an object. Setting meta.color on a fresh stdClass gives you $obj->meta['color'], not $obj->meta->color. If you need object children, build them yourself before writing.
When to reach for it
Use it for patching decoded payloads, building config-shaped arrays from flat input, or normalising records before validation — anywhere the alternative is a stack of isset() guards.
Example
When to use it
- Patch one field deep inside a decoded JSON payload before re-encoding it.
- Build a nested config array from flat dot-keyed form input.
- Stamp a default onto every row of a nested list with a * wildcard.
Frequently asked questions
Does data_set modify the original array or return a copy?
It modifies the original — the target is declared as a reference parameter, so the variable you pass is mutated in place. The return value is that same structure, so assigning it back is harmless but unnecessary.
How do I set a value only if the key does not already exist?
Pass false as the fourth argument, ideally as the named argument overwrite: false. data_fill() is the same operation with a shorter signature, so use whichever reads better at the call site.
Why did my wildcard data_set not write anything?
Because the level the wildcard targets was empty or missing — a * segment only visits elements that already exist and never creates them. Seed the list first, then run the wildcard write.
More examples
Wildcard writes across a list
The wildcard visits only elements that already exist. The second half is the trap: no error is raised on an empty level, the write is simply silently skipped.
<?php
$catalog = [
'products' => [
['name' => 'Desk 1', 'price' => 100],
['name' => 'Desk 2', 'price' => 150],
],
];
data_set($catalog, 'products.*.currency', 'EUR');
data_set($catalog, 'products.*.price', 0);
print_r($catalog['products'][1]);
// Array ( [name] => Desk 2 [price] => 0 [currency] => EUR )
// A wildcard over an empty level writes nothing at all.
$empty = ['products' => []];
data_set($empty, 'products.*.price', 200);
var_dump($empty['products']);
// array(0) {}Building a structure from nothing
Every intermediate key is created as it is needed, which makes data_set a compact way to turn flat dot-keyed input (form fields, env-style keys) into a nested array.
<?php
$config = [];
data_set($config, 'services.mailer.transport', 'smtp');
data_set($config, 'services.mailer.port', 587);
data_set($config, 'services.queue.driver', 'redis');
print_r($config);
/*
Array
(
[services] => Array
(
[mailer] => Array ( [transport] => smtp [port] => 587 )
[queue] => Array ( [driver] => redis )
)
)
*/Patching a request payload in a controller
The two calls show the two modes side by side: an unconditional write for values the application owns, and overwrite: false for defaults the client is allowed to override. Needs a booted application for the Request and response helpers.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class WebhookController extends Controller
{
public function store(Request $request)
{
$payload = $request->all();
// Force a value we control, regardless of what was sent.
data_set($payload, 'meta.source', 'webhook');
// Only supply a currency if the sender omitted one.
data_set($payload, 'order.currency', 'EUR', overwrite: false);
return response()->json($payload);
}
}
Discussion