Laravel Recursive Relationships

后端 未结 7 1973
醉梦人生
醉梦人生 2020-11-28 21:22

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         


        
相关标签:
7条回答
  • 2020-11-28 21:31

    I've created a package that uses common table expressions (CTE) to implement recursive relationships: https://github.com/staudenmeir/laravel-adjacency-list

    You can use the descendants relationship to get all children of an account recursively:

    class Account extends Model
    {
        use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
    }
    
    $allChildren = Account::find($id)->descendants;
    
    0 讨论(0)
  • 2020-11-28 21:32

    We're doing something similar, but our solution was this:

    class Item extends Model {
      protected $with = ['children'];
    
      public function children() {
        $this->hasMany(App\Items::class, 'parent_id', 'id');
     }
    }
    
    0 讨论(0)
  • 2020-11-28 21:34

    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();
    
    0 讨论(0)
  • 2020-11-28 21:43

    I think I've come up with a decent solution as well.

    class Organization extends Model
    {
        public function customers()
        {
            return $this->hasMany(Customer::class, 'orgUid', 'orgUid');
        }
    
        public function childOrganizations()
        {
            return $this->hasMany(Organization::class, 'parentOrgUid', 'orgUid');
        }
    
        static function addIdToQuery($query, $org)
        {
            $query = $query->orWhere('id', $org->id);
            foreach ($org->childOrganizations as $org)
            {
                $query = Organization::addIdToQuery($query, $org);
            }
            return $query;
        }
    
        public function recursiveChildOrganizations()
        {
            $query = $this->childOrganizations();
            $query = Organization::addIdToQuery($query, $this);
            return $query;
        }
    
        public function recursiveCustomers()
        {
             $query = $this->customers();
             $childOrgUids = $this->recursiveChildOrganizations()->pluck('orgUid');
             return $query->orWhereIn('orgUid', $childOrgUids);
        }
    
    }
    

    Basically, I'm starting with a query builder relationship and adding orWhere conditions to it. In the case of finding all of the child organizations, I use a recursive function to drill down through the relationships.

    Once I have the recursiveChildOrganizations relationship, I've run the only recursive function needed. All of the other relationships (I've shown recursiveCustomers but you could have many) use this.

    I avoid instantiating the objects at every possible turn, since the query builder is so much faster than creating models and working with collections.

    This is much faster than building a collection and recursively pushing members to it (which was my first solution), and since each method returns a query builder and not a collection, it stacks wonderfully with scopes or any other conditions you want to use.

    0 讨论(0)
  • 2020-11-28 21:47

    For future reference:

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

    This is for a Category model that has id, title, parent_id. Here's the database migration code:

        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');
        });
    
    0 讨论(0)
  • 2020-11-28 21:48
    public function childrenAccounts()
    {
        return $this->hasMany('Account', 'act_parent', 'act_id')->with('childrenAccounts');
    }
    

    This code returns all children accounts (recurring)

    0 讨论(0)
提交回复
热议问题