Viewed   388 times

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.

 Answers

5

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();
Tuesday, November 15, 2022
4

Finally found it.. In the ->get() you have to put the 'staff_id' like this

Vehicle::where('id',1)
            ->with(['staff'=> function($query){
                            // selecting fields from staff table
                            $query->select(['staff.id','staff.name']);
                          }])
            ->get(['id','name','staff_id']);

Since I didn't take the staff_id, it couldn't perform the join and hence staff table fields were not shown.

Thursday, December 22, 2022
 
4

Each group can have multiple contacts, and each contact can have multiple phone numbers, you'll need to define one-to-many relation from group to contact and from contact to phone number:

class Group extends Model {
  public function contacts() {
    return $this->hasMany(Contact::class);
  }
}

class Contact extends Model {
  public function phoneNumbers() {
    return $this->hasMany(PhoneNumber::class);
  }
}

With relations defined, you will be able to load groups, their contacts and contacts' phone numbers with:

$groups  = Group::with(['contacts', 'contacts.phoneNumbers'])->get();

This will give you a collection of groups. Each of groups will contain a collection of contacts in their contacts property. Each of contacts will contain phone numbers collection in their phoneNumbers properties. By iterating through those collections you should be able to get data needed to render the structure you need, e.g.:

@foreach($groups as $group)
  {{ $group->name }}
  @foreach ($group->contacts as $contact)
    {{ $contact->name }}
    @foreach ($contact->phoneNumbers as $number)
      {{ $number->number }}
    @endforeach
  @endforeach
@endforeach

All necessary information on how to model your data with Eloquent can be found in the documentation: https://laravel.com/docs/master/eloquent

Sunday, December 11, 2022
 
gusgard
 
2

I believe this is a perfect use-case for Eloquent events (http://laravel.com/docs/eloquent#model-events). You can use the "deleting" event to do the cleanup:

class User extends Eloquent
{
    public function photos()
    {
        return $this->has_many('Photo');
    }

    // this is a recommended way to declare event handlers
    public static function boot() {
        parent::boot();

        static::deleting(function($user) { // before delete() method call this
             $user->photos()->delete();
             // do the rest of the cleanup...
        });
    }
}

You should probably also put the whole thing inside a transaction, to ensure the referential integrity..

Wednesday, November 30, 2022
 
5

Try out getter method for property which returns merged collections returned from relations.

public function getCompetitionsAttribute($value)
{
    // There two calls return collections
    // as defined in relations.
    $competitionsHome = $this->competitionsHome;
    $competitionsGuest = $this->competitionsGuest;

    // Merge collections and return single collection.
    return $competitionsHome->merge($competitionsGuest);
}

Or you can call additional methods before collection is returned to get different result sets.

public function getCompetitionsAttribute($value)
{
    // There two calls return collections
    // as defined in relations.
    // `latest()` method is shorthand for `orderBy('created_at', 'desc')`
    // method call.
    $competitionsHome = $this->competitionsHome()->latest()->get();
    $competitionsGuest = $this->competitionsGuest()->latest()->get();

    // Merge collections and return single collection.
    return $competitionsHome->merge($competitionsGuest);
}
Saturday, August 6, 2022
 
marwie
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :