DEV Community

Cover image for Handling Nested and Root Attributes Gracefully
Nasrul Hazim Bin Mohamad
Nasrul Hazim Bin Mohamad

Posted on

Handling Nested and Root Attributes Gracefully

In real-world Laravel applications, it’s common to work with complex data structures.

Sometimes, we have a nested attribute like details.name in our model, but in other cases, the same data might exist at the root level, e.g. name.

If you always use data_get($model, 'details.name'), you risk getting null when the nested attribute is empty — even if the root attribute exists.

This can happen when working with data that comes from different sources or has different states (e.g., draft vs. submitted applications).

The Problem

Here’s a common scenario:

return [
    'name' => data_get($company, 'details.name'),
    'category' => data_get($company, 'details.category'),
    'authorised_capital' => data_get($company, 'details.authorised_capital'),
];
Enter fullscreen mode Exit fullscreen mode

If details exists but details.category is empty, Laravel’s data_get won’t check the root-level category. You’ll end up with null values when a perfectly valid root value exists.

The Fallback Approach

To solve this, we can create a simple helper that checks:

  1. If the prefixed key (details.key) exists and is not empty, return it.
  2. Otherwise, fall back to the root key (key).

Here’s how we can do it:

function data_get_with_fallback($target, $key, $default = null, $prefix = 'details')
{
    $prefixed = data_get($target, "$prefix.$key");
    return $prefixed !== null && $prefixed !== ''
        ? $prefixed
        : data_get($target, $key, $default);
}
Enter fullscreen mode Exit fullscreen mode

Usage

return [
    'name'               => data_get_with_fallback($company, 'name'),
    'reference'          => data_get($this->model, 'running_number'),
    'category'           => data_get_with_fallback($company, 'category')
                              . ' ( ' . data_get_with_fallback($company, 'nature_of_business') . ' )',
    'authorised_capital' => data_get_with_fallback($company, 'authorised_capital'),
    'paid_up_capital'    => data_get_with_fallback($company, 'paid_up_capital'),
    'establishment'      => $date = data_get_with_fallback($company, 'registered_at')
                              ? Carbon::parse($date)->format('d-m-Y')
                              : null,
];
Enter fullscreen mode Exit fullscreen mode

Why This Matters

  • Consistency — Always try details.key first, then fall back to the root attribute.
  • Less Null Handling — Reduce conditional checks for empty values.
  • Reusability — Works for any model or array structure with nested + root data patterns.
  • Cleaner Code — Avoids repeating data_get calls and if conditions everywhere.

Final Thoughts

This pattern is simple but powerful.

If your application often deals with “nested-or-root” attributes, having a single reusable helper like data_get_with_fallback() keeps your code clean, DRY, and easy to maintain.


Photo by Corinne Kutz on Unsplash

Top comments (1)

Collapse
 
xwero profile image
david duymelinck • Edited

You don't need data_set or some custom method.

return [
    'name' => $company['details']['name'] ?? $company['name'],
    'category' => $company['details']['category'] ?? $company['category'],
];
Enter fullscreen mode Exit fullscreen mode

There are a few problems with the method;

  • By adding a default prefix value you will make the function harder for any other prefix to work with
  • The value could be an array, and then the fallback is activated
  • The name of the function is too generic. What is the fallback?

If you really want a function this one would be better.

function get_nestedOrRoot(array $collection, string $key)
{
    if( !str_contains($key, '.') ) {
        throw new \Exception('Only a nested key is allowed.');
    }

    $root = substr(strrchr($key, '.'), 1);

    return data_get($collection, $key) ?? data_get($collection, $root);
}
Enter fullscreen mode Exit fullscreen mode

update: for the "hardcore" sql people

SELECT 
    u.id,
    COALESCE(d.name, u.name) AS name
FROM users u
LEFT JOIN details d 
    ON u.id = d.user_id;
Enter fullscreen mode Exit fullscreen mode

Why fix it in code, if you can fix it in sql.