Laravel递归关系

时间:2014-10-30 12:08:08

标签: php laravel eloquent

我正在 Laravel 中开展项目。我有一个帐户模型,可以有一个父母或可以有孩子,所以我的模型设置如下:

public function immediateChildAccounts()
{
    return $this->hasMany('Account', 'act_parent', 'act_id');
}

public function parentAccount()
{
    return $this->belongsTo('Account', 'act_parent', 'act_id');
}

这很好用。我想做的是让所有孩子都在某个帐户下。目前,我正在这样做:

public function allChildAccounts()
{
    $childAccounts = $this->immediateChildAccounts;
    if (empty($childAccounts))
        return $childAccounts;

    foreach ($childAccounts as $child)
    {
        $child->load('immediateChildAccounts');
        $childAccounts = $childAccounts->merge($child->allChildAccounts());
    }

    return $childAccounts;
}

这也有效,但我不得不担心它是否会变慢。这个项目是我们在工作中使用的旧项目的重写。我们将有几千个帐户迁移到这个新项目。对于我所拥有的少数测试帐户,此方法不会产生任何性能问题。

有更好的解决方案吗?我应该只运行原始查询吗? Laravel 有什么办法可以解决这个问题吗?

总结 对于任何给定的帐户,我想要做的是在单个列表/集合中获取每个子帐户及其子女的每个孩子等等。图表:

A -> B -> D
|--> C -> E
     |--> F 
G -> H

如果我运行A-> immediateChildAccounts(),我应该得到{B,C}
如果我运行A-> allChildAccounts(),我应该得到{B,D,C,E,F}(顺序并不重要)

同样,我的方法有效,但似乎我做了太多的查询。

另外,我不确定在这里问这个问题是否可以,但这是相关的。如何获取包含子帐户的所有帐户的列表?所以基本上是上述方法的逆。这样,用户就不会尝试向已经是其孩子的父母提供帐户。使用上面的图表,我想要(在伪代码中):

Account :: where(account_id不在(A-> allChildAccounts()))。所以我会得到{G,H}

感谢您的任何见解。

8 个答案:

答案 0 :(得分:61)

这是你如何使用递归关系:

public function childrenAccounts()
{
    return $this->hasMany('Account', 'act_parent', 'act_id');
}

public function allChildrenAccounts()
{
    return $this->childrenAccounts()->with('allChildrenAccounts');
}

然后:

$account = Account::with('allChildrenAccounts')->first();

$account->allChildrenAccounts; // collection of recursively loaded children
// each of them having the same collection of children:
$account->allChildrenAccounts->first()->allChildrenAccounts; // .. and so on

这样可以节省大量查询。这将为每个嵌套级别执行1个查询+ 1个额外查询。

我不能保证它对您的数据有效,您需要对它进行明确的测试。


这适用于没有孩子的帐户:

public function scopeChildless($q)
{
   $q->has('childrenAccounts', '=', 0);
}

然后:

$childlessAccounts = Account::childless()->get();

答案 1 :(得分:2)

我正在做类似的事情。我认为答案是缓存输出,并在数据库更新时清除缓存(假设您的帐户本身没有太大变化?)

答案 2 :(得分:2)

public function childrenAccounts()
{
    return $this->hasMany('Account', 'act_parent', 'act_id')->with('childrenAccounts');
}

此代码返回所有子帐户(重复)

答案 3 :(得分:1)

我们正在做类似的事情,但我们的解决方案就是:

class Item extends Model {
  protected $with = ['children'];

  public function children() {
    $this->hasMany(App\Items::class, 'parent_id', 'id');
 }
}

答案 4 :(得分:0)

供以后参考:

public function parent()
{
    // recursively return all parents
    // the with() function call makes it recursive.
    // if you remove with() it only returns the direct parent
    return $this->belongsTo('App\Models\Category', 'parent_id')->with('parent');
}

public function child()
{
    // recursively return all children
    return $this->hasOne('App\Models\Category', 'parent_id')->with('child');
}

这是针对具有Category的{​​{1}}模型。这是数据库迁移代码:

id, title, parent_id

答案 5 :(得分:0)

我创建了一个使用通用表表达式(CTE)来实现递归关系的包:https://github.com/staudenmeir/laravel-adjacency-list

您可以使用descendants关系来递归获取帐户的所有子帐户:

class Account extends Model
{
    use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
}

$allChildren = Account::find($id)->descendants;

答案 6 :(得分:0)

如果您认为这是您始终需要访问的内容,您也可以将此关系添加到模型的“with”属性中:

protected $with = [
    'childrenAccounts'
];

答案 7 :(得分:0)

我在表(用户)中创建了 managed_by 并且这个解决方案可以递归地获得所有无限级别的孩子。

在[用户]模型中

 public function Childs(){
        return $this->hasMany('App\User', 'managed_by', 'id')->with('Childs');
    }

在 [helpers] 文件中(我的魔法解决方案)

if (!function_exists('user_all_childs_ids')) {
        function user_all_childs_ids(\App\User $user)
        {
            $all_ids = [];
            if ($user->Childs->count() > 0) {
                foreach ($user->Childs as $child) {
                    $all_ids[] = $child->id;
                    $all_ids=array_merge($all_ids,is_array(user_all_childs_ids($child))?user_all_childs_ids($child):[] );
                }
            }
            return $all_ids;
        }
    }