如何展平 Laravel 递归关系集合(树集合)?

aim*_*mme 8 php arrays laravel eloquent laravel-query-builder

如何将具有层次结构自引用模型、树集合的集合展平为单维集合。我有一个有父母和孩子的自引用模型。

我希望结果返回一个雄辩的集合,而不是一个简单的集合或数组。数组已用作结果结果以方便演示

关系是这样声明的。

public function parent()
{
    return $this->belongsTo(self::class, 'parent_id');
}

public function parentRecursive()
{
    return $this->parent()->with('parentRecursive');
}

public function children()
{
    return $this->hasMany(self::class, 'parent_id');
}

public function childrenRecursive()
{
    return $this->children()->with('childrenRecursive');
}
Run Code Online (Sandbox Code Playgroud)

所以当我调用model->childrenRecursive它时,它会按原样返回集合。像这样。我对其进行了更改toArray()以使其易于阅读。

array:1 [
  0 => array:6 [
    "id" => 5
    "name" => "I am a child of 1"
    "parent_id" => "1"
    "created_at" => "2016-12-26 13:53:50"
    "updated_at" => "2016-12-26 13:53:50"
    "children_recursive" => array:1 [
      0 => array:6 [
        "id" => 6
        "name" => "I am child of 5"
        "parent_id" => "5"
        "created_at" => "2016-12-26 13:53:50"
        "updated_at" => "2016-12-26 13:53:50"
        "children_recursive" => array:2 [
          0 => array:6 [
            "id" => 7
            "name" => "I am child of 6"
            "parent_id" => "6"
            "created_at" => "2016-12-26 13:53:50"
            "updated_at" => "2016-12-26 13:53:50"
            "children_recursive" => []
          ],
          1 => array:6 [
            "id" => 8
            "name" => "I am child of 6 too"
            "parent_id" => "6"
            "created_at" => "2016-12-26 13:53:50"
            "updated_at" => "2016-12-26 13:53:50"
            "children_recursive" => []
          ]
        ]
      ]
    ]
  ]
]
Run Code Online (Sandbox Code Playgroud)

我想要实现的是单维集合。这是toArray()该集合的外观。

array:4 [
  0 => array:6 [
    "id" => 5
    "name" => "I am a child of 1"
    "parent_id" => "1"
    "created_at" => "2016-12-26 13:53:50"
    "updated_at" => "2016-12-26 13:53:50"
    ],
  1 => array:6 [
    "id" => 6
    "name" => "I am child of 5"
    "parent_id" => "5"
    "created_at" => "2016-12-26 13:53:50"
    "updated_at" => "2016-12-26 13:53:50"
    ],
  2 => array:6 [
    "id" => 7
    "name" => "I am child of 6"
    "parent_id" => "6"
    "created_at" => "2016-12-26 13:53:50"
    "updated_at" => "2016-12-26 13:53:50"
    ],
  3 => array:6 [
    "id" => 8
    "name" => "I am child of 6 too"
    "parent_id" => "6"
    "created_at" => "2016-12-26 13:53:50"
    "updated_at" => "2016-12-26 13:53:50"
    ]
]
Run Code Online (Sandbox Code Playgroud)

我试过很多收集方法类似filterflatMapflatten和多阵列的方法。但还没有找到合适的解决方案。

The*_*pha 5

我也没有在Laravel集合中找到任何内置方法。您可以尝试这样的事情(将其用作全局函数或专用类方法,这取决于您。这是想法):

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);
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

// When available into global scope as a function
$flattenArray = flatten($arrayFromTheCollection);
Run Code Online (Sandbox Code Playgroud)


Bak*_*ems 5

有点晚了,但我将发布我希望在我最终自己编写之前能够找到的内容。

与原始帖子类似,我的表中有一个递归父/子关系categories(但这适用于任何带有自引用parent_id列的表)。您可以像这样设置模型:

分类.php

<?php
namespace 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));
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在你的应用程序的某个地方,你需要有一个地方来放置一些全局辅助函数。您可以按照此处找到说明进行操作,然后粘贴以下辅助函数:

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;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码将允许您使用$category->flat_ancestors,这将生成所有类别祖先的平面集合,无论有多少。类似地, using$category->flat_descendants将产生所有子类别和孩子的子类别的平面集合,依此类推,直到所有后代类别都被考虑在内。

需要注意的一些事项:

  • 这种类型的方法可能会导致一个无限循环,如果你已经Category 1引用Category 2作为其母公司,再Category 2Category 1是其父母。请注意父母/子女的关系是无乱伦的 :-)
  • 这种方法也不是很有效。对于一堆父/子递归关系来说没问题,但特别是对于 flat_descendants函数,数据库查询的数量在每一代级别都呈指数增长。


Joh*_*911 5

这将递归地展平。但是它并不能防止重复,因此如果这是一个问题,您需要将它们过滤掉。

在你的AppServiceProvider::boot方法中

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;
});
Run Code Online (Sandbox Code Playgroud)

然后

$flattened = $myCollection->flattenTree('childrenRecursive');

// or in the case of the question
$flattened = $model->childrenRecursive->flattenTree('childrenRecursive');
Run Code Online (Sandbox Code Playgroud)