Skip to content

Instantly share code, notes, and snippets.

@brunogaspar
Last active November 2, 2023 16:48
Star You must be signed in to star a gist
Embed
What would you like to do?
Recursive Laravel Collection Macros

What?

If a nested array is passed into a Laravel Collection, by default these will be threaded as normal arrays.

However, that's not always the ideal case and it would be nice if we could have nested collections in a cleaner way.

This is where this macro comes in handy.

Setup

Register this macro for example on the boot method of your app\Providers\AppServiceProvider.php file:

\Illuminate\Support\Collection::macro('recursive', function () {
    return $this->map(function ($value) {
        if (is_array($value) || is_object($value)) {
            return collect($value)->recursive();
        }

        return $value;
    });
});

Note: Tested on Laravel 5.5 and 5.6!

How

Usage is quite simple:

$data = [
    [
        'name' => 'John Doe',
        'emails' => [
            'john@doe.com',
            'john.doe@example.com',
        ],
        'contacts' => [
            [
                'name' => 'Richard Tea',
                'emails' => [
                    'richard.tea@example.com',
                ],
            ],
            [
                'name' => 'Fergus Douchebag', // Ya, this was randomly generated for me :)
                'emails' => [
                    'fergus@douchebag.com',
                ],
            ],
        ],
    ],
];

$collection = collect($data)->recursive();
@bhaidar
Copy link

bhaidar commented Dec 21, 2022

Hey @brunogaspar, I'm trying to understand how this recursive macro works internally.
When you call recursive() on a collection, the first layer of arrays gets converted to collections. The inner layers get converted when for example, mapping or printing (toArray) the entire collection.

Maybe I'm missing something here.

When a value is an array or object, you return collect($value)->recursive(). This doesn't run right away, correct?


Oh I just got it! When you return the collect($value)->recursive() it actually runs the recursive() function on the $value. The 4value gets converted to a collection and all its children if any an array, will also get converted to a collection

Thanks

@joserick
Copy link

I hope this new function can help you in something @bhaidar .

Combination of "Collect Recursive with depth" and "Access collect items as properties" (improved*)

// Add a new method called "recursive" to the Collection class.
\Illuminate\Support\Collection::macro('recursive', function (int $depth = null) {
    // Use the map method to iterate over the items in the collection.
    return $this->map(function ($item) use ($depth) {
        // If the depth is 0 or the item is not a collection, array, or object, return the item as-is.
        if (($depth === 0) || !($item instanceof \Illuminate\Support\Collection || is_array($item) || is_object($item))) {
            return $item;
        }

        // Create a new anonymous class that extends the Collection class and overrides the __get and __set magic methods.
        // To be able to access the collection items as if they were properties of a object.
        return (new class(new static($item)) extends \Illuminate\Support\Collection {
            public function __get($key) { return $this->get($key); }
            public function __set($key, $value) { $this->put($key, $value); }
        })->recursive($depth - 1); // Apply the "recursive" method to the new Collection instance.
    });
});

Access item "name" as a property:

$collection = collect($data)->recursive();
$collection->first()->name; // John Doe

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment