data()

The data() function takes a source object/array/collection and a list of fields, and returns an array with only those fields. It is based on Laravel's Arr::only() and data_get() functions.

It is particularly useful for converting Eloquent Models and Collections into JSON, e.g. to pass to Inertia.js in Nexus, or as part of an ajax response. It is often a simpler alternative to API Resources or manual transformations.

Basic Usage

The data() function takes two parameters - the input, and a list of fields. In its most basic form, it works the same as the Arr::only() method - the output is a copy of the input with only the keys you specify:

// Input:
$input = [
    'aaa' => 111,
    'bbb' => 222,
    'ccc' => 333,
];

// Usage:
$output = data($input, [
    'aaa',
    'bbb',
]);

// Result:
$output = [
    'aaa' => 111,
    'bbb' => 222, // Note that the 'ccc' key is removed because it wasn't specified
];

The input can also be an object, such as an Eloquent model, or a Collection - it will work just the same, and it will always return a plain array (not an object):

$user = User::find(1);

$output = data($user, [
    'name',
    'email',
]);

// Result:
$output = [
    'name' => 'Dave Miller',
    'email' => 'dave@alberon.co.uk',
];

Multi-Dimensional Arrays

The data() method can also accept dot notation, similar to Laravel's data_get() method.

For example, here we take a multi-dimensional array as input and specify which keys we want to keep in the output:

$input = [
    'aaa' => [
        'a1' => 111,
        'a2' => 222,
    ],
    'bbb' => [
        'b3' => 333,
        'b4' => 444,
    ],
    'ccc' => [
        'c5' => 555,
        'c6' => 666,
    ],
];

$output = data($input, [
    'aaa.a1',
    'bbb.b4',
]);

// Result:
$output = [
    'aaa' => [
        'a1' => 111, // Note: 'aaa.a2' is missing
    ],
    'bbb' => [
        'b4' => 444, // Note: 'bbb.b3' is missing
    ],
    // Note: 'ccc' and its children are missing
];

As mentioned above, the input can also be an object, such as an Eloquent model, or a Collection.

For example, assuming the User model has a BelongsTo relation named status:

$user = User::find(1);

$output = data($user, [
    'name',
    'email',
    'status.id',
    'status.name',
]);

// Result:
$output = [
    'name' => 'Dave Miller',
    'email' => 'dave@alberon.co.uk',
    'status' => [
        'id' => 1,
        'name' => 'Active',
    ],
];

Nulls

If part of the input is missing or null, the output is null and any children are ignored. For example, if a user doesn't have a related status:

$user = User::find(1);

$output = data($user, [
    'name',
    'email',
    'status.id',
    'status.name',
]);

// Result:
$output = [
    'name' => 'Dave Miller',
    'email' => 'dave@alberon.co.uk',
    'status' => null,
];

Wildcards

A * wildcard instructs the data() method to return all keys in that position. For example:

$input = [
    'aaa' => [
        'a1' => ['value' => 111, 'other' => 1111],
        'a2' => ['value' => 222, 'other' => 2222],
    ],
    'bbb' => [
        'b3' => ['value' => 333, 'other' => 3333],
        'b4' => ['value' => 444, 'other' => 4444],
    ],
    'ccc' => [
        'c5' => ['value' => 555, 'other' => 5555],
        'c6' => ['value' => 666, 'other' => 6666],
    ],
];

$output = data($input, [
    'aaa.*.value',
    'bbb.*.value',
]);

// Result:
$output = [
    'aaa' => [
        'a1' => ['value' => 111], // All children of 'aaa' and 'bbb' are included, but only the 'value' keys
        'a2' => ['value' => 222],
    ],
    'bbb' => [
        'b3' => ['value' => 333],
        'b4' => ['value' => 444],
    ],
];

This is especially useful when handling Collections of models. For example:

$users = User::orderBy('name')->with('status')->get();

$output = data($users, [
    '*.name',
    '*.email',
    '*.status.name',
]);

