Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel Recursive Relationships

I'm working on a project in Laravel. I have an Account model that can have a parent or can have children, so I have my model set up like so:

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

This works fine. What I want to do is get all children under a certain account. Currently, I'm doing this:

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

This also works, but I have to worry if it's slow. This project is the re-write of an old project we use at work. We will have several thousand accounts that we migrate over to this new project. For the few test accounts I have, this method poses no performance issues.

Is there a better solution? Should I just run a raw query? Does Laravel have something to handle this?

In summary What I want to do, for any given account, is get every single child account and every child of it's children and so on in a single list/collection. A diagram:

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

If I run A->immediateChildAccounts(), I should get {B, C}
If I run A->allChildAccounts(), I should get {B, D, C, E, F} (order doesn't matter)

Again, my method works, but it seems like I'm doing way too many queries.

Also, I'm not sure if it's okay to ask this here, but it is related. How can I get a list of all accounts that don't include the child accounts? So basically the inverse of that method above. This is so a user doesn't try to give an account a parent that's already it's child. Using the diagram from above, I want (in pseudocode):

Account::where(account_id not in (A->allChildAccounts())). So I would get {G, H}

Thanks for any insight.

like image 770
Troncoso Avatar asked Oct 30 '14 12:10

Troncoso


2 Answers

This is how you can use recursive relations:

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

Then:

$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 

This way you save a lot of queries. This will execute 1 query per each nesting level + 1 additional query.

I can't guarantee it will be efficient for your data, you need to test it definitely.


This is for childless accounts:

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

then:

$childlessAccounts = Account::childless()->get(); 
like image 134
Jarek Tkaczyk Avatar answered Sep 21 '22 15:09

Jarek Tkaczyk


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

This code returns all children accounts (recurring)

like image 33
George Avatar answered Sep 23 '22 15:09

George