Skip to content

Instantly share code, notes, and snippets.

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();
@ddavaham

This comment has been minimized.

Copy link

@ddavaham ddavaham commented Dec 2, 2017

I work with a rest API returns objects. I usually cast these object to a collections. They tend to nested arrays or objects, so I added support for objects as well. I would have of done a pull request, but I do not think that Gist support them

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

Complete Code

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

                return $value;
            });
        });
@brunogaspar

This comment has been minimized.

Copy link
Owner Author

@brunogaspar brunogaspar commented Dec 6, 2017

@ddavaham I've done the suggested change on the OP.

Thanks!

@seanmangar

This comment has been minimized.

Copy link

@seanmangar seanmangar commented Jan 4, 2018

Had the same issue. Thanks for this Bruno! 👍

@robsontenorio

This comment has been minimized.

Copy link

@robsontenorio robsontenorio commented Jun 16, 2018

@4hm3d same issue. can you share full macro code?

@medteck

This comment has been minimized.

Copy link

@medteck medteck commented Jun 22, 2018

Had to deal with a large amount of recursive JSON data! This fixed my problem.
Thanks 👍

@adrienne

This comment has been minimized.

Copy link

@adrienne adrienne commented Jun 24, 2018

Please consider submitting a pull request to these folks: https://github.com/spatie/laravel-collection-macros who have a super handy collection of macros that this would be a great fit for!

@roni-estein

This comment has been minimized.

Copy link

@roni-estein roni-estein commented Nov 27, 2018

Thx

@marcheffels

This comment has been minimized.

Copy link

@marcheffels marcheffels commented Jan 17, 2019

Thx

@Mombuyish

This comment has been minimized.

Copy link

@Mombuyish Mombuyish commented Mar 17, 2019

Thanks!

@brunogaspar

This comment has been minimized.

Copy link
Owner Author

@brunogaspar brunogaspar commented Mar 31, 2019

Hey peeps!

Since i've saw a bit of "demand" for this helper/macro to be in a more installable way and considering Spatie refused a few pull requests to add this macro, i've decided to create my own package.

https://github.com/werxe/laravel-collection-macros

Feel free to use it and suggest new macros, i'm sure i'll be adding a few more.

Thanks!

@jonaslm

This comment has been minimized.

Copy link

@jonaslm jonaslm commented Oct 17, 2019

We wanted something similar, but for it to leave standard objects alone (because $object->foo is nicer than $object->get('foo') or $object['foo']). We also rewrote it as a helper method instead of a Collection macro, since it's more like the collect() helper method than something you do for an existing Collection.

If anyone else has the same needs, here you go:

function recursivelyCollect($item) {
	if (is_array($item)) {
		return recursivelyCollect(collect($item));
	} elseif ($item instanceof Collection) {
		$item->transform(static function ($collection) {
			return recursivelyCollect($collection);
		});
	} elseif (is_object($item)) {
		foreach ($item as $key => &$val) {
			$item->{$key} = recursivelyCollect($val);
		}
	}
	return $item;
}

A tinker example based on the one given in the original gist:

>>> $data
=> [
     {#3989
       +"name": "John Doe",
       +"emails": [
         "john@doe.com",
         "john.doe@example.com",
       ],
       +"contacts": [
         {#3992
           +"name": "Richard Tea",
           +"emails": [
             "richard.tea@example.com",
           ],
         },
         {#3990
           +"name": "Fergus Douchebag",
           +"emails": [
             "fergus@douchebag.com",
           ],
         },
       ],
     },
   ]
>>> recursivelyCollect($data);
=> Illuminate\Support\Collection {#3942
     all: [
       {#3989
         +"name": "John Doe",
         +"emails": Illuminate\Support\Collection {#3975
           all: [
             "john@doe.com",
             "john.doe@example.com",
           ],
         },
         +"contacts": Illuminate\Support\Collection {#3971
           all: [
             {#3992
               +"name": "Richard Tea",
               +"emails": Illuminate\Support\Collection {#3974
                 all: [
                   "richard.tea@example.com",
                 ],
               },
             },
             {#3990
               +"name": "Fergus Douchebag",
               +"emails": Illuminate\Support\Collection {#3972
                 all: [
                   "fergus@douchebag.com",
                 ],
               },
             },
           ],
         },
       },
     ],
   }
