Mastering Laravel’s data_get(): Read Anything from Arrays, Objects, and Collections
If you’ve ever written a long chain of null checks to fetch a deeply nested value — foo['bar']['baz'] ?? null — you’ll love Laravel’s data_get() helper. It’s a tiny, dependable utility that safely retrieves values from arrays, objects, ArrayAccess implementations, and even nested structures using a simple “dot notation.”
In this guide, you’ll learn every practical way to use data_get(): from the basics to advanced wildcard patterns, defaults, edge cases, and how it compares to alternatives like Arr::get() and the PHP nullsafe operator.
- What is data_get()
- Basic usage (dot notation, arrays, objects, collections)
- Providing default values (including closures)
- Wildcards with * and deep matching
- Escaping dots in keys
- Using data_get() with Collections
- Comparing data_get() vs Arr::get() vs nullsafe operator
- Common patterns and real-world examples
- Edge cases and gotchas
- Related helpers: data_set() and data_fill()
What is data_get()
Signature:
-
mixed data_get(mixed $target, string|array|null $key, mixed $default = null)
-
$target: The thing you’re reading from — array, object, ArrayAccess, or a mix.
-
$key: A dot-notated path, an array of segments, or null (which returns the whole target).
-
$default: Value to return if the path isn’t found. Can be a value or a closure.
Under the hood, data_get() walks your structure segment by segment. For objects, it reads public properties (and properties via magic __get). For arrays, it reads array keys. If at any point a segment doesn’t exist, it returns the default value.
Basic usage
Arrays
1$user = [ 2 'name' => 'Taylor', 3 'contacts' => [ 5 'social' => [ 6 'twitter' => '@taylorotwell', 7 ], 8 ], 9];10 12data_get($user, 'contacts.social.twitter'); // '@taylorotwell'
Objects
1class Profile { 2 public string $timezone = 'UTC'; 3} 4 5class User { 6 public Profile $profile; 7 public function __construct() 8 { 9 $this->profile = new Profile;10 }11}12 13$user = new User;14 15data_get($user, 'profile.timezone'); // 'UTC'
If your object implements ArrayAccess or uses magic getters, data_get() can still read the value.
Mixed arrays and objects
1$post = (object) [2 'author' => [3 'name' => 'Sam',4 'links' => (object) ['website' => 'https://example.com'],5 ],6];7 8// data_get() happily traverses both arrays and objects.9data_get($post, 'author.links.website'); // 'https://example.com'
When $key is null
1$data = ['a' => 1];2data_get($data, null); // returns the entire $data value
Providing default values
If any segment along the path is missing, data_get() returns the default (third argument).
1data_get($user, 'contacts.phone', 'n/a'); // 'n/a'
You can compute the default lazily using a closure. Laravel will call the closure only if the value is missing:
1$data_get_default = data_get($user, 'contacts.sms', fn () => config('defaults.sms_number'));
This is helpful when the default is expensive to compute or you need environment configuration.
Wildcards with *
Wildcards let you fetch values from lists in one shot. A * segment means “for each element at this level, continue the lookup and return the results.”
One-level wildcard
1$users = [4];5 6data_get($users, '*.email');7// ['[email protected]', '[email protected]']
Deep wildcards
1$teams = [2 ['name' => 'Alpha', 'members' => [['name' => 'A1'], ['name' => 'A2']]],3 ['name' => 'Beta', 'members' => [['name' => 'B1']]],4];5 6data_get($teams, '*.members.*.name');7// [['A1', 'A2'], ['B1']]
Note: The shape of the returned value mirrors the wildcard levels. If you want a flat list, you can flatten after:
1collect(data_get($teams, '*.members.*.name'))->flatten()->all();2// ['A1', 'A2', 'B1']
Wildcards with missing keys
If a key is missing in some items, those branches produce null (and may be omitted if subsequent traversal fails):
1$users = [3 ['name' => 'Linus'],4];5 6data_get($users, '*.contacts.email');7// ['[email protected]', null]
Default values with wildcards
The default applies when the top-level traversal can’t find anything. For missing values within a wildcard branch, you’ll typically normalize after:
1$result = data_get($users, '*.contacts.phone');2// [null, null]3 4$normalized = collect($result)->map(fn ($value) => $value ?? 'n/a')->all();5// ['n/a', 'n/a']
Escaping dots in keys
If your array key literally contains a dot, escape it with a backslash (.).
1$settings = [2 'cache' => [3 'driver.name' => 'redis',4 ],5];6 7data_get($settings, 'cache.driver\.name'); // 'redis'
If your key string is already segmented, you can also pass an array of segments instead of a dot string:
1data_get($settings, ['cache', 'driver.name']); // equivalent
Using data_get() with Collections
Collections are iterable, but data_get() expects arrays/objects for traversal. The simplest approach is to call ->all() on the collection when it represents a list. For object-like collections (e.g., keyed by strings), data_get() works with the underlying array.
1$users = collect([4]);5 6data_get($users->all(), '*.email');7// ['[email protected]', '[email protected]']
For nested collections, convert the relevant layers with ->all() as needed, or map with data_get() per item:
1$teams = collect([2 ['members' => collect([['name' => 'A1'], ['name' => 'A2']])],3]);4 5$names = $teams->map(fn ($team) => data_get($team, 'members.*.name'));6// $names: collect([['A1', 'A2']])
data_get() vs Arr::get() vs PHP’s nullsafe operator
-
data_get()
- Works with arrays, objects, ArrayAccess, and mixes of these.
- Supports wildcards (*), escaped dots, and closure defaults.
- Great for traversing heterogeneous data (API responses, decoded JSON, Eloquent models, DTOs).
-
Arr::get()
- Focused on arrays/ArrayAccess. No wildcard expansion.
- If you only work with arrays and want a slightly smaller surface area, Arr::get() is fine.
-
PHP nullsafe operator (->?)
- Reads object properties/methods, but doesn’t handle arrays or wildcards.
- Great for straight object graphs; less helpful for mixed array/object structures.
Often, data_get() replaces nested null coalescing and complex conditionals in one readable call.
Common patterns and real-world examples
Reading API responses
1$response = Http::get('https://api.example.com/user')->json();2 3$name = data_get($response, 'profile.name');4$firstPostTitle = data_get($response, 'posts.0.title');5$allTagNames = collect(data_get($response, 'posts.*.tags.*.name'))->flatten()->all();
Reading Eloquent models safely
1// Given $order is an Eloquent model with nested relations2$city = data_get($order, 'shippingAddress.city', 'Unknown');3$lineItemsSkus = data_get($order, 'items.*.sku', []);
Transforming data for views
1$payload = [ 2 'users' => [ 3 ['name' => 'Ada', 'roles' => [['name' => 'admin'], ['name' => 'editor']]], 4 ['name' => 'Linus', 'roles' => [['name' => 'viewer']]], 5 ], 6]; 7 8$userRoles = collect(data_get($payload, 'users.*.roles.*.name')) 9 ->map(fn ($roles) => collect($roles)->sort()->values()->all())10 ->all();11// $userRoles => [['admin','editor'], ['viewer']]
Guards for optional config
1$dsn = data_get(config('services.sentry'), 'dsn');2$retries = data_get(config('queue.connections.redis'), 'retry_after', 90);
Edge cases and gotchas
- Non-existent intermediate segments short-circuit to default.
- Example: data_get($user, 'profile.timezone', 'UTC') returns 'UTC' if profile is null or missing.
- Wildcards preserve structure depth.
- Use Collection::flatten() (or array flattening) if you need a single list.
- Numeric indices are valid segments.
- data_get($posts, '0.title') fetches the first item’s title.
- Don’t confuse property access with method calls.
- data_get() does not execute methods; it reads properties/ArrayAccess.
- Escaping matters for literal dots in keys.
- Use driver.name or pass segments as an array.
Related helpers: data_set() and data_fill()
While data_get() reads values, its siblings write values into nested structures.
- data_set(&$target, $key, $value, $overwrite = true)
- Sets a value at the given path, creating intermediate arrays/objects as needed.
- Respects the overwrite flag.
- data_fill(&$target, $key, $value)
- Like data_set() but only sets the value if it is missing.
Example:
1$user = [];2 3data_set($user, 'profile.timezone', 'UTC');4// $user = ['profile' => ['timezone' => 'UTC']]5 6$data = [];7data_fill($data, 'a.b', 123);8// $data = ['a' => ['b' => 123]]
Final tips
- Prefer data_get() when input shape is uncertain (API, untrusted payloads, optional relations).
- Use closure defaults for lazy or environment-based fallbacks.
- Combine wildcards with Collections for powerful transformations.
- Escape keys with dots using \. or supply key segments as an array.
With data_get(), your code becomes safer and more expressive, reducing the need for repetitive null checks and nested conditionals.