How do I flatten laravel recursive relationship collection (tree collections)? How do I flatten laravel recursive relationship collection (tree collections)? laravel laravel

How do I flatten laravel recursive relationship collection (tree collections)?


It's a bit late, but I'm going to post what I wish I had been able to find before I ended up writing it myself.

Similar to the original post, I have a recursive parent/child relationship in my categories table (but this could apply to any table with a self-referencing parent_id column). You can set up your Model like this:

Category.php

<?phpnamespace App\Models;use Illuminate\Database\Eloquent\Model;class Category extends Model {    // Relationships    public function parent()    {        return $this->belongsTo('App\Models\Category', 'parent_id');    }    public function children()    {        return $this->hasMany('App\Models\Category', 'parent_id');    }    public function nested_ancestors()    {        return $this->belongsTo('App\Models\Category', 'parent_id')->with('parent');    }    public function nested_descendants()    {        return $this->hasMany('App\Models\Category', 'parent_id')->with('children');    }    // Attributes    public function getFlatAncestorsAttribute()    {        return collect(flat_ancestors($this));    }    public function getFlatDescendantsAttribute()    {        return collect(flat_descendants($this));    }}

Then somewhere in your application, you need to have a place to put some global helper functions. You could follow the instructions found here, and then just paste in the following helper functions:

Helpers.php

function flat_ancestors($model) {  $result = [];  if ($model->parent) {    $result[] = $model->parent;    $result = array_merge($result, flat_ancestors($model->parent));  }  return $result;}function flat_descendants($model) {  $result = [];  foreach ($model->children as $child) {    $result[] = $child;    if ($child->children) {      $result = array_merge($result, flat_descendants($child));    }  }  return $result;}

The code above will then allow you to use $category->flat_ancestors, which will produce a flat collection of all the category's ancestors, no matter how many there are. Similarly, using $category->flat_descendants will yield a flat collection of all the child categories, and the child's children categories, and so on until all the posterity categories have been accounted for.

Some things to be careful of:

  • This type of approach could potentially lead to an infinite loop ifyou have Category 1 referencing Category 2 as its parent, andthen Category 2 has Category 1 as its parent. Just be carefulthat parent/child relationships are incest free :-)
  • This type of approach also isn't very efficient. It'll be fine for a bunch ofparent/child recursive relationships, but especially for theflat_descendants functions, the number of database queries growsexponentially for each generation level.


I didn't find any builtin method into theLaravel collection either. You may try something like this (Use it as a global function or as a dedicated class method, it's up to you. here is the idea):

function flatten($array) {    $result = [];    foreach ($array as $item) {        if (is_array($item)) {            $result[] = array_filter($item, function($array) {                return ! is_array($array);            });            $result = array_merge($result, flatten($item));        }     }    return array_filter($result);}

Then use it like this:

// When available into global scope as a function$flattenArray = flatten($arrayFromTheCollection);


This will will recursively flatten. It doesn't prevent duplicates though, so you'll need to filter them out if that's an issue.

In your AppServiceProvider::boot method

use Illuminate\Support\Collection;//...Collection::macro('flattenTree', function ($childrenField) {    $result = collect();     foreach ($this->items as $item) {        $result->push($item);        if ($item->$childrenField instanceof Collection) {            $result = $result->merge($item->$childrenField->flattenTree($childrenField));        }    }    return $result;});

Then

$flattened = $myCollection->flattenTree('childrenRecursive');// or in the case of the question$flattened = $model->childrenRecursive->flattenTree('childrenRecursive');