Laravel - 在树结构中计算,其中父项中的计数是来自子项的计数总和

时间:2017-01-28 22:10:21

标签: php laravel count tree relationships

我一直在努力解决这个问题,无法真正找到解决方案,所以我希望得到一些帮助。

我有两个相关模型ModelOne,它们也与自身相关,可以拥有无​​数个孩子,孙子等,并与ModelTwo相关。

class ModelOne extends Model
{
    ...

    public function model_two()
    {
        return $this->hasMany(ModelTwo::class, 'foreign_key');
    }

    //recursively get all children, grandchildren etc.
    public function children() {
        return $this->hasMany(ModelOne::class, 'foreign_key')->with('children');
    }

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

class ModelTwo extends Model
{
    ...

    public function model_one()
    {
        return $this->belongsTo(ModelOne::class, 'foreign_key');
    }   
}

在我ModelOne的get函数中,我有:

public function get($id = null) {
    return is_null($id) ?
        ModelOne::withCount('model_two')->get() :
        ModelOne::withCount('model_two')->where('id', $id)->first();
}

这给了我一个很好的一维对象数组(或一个对象),我需要的是与计数形式相关的ModelTwo,但每个相关对象的个别计数。

我需要的是,对于返回数组中每个具有子项,孙子项等的ModelOne个对象,要计算该对象中所有子项,孙子项等的计数总和。 因此,例如,如果我有一个ModelOne的对象,其id = 2,parent_id = 1且计数为3,另一个对象的id = 3,parent_id = 1且计数为2,那么对于具有id的对象= 1,即前两个的父级,count将为5.此逻辑在树中继续为所有节点。

[
  {
    "id":1
    "parent_id":"NULL",
    "model_two_count":5
  },
  {
    "id":2
    "parent_id":1,
    "model_two_count":3
  },
  {
    "id":3
    "parent_id":1,
    "model_two_count":2
 }

 ...

]

1 个答案:

答案 0 :(得分:2)

所以,我想出了解决方案,这里适合所有可能遇到同样问题的人。

首先,在我的ModelOne我添加了withCount('model_two'),以便我立即从ModelTwo获得关系计数。我还添加了一个属性$return_count,它将保存所有子孙的计数总和等。

class ModelOne extends Model
{

    private $return_count = 0;

    ...

    public function model_two()
    {
        return $this->hasMany(ModelTwo::class, 'foreign_key');
    }

    //recursively get all children, grandchildren etc.
    public function children() {
        return $this->hasMany(ModelOne::class, 'foreign_key')->withCount('model_two')->with('children');
    }

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

然后,我添加了两个辅助函数,一个递归地获取所有子孙 - 等等。对于给定的父ID,或者从表中返回所有记录:

public function get_recursive($parentId = null){
    return is_null($parentId) ?
        ModelOne::with('children')->get() :
        ModelOne::where('id', $parentId)->with('children')->first();
}

此功能的输出如下:

[
  {
    "id":1
    "parent_id":"NULL",
    "model_two_count":0.
    "children": [
        {
            "id":2
            "parent_id":1,
            "model_two_count":3
        },
        {
            "id":3
            "parent_id":1,
            "model_two_count":2
         }
      ]
   }

 ...

]

此示例适用于二维嵌套,但它可以无限深。 在此函数中创建的返回对象成为第二个函数中的输入参数。第二个功能实际上是对所有子孙等的计数进行递归求和。对于给定的父对象。可以访问属性model_two_count,因为它是在调用withCount('model_two')时创建的。此外,在调用children时会创建属性with('children')

private function count_sum($parentChildren) {
    foreach ($parentChildren->children as $child) {
        $this->return_count += $child->model_two_count;
        $this->count_sum($child);
    }
    return $this->return_count;
}

最后我调用了我的get函数:

public function get($id = null) {
    if(is_null($id = null)) {
        $data = ModelOne::withCount('model_two')->get();                 
        return $data->map(function ($i) {               

            //get all children-grandchildren-etc. for specfic object with id = $i->id
            $children = $this->get_recursive($i->id);

             //if object with id = $i->id have children then model_two_count is sum of all model_two_count from all children-grandchildren-etc.                    
             if (!empty(array_filter((array)$children->children))) {
                $i->model_two_count = $this->count_sum($children);
                //reset the return_count variable for next iteration. If this is not done, then sum from previous object will be added to the next object count
                $this->return_count = 0;
            } 
            return $i;
        });
    } else {
        $data = ModelOne::withCount('model_two')->where('id', $id)->get();                 
        return $data->map(function ($i) {
            $children = $this->get_recursive($i->id);
            if (!empty(array_filter((array)$children->children))) {
                $i->model_two_count = $this->count_sum($children);                         
                $this->return_count = 0;
             } 
             return $i;
         })->first();
     }
 }

这样可以获得理想的输出效果:

[
  {
    "id":1
    "parent_id":"NULL",
    "model_two_count":5
  },
  {
    "id":2
    "parent_id":1,
    "model_two_count":3
  },
  {
    "id":3
    "parent_id":1,
    "model_two_count":2
 }

 ...

]