data_get
Read a nested value from an array or object using dot notation, with a default.
data_get(mixed $target, string|array|int|null $key, mixed $default = null): mixedParameters
| Parameter | Type | Description |
|---|---|---|
$target | mixed | The array, object, Collection or ArrayAccess value to read from. |
$key | string|array|int|null | The dot-notation path. Segments may be array keys, object properties, an * wildcard, or the {first} / {last} placeholders. Passing null returns the target unchanged. |
$defaultoptional | mixed= null | Returned when the path does not resolve. If it is a closure it is invoked lazily through value(). |
Return value
mixedThe value at the given path, or the default when the path does not resolve.
Changelog
- PHP 11.0The {first} and {last} path placeholders were added. On 10.x and earlier those segments are treated as literal keys and will not match.
data_get() walks a dot path through whatever it is handed — nested arrays, public object properties, Collections, ArrayAccess objects, or a mix of all of them in the same path. If any segment is missing it stops and returns the default instead of emitting a warning.
Why not Arr::get()
Arr::get() only traverses arrays and ArrayAccess. data_get() is the one to use when the shape is not guaranteed to be all arrays — decoded JSON that may hand you stdClass, a mix of models and arrays, or third-party payloads.
Wildcards and placeholders
An * segment collects the matching value from every element at that level and always returns an array, even when a single element matches. {first} and {last} pick one element instead, so they return a scalar.
The default is lazy
The third argument is passed through value(), so a closure is only invoked when the path actually misses. That makes an expensive fallback cheap to supply.
Null is a value
The default only applies when the path does not resolve. A key that exists and holds null returns null, not your default.
Example
When to use it
- Pull a deeply nested field out of a decoded webhook payload without chained isset() checks.
- Collect one attribute from every row of a nested list with a * wildcard.
- Read a path that may traverse both array keys and object properties in the same expression.
Frequently asked questions
What is the difference between data_get and Arr::get?
data_get traverses objects as well as arrays, while Arr::get only handles arrays and ArrayAccess. data_get also supports * wildcards and the {first}/{last} placeholders, so use it whenever the structure may contain stdClass or model instances.
Why does data_get return null instead of my default value?
Because the key exists and its value is null — the default is only used when the path cannot be resolved at all. Write data_get($data, 'key') ?? $default if you want an existing null replaced too.
Does the * wildcard return a single value when only one item matches?
No, a wildcard always returns an array, even for one match or zero matches. Use the {first} or {last} placeholder, or call head() on the result, when you want a scalar back.
More examples
Mixed arrays and objects in one path
The same call handles a stdClass tree from json_decode, a plain array and a Collection. This is the distinction from Arr::get(), which would fail on the object segments.
<?php
$response = json_decode('{"user":{"profile":{"city":"Lisbon"}}}');
// $response is a stdClass tree, but the path syntax is identical.
echo data_get($response, 'user.profile.city'), "\n";
// Lisbon
echo data_get($response, 'user.profile.country', 'unknown'), "\n";
// unknown
// Collections work too.
echo data_get(collect(['a' => ['b' => 42]]), 'a.b'), "\n";
// 42{first} and {last} placeholders
{first} and {last} return the single matching value, while * returns an array of every match. Mixing them up is the usual cause of an "array to string conversion" notice further down the code.
<?php
$flight = [
'segments' => [
['from' => 'LHR', 'departure' => '9:00', 'to' => 'IST', 'arrival' => '15:00'],
['from' => 'IST', 'departure' => '16:00', 'to' => 'PKX', 'arrival' => '20:00'],
],
];
echo data_get($flight, 'segments.{first}.arrival'), "\n"; // 15:00
echo data_get($flight, 'segments.{last}.to'), "\n"; // PKX
// Contrast with a wildcard, which always returns an array.
print_r(data_get($flight, 'segments.*.from'));
// Array ( [0] => LHR [1] => IST )Lazy default in an application
Because the default is resolved through value(), an expensive fallback such as a config or database lookup costs nothing when the key is present. This snippet needs a booted application for config().
<?php
namespace App\Services;
class Settings
{
public function __construct(private array $payload) {}
public function currency(): string
{
// The closure only runs when the path is missing, so the
// config lookup is skipped on the happy path.
return data_get(
$this->payload,
'account.billing.currency',
fn () => config('app.default_currency')
);
}
}
Discussion