Laravel递归关系

Tro*_*oso 35 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');
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

有更好的解决方案吗?我应该只运行原始查询吗?是否Laravel有东西来处理呢?

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

A -> B -> D
|--> C -> E
     |--> F 
G -> H
Run Code Online (Sandbox Code Playgroud)

如果我运行A-> immediateChildAccounts(),我应该得到{B,C}
如果我运行A-> allChildAccounts(),我应该得到{B,D,C,E,F}(顺序无所谓)

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

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

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

感谢您的任何见解.

Jar*_*zyk 68

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

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

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

然后:

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

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

我无法保证它对您的数据有效,您需要明确地进行测试.


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

public function scopeChildless($q)
{
   $q->has('childrenAccounts', '=', 0);
}
Run Code Online (Sandbox Code Playgroud)

然后:

$childlessAccounts = Account::childless()->get();
Run Code Online (Sandbox Code Playgroud)

  • 哇。你是天才。我的代码现在快了两倍:) (2认同)
  • 我不知道它在做什么,但是它正在工作! (2认同)
  • @Roark简而言之,这是雄辩的;) (2认同)

小智 13

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

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


Jon*_*eir 10

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

您可以使用该descendants关系以递归方式获取帐户的所有子项:

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

$allChildren = Account::find($id)->descendants;
Run Code Online (Sandbox Code Playgroud)


Vah*_*iri 8

备查:

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

这是针对Category具有id, title, parent_id. 这是数据库迁移代码:

    Schema::create('categories', function (Blueprint $table) {
        $table->increments('id');
        $table->timestamps();
        $table->string('title');
        $table->integer('parent_id')->unsigned()->nullable();
        $table->foreign('parent_id')->references('id')->on('categories')->onUpdate('cascade')->onDelete('cascade');
    });
Run Code Online (Sandbox Code Playgroud)


Mek*_*eki 5

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

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

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