How to use Laravel's hasManyThrough across 4 tables

后端 未结 3 476
栀梦
栀梦 2021-01-05 15:08

I have 4 tables with a structure and flow like this:

User
Accounts
Contacts
Orders

The relationship is as follows:

$user->hasMany(\

相关标签:
3条回答
  • 2021-01-05 15:24

    you can through from user to contact then join with Orders

    public function orders(){
       return $this->hasManyThrough('Contact', 'Account', 'owner_id')->join('orders','contact.id','=','orders.contact_ID')->select('orders.*');
    }
    

    It works for me in same case, all feedback welcome

    0 讨论(0)
  • 2021-01-05 15:25

    I created a HasManyThrough relationship with unlimited levels: Repository on GitHub

    After the installation, you can use it like this:

    class User extends Model {
        use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
    
        public function orders() {
            return $this->hasManyDeep(Order::class, [Account::class, Contact::class]);
        }
    }
    
    0 讨论(0)
  • 2021-01-05 15:29

    It was quite difficult at first to identify exactly how the query builder slices things together. I knew how I wanted to format the query with a raw SQL statement, but I wanted to use Eloquent for this particular task given the other defined relationships.

    I was able to resolve this by overloading the attribute and manually creating the relation. However, the actual query and hasMany needs to be built manually as well. Let's take a look at how I achieved this. Remember, the goal is to get Orders off of the user through 2 has-Many relationships.

    First, the overloading of the attribute.

    public function getOrdersAttribute()
    {
        if( ! array_key_exists('orders', $this->relations)) $this->orders();
    
        return $this->getRelation('orders');
    }
    

    The idea in the above function is to capture when a lazy loaded ->orders is called. such as $user->orders. This checks to see if the orders method is in the relations array for the existing User model. If it's not, then it calls our next function in order to populate the relatioship, and finally returns the relationship we've just created.

    This is what the function that actually queries the Orders looks like:

    public function orders()
    {
        $orders = Order::join('contacts', 'orders.contact_id', '=', 'contacts.id')
            ->join('accounts', 'contacts.account_id', '=', 'accounts.id')
            ->where('accounts.owner_id', $this->getkey())
            ->get();
    
        $hasMany = new Illuminate\Database\Eloquent\Relations\HasMany(User::query(), $this, 'accounts.owner_id', 'id');
    
        $hasMany->matchMany(array($this), $orders, 'orders');
    
        return $this;
    }
    

    In the above, we tell the Orders table to join it's contacts (which is the established route given the ownership of belongsTo() in this scenario). Then from the contacts, we join the accounts, then from the accounts we can get there from our user by matching our owner_id column against our existing $user->id, so we don't need to do anything further.

    Next, we need to manually create our relationship by instantiating an instance of hasMany from the Eloquent Relationship builder.

    Given that the HasMany method actually extends the HasOneOrMany abstract class, we can reach the HasOneOrMany abstract class by passing our arguments directly to HasMany, like below:

    $hasMany = new Illuminate\Database\Eloquent\Relations\HasMany(User::query(), $this, 'accounts.owner_id', 'id');
    

    The HasOneOrMany expects the following to it's constructor:

    Builder $query,
    Model $parent,
    $foreignKey, 
    $localKey
    

    So for our builder query, we've passed an instance of our model that we wish to establish the relationship with, the 2nd argument being an instance of our Model ($this), the 3rd argument being the foreign key constraint from our Current->2nd model, and finally the last argument being the column to match from our current model against the foreign key constraint on our Current->2nd model.

    Once we've created our instance of Relation from our HasMany declaration above, we then need to match the results of the relationship to their many parents. We do this with the matchMany() method which accepts 3 arguments:

    array $models,
    Collection $results,
    $relation
    

    So in this case, the array of models would be an array instance of our current eloquent model (user) which can be wrapped in an array to achieve our effect.

    The 2nd argument would be the result of our intitial $orders query in our orders() function.

    Finally, the 3rd argument will be the relation string that we wish to use to fetch our instance of this relationship; which for us is order.

    Now you can correct use either Eloquent or Lazy Loading to fetch our orders for our user.

    User::find(8)->orders();
    
    $user->orders;
    

    Hopefully this is helpful for someone else facing a similar issue.

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