如何扁平化laravel递归关系集合(树集合)?

slhcrj9b  于 2023-08-08  发布在  其他
关注(0)|答案(6)|浏览(119)

我如何使用层次结构自引用模型将集合扁平化,将树集合扁平化为单个维度集合。我有一个有父母和孩子的自我参考模型。

我希望结果返回一个有说服力的集合,而不是一个简单的集合或数组。数组已用作结果结果,以便于演示

关系是这样声明的。

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');
}

字符串
因此,当我调用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" => []
          ]
        ]
      ]
    ]
  ]
]


我想实现的是集合是一维的。下面是该集合的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"
    ]
]


我尝试过很多集合方法,比如filterflatMapflatten和多个数组方法。但还没有找到合适的解决方案。

ee7vknir

ee7vknir1#

现在有点晚了,但我将发布我希望在我自己结束写作之前能够找到的东西。
与最初的帖子类似,我在categories表中有一个递归的父/子关系(但这可以应用于任何具有自引用parent_id列的表)。你可以这样设置你的模型:

Category.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));
    }
}

字符串
然后在应用程序中的某个地方,您需要有一个地方来放置一些全局帮助器函数。您可以按照instructions found here,然后粘贴以下辅助函数:

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;
}


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

注意事项:

  • 如果Category 1引用Category 2作为其父节点,然后Category 2Category 1作为其父节点,则这种方法可能会导致无限循环。只是要小心,父母/孩子的关系是乱伦免费:-)
  • 这种方法也不是很有效。这对于一堆父/子递归关系来说是很好的,但特别是对于flat_descendants函数,数据库查询的数量在每个生成级别上都呈指数级增长。
monwx1rj

monwx1rj2#

我也没有在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);
}

字符串
然后像这样使用它:

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

jutyujz0

jutyujz03#

这将递归地变平。但它并不能防止重复,所以如果这是一个问题,你需要过滤掉它们。
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;
});

字符串
然后,

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

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

rn0zuynd

rn0zuynd4#

这是我的代码,可能会有帮助。

Collection::macro('flattenTree', function ($childrenField = 'children', $levelAttribute = 'level')
    {
        $toProcess = $this->items;
        $processed = [];
        while($item = array_shift($toProcess))
        {
            $item->$levelAttribute ++;
            $processed[] = $item;
            if (count($item->$childrenField) > 0) {
                $children = array_reverse($item->$childrenField->items);
                foreach ($children as $child) {
                    $child->$levelAttribute = $item->$levelAttribute;
                    array_unshift($toProcess,$child);
                }
            }
        }
        return Collection::make($processed);
    });

字符串
您应该将此代码放在AppServiceProvider.php或任何您希望的提供程序的 Boot 方法中,然后您可以像这样使用它

Category::where('parent_category_id', null)->get()->flattenTree();


这将使树变平并向每个对象添加一个级别属性以指示对象的深度级别
祝大家好运

cxfofazt

cxfofazt5#

对于那些因为乱伦关系而陷入死循环的人,我使用这个解决方案通过急切加载的关系来检索后代的属性-工作方式类似于完全扁平化关系,但避免通过foreach陷入死循环。
Solution link

tez616oj

tez616oj6#

我喜欢这个属性解决方案没有特别的原因,除了能够在获取后使用map轻松提取信息。

public function chilrenRecursive()
{
    return $this->children()->with('chilrenRecursive');
}

public function getFlatSelfAndChildrenAttribute()
{
    return collect([$this])->merge(
        $this->chilrenRecursive->flatMap(function($q){
            return $q->flatSelfAndChildren ?? collect([$q]);
        })
    );
}

// Example controller code
return $thing->flatSelfAndChildren->map(function($q){ return $q->id;})->toArray();

字符串

相关问题