@lorisleiva

This comment has been minimized.

Copy link

@lorisleiva lorisleiva commented Nov 28, 2019

Thanks, that was very helpful! 👍

I've tweaked the macro so that it wraps the $value using the lowest inheritance class.

Collection::macro('recursive', function () {
    return $this->map(function ($value) {
        return is_array($value) || is_object($value)
            ? (new static($value))->recursive()     // <- new static() instead of collect()
            : $value;
    });
});

For example, if you extend the Collection class like this:

class Knowledge extends Collection
{
    // ...
}

Then you can recursively wrap a nested array like that:

Knowledge::wrap($array)->recursive();

And now all nested collections will be instances of Knowledge.

@brunogaspar

This comment has been minimized.

Copy link
Owner Author

@brunogaspar brunogaspar commented Nov 28, 2019

No problem, glad you found it helpful!

@amirasyraf

This comment has been minimized.

Copy link

@amirasyraf amirasyraf commented Oct 11, 2020

Thanks a lot for this.

@wivaku

This comment has been minimized.

Copy link

@wivaku wivaku commented Nov 29, 2020

Nice! Is it possible to get combination of the 1) the original macro, 2) @lorisleiva 's tweak and 3) the modification by @jonaslm ?

This so I can use e.g.

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

(instead of the original macro: $collection->first()->get('name'))

@Tantalump

This comment has been minimized.

Copy link

@Tantalump Tantalump commented Dec 4, 2020

@wivaku Can you share your code? Thanks.

@joserick

This comment has been minimized.

Copy link

@joserick joserick commented May 19, 2021

A thousand years later... Requested by @wivaku:

Combination of the 1) the original macro @brunogaspar, 2) @lorisleiva 's tweak and 3) the modification by @jonaslm.

Collection::macro('recursive', function () {
    return $this->whenNotEmpty($recursive = function ($item) use (&$recursive) {
        if (is_array($item)) {
            return $recursive(new static($item));
        } elseif ($item instanceof Collection) {
            $item->transform(static function ($collection, $key) use ($recursive, $item) {
                return $item->{$key} = $recursive($collection);
            });
        } elseif (is_object($item)) {
            foreach ($item as $key => &$val) {
                $item->{$key} = $recursive($val);
            }
        }
        return $item;
    });
});
$collection = collect($data)->recursive();
$collection->first()->name; // John Doe

If anyone has any ideas on how to reduce it (less code) I would appreciate it.

@wivaku @Tantalump

@wivaku

This comment has been minimized.

Copy link

@wivaku wivaku commented May 20, 2021

Excellent, thanks @joserick !

@77media-creations

This comment has been minimized.

Copy link

@77media-creations 77media-creations commented Sep 14, 2021

@joserick Nice, this works in laravel 8.

@dani0332

This comment has been minimized.

Copy link

@dani0332 dani0332 commented Sep 25, 2021

Resolved

Should have added \Illuminate\Support\Collection at $item instanceof Collection


@joserick Strangely though It doesn't work for me. But the original macro works/

$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();
  dd($collection->first()->name);

And the macro is

        \Illuminate\Support\Collection::macro('recursive', function () {
            return $this->whenNotEmpty($recursive = function ($item) use (&$recursive) {
                if (is_array($item)) {
                    return $recursive(new static($item));
                } elseif ($item instanceof Collection) { // <-- Should have added \Illuminate\Support\Collection
                    $item->transform(static function ($collection, $key) use ($recursive, $item) {
                        return $item->{$key} = $recursive($collection);
                    });
                } elseif (is_object($item)) {
                    foreach ($item as $key => &$val) {
                        $item->{$key} = $recursive($val);
                    }
                }
                return $item;
            });
        });

on dd($collection->first()); means its making it an array

array:3 [
  "name" => "John Doe"
  "emails" => array:2 [
    0 => "john@doe.com"
    1 => "john.doe@example.com"
  ]
  "contacts" => array:2 [
    0 => array:2 [
      "name" => "Richard Tea"
      "emails" => array:1 [
        0 => "richard.tea@example.com"
      ]
    ]
    1 => array:2 [
      "name" => "Fergus Douchebag"
      "emails" => array:1 [
        0 => "fergus@douchebag.com"
      ]
    ]
  ]
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment