Laravel Collections are a powerful feature that allow developers to work with arrays and other data structures in an intuitive, object-oriented way. They provide a clean, expressive API to manipulate data without the need to loop through arrays manually. One of the many ways to extend the functionality of Laravel collections is through macros, which allow you to define custom methods that can be reused throughout your application.

In this blog post, we'll take a closer look at a custom macro that performs recursive mapping on arrays and objects within a collection.

Understanding the code

Here's the macro in question:

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

This macro extends Laravel's Collection class with a new method named recursive. The goal of this method is to recursively map over arrays and objects within a collection, transforming all nested structures into collections themselves.

Let's break it down step by step.

Defining the macro

Collection::macro('recursive', function () {

Macros in Laravel allow you to add custom methods to existing classes. Here, we're adding a method called recursive to the Collection class. By using the macro() method, we can inject this functionality anywhere in our Laravel application.

Mapping the collection

return $this->map(function ($value) {

Inside the macro, the first operation is to call the map() method on the current collection ($this). The map() function iterates over each item in the collection and allows you to transform it.

The anonymous function passed to map() will receive each element ($value) in the collection. Depending on what type of data $value contains, we'll handle it differently.

Handling arrays

if (is_array($value)) {
    return collect($value)->recursive();
}

If the current item is an array, we convert it into a collection using the collect() helper. This converts the array into a Laravel collection, and we call the recursive() method on it to ensure that any nested arrays or objects within it are also recursively transformed into collections.

Handling objects

if (is_object($value)) {
    return collect((array) $value)->recursive();
}

Similarly, if the item is an object, we first cast it into an array using (array) $value, then wrap that array into a collection with collect(). As with arrays, the recursive() method is called to ensure that any nested structures are handled.

Returning primitive values

return $value;

Finally, if the value is neither an array nor an object (for example, if it's a string, integer, boolean, etc.), we simply return it as is.

Why use a recursive collection macro?

There are several scenarios in which a recursive collection might be useful:

  • Working with deeply nested data: APIs or database queries might return complex, deeply nested arrays or objects. By converting everything into collections, you gain access to Laravel's powerful collection methods, such as filter(), pluck(), and each(), on every level of the data structure.

  • Consistent Data Structures: When working with a mix of arrays and objects, it can be cumbersome to constantly check and convert data types. By recursively converting everything to collections, you ensure consistency across your data handling.

  • Cleaner Code: Collections provide a fluent, expressive syntax that can make your code easier to read and maintain compared to manually looping through data and checking types.

Example usage

Let's take a look at how you might use this macro in a real-world scenario:

$data = [
    'user' => [
        'name' => 'John Doe',
        'roles' => [
            ['id' => 1, 'name' => 'Admin'],
            ['id' => 2, 'name' => 'Editor']
        ]
    ],
    'settings' => (object) [
        'theme' => 'dark',
        'notifications' => [
            'email' => true,
            'sms' => false
        ]
    ]
];

$collection = collect($data)->recursive();

// Now you can work with the entire structure as collections
$adminRole = $collection->get('user')->get('roles')->firstWhere('id', 1);
$theme = $collection->get('settings')->get('theme');

In this example, the $data array contains a mix of nested arrays and objects. By calling recursive(), we can treat everything as collections and use collection methods like firstWhere() and get() to work with the data in a clean, readable way.

Conclusion

Extending Laravel's collections with a recursive macro is a great way to simplify working with complex, nested data structures. It allows you to leverage the full power of Laravel's collection API, even with deeply nested arrays and objects, without the need for manual loops or conditionals.

By using this macro, your code becomes more expressive, consistent, and maintainable. If you regularly work with nested data in Laravel, this is a trick worth adding to your toolbox.