// Result:
$output = [
    [
        'name' => 'Dave Miller',
        'email' => 'dave@alberon.co.uk',
        'status' => ['name' => 'Active'],
    ],
    [
        'name' => 'Tim Ault',
        'email' => 'tim@alberon.co.uk',
        'status' => ['name' => 'Active'],
    ],
];

Remember to use eager loading (with()) if you are looking up nested relations like this.

Warning: If you forget the leading *., you will not get the desired result! If you forget it and pass the result to a Vue component with typed props, you will probably get an error saying it expected an Array but got an Object.

Note: You cannot use wildcards for partial matches - e.g. 'a*' would look for a literal $input['a*'] rather than returning all keys that begin with a.

Pagination

As a special case, if you pass a paginator instance as the first parameter, the output is wrapped in an outer array containing the paginator data:

$users = User::orderBy('name')->paginate();

$output = data($users, [
    '*.name',
    '*.email',
]);

// Result:
$output = [
    'data' => [
        [
            'name' => 'Dave Miller',
            'email' => 'dave@alberon.co.uk',
        ],
        [
            'name' => 'Tim Ault',
            'email' => 'tim@alberon.co.uk',
        ],
    ],
    'page' => 1,
    //...
];

This only applies when the paginator is passed in as the first parameter - you shouldn't have a paginator nested in the input.

Note: This uses the paginator's toArray() method. In Nexus, a custom paginator class is used to customise the paginator output.

Conditional Keys

If you sometimes need to remove certain keys - e.g. things the user doesn't have permission to access - pass an associative array with a value of true or false as appropriate:

$output = data($user, [
    'name',
    'email' => Gate::allows('see email addresses'), // If the gate returns false, 'email' will not be included in the output
]);

Note: It must be a boolean for this to work (not string, number, etc.) - cast it with (bool)$value if needed.

Method Calls

To call a method on an object, instead of looking for a property, add () after the name:

$output = data($record, [
    '*.active' => '*.active()', // Calls $record[0]->active(), for example
]);

This is useful when you can't easily add dynamic properties - for example, when using Spatie DTO.

Note: You can't pass any parameters using this syntax - in that case you can use a closure instead.

Advanced Usage

The following features are for advanced scenarios, and should be used sparingly.

Aliasing (Renaming)

If you need to rename a key, pass an associative array with a string value containing the original name:

$output = data($user, [
    'name' => 'full_name', // Looks up $user->full_name, but renames it to $output['name']
]);

If the result is an array/object/etc., you can still specify which sub-keys you want as normal:

$output = data($page, [
    'user' => 'creator',   // Looks up $page->creator, but renames it to $output['user']
    'user.name',           // Looks up $page->creator->name, but renames it to $output['user']['name']
    'user.email',          // etc.
]);

Note: I don't recommend doing this regularly, especially in Nexus, as it complicates the PHP code and makes the Vue code harder to follow as it is disconnected from the data layer.

Casting / Closures

If you need to cast a value to a different type, pass an associative array with a closure:

$output = data($input, [
    'date' => fn($date) => carbon($date), // Equivalent to $output['date'] = carbon($input['date'])
]);

Or equivalently:

$output = data($input, [
    'date' => function($date) {
        return carbon($date);
    },
]);

Note: If you are using Eloquent, you should use $casts or accessors on the model wherever possible, instead of this syntax.

Advanced Closures

Closures will also receive the ancestor elements in reverse order as parameters, so you can do some even more advanced things if needed...

For example:

$output = data($page, [
    'creator.name' => fn($_, $creator) => trim("{$creator->first_name} {$creator->last_name}");
]);

In this example, the name attribute doesn't exist, so the first parameter is always null, but the second parameter is the parent element ($creator) and can be used to retrieve the first & last names. We use the dummy variable $_ to signal that it's not used.

Note: If you are using Eloquent, you should use accessors on the model wherever possible, instead of this syntax.

Static Values

If you need to provide a static value for any reason, you must also wrap it in a closure:

$results = data($options, [
    '*.id',
    '*.title',
    '*.count' => fn() => 0, // May be incremented later
]);