Has Many Through ... Pivot
Has Many Through Pivot means: accessing a distant “one to many” relationship though another relationship that uses a Pivot table (even a polymorphic one) to connect.
Imagine a site where:
- users (
User
model from Winter.user plugin) can belongs to many companies (Company
model) - and each
Company
has manySalePoint
How we can attach a SalePoint
to a User
? A CompanyUser
pivot model will help us.
It’s basically a Many-To-Many relation. There are many Users, and many Companies.
First step: Make a CompanyUser Pivot
Doing this allows to access directly to the pivot table information without the need of raw DB calls. You can use Artisan to make it, or just write it down.
php artisan create:model Acme.CompanyUserPivot -p --no-migration
<?php
namespace Acme\MyPlugin\Models;
use Winter\Storm\Database\Pivot;
/**
* Company-User Pivot Model
*/
class CompanyUserPivot extends Pivot
{
/**
* @var string The database table used by the model.
*/
public $table = 'acme_myplugin_company_user';
public $belongsTo = [
'user' => \Winter\User\Models\User::class,
'company' => \Acme\MyPlugin\Models\Company::class,
];
public $hasManyThrough = [
'salepoints' => [
\Acme\MyPlugin\Models\SalePoint::class,
'through' => \Acme\MyPlugin\Models\Company::class,
]
];
}
Second Step: Company Model
Here we need to tell the Company
model that we will use our newly created CompanyUserPivot
model to connect it to the User model.
// Plugins/Acme/MyPlugin/Models/Company.php
// Add used class
use Winter\User\Models\User;
// Add relationship
public $belongsToMany = [
'users' => [
User::class,
'table' => 'acme_myplugin_company_user',
'pivotModel' => 'Acme\MyPlugin\Models\CompanyUserPivot',
],
];
Third Step: Define the inverse for the User Model
As the User
model is already defined, we need to extend it to add relations from our plugin's boot()
method.
// Plugins/Acme/MyPlugin/Plugin.php
// Add used class
use Acme\MyPlugin\Models\Company;
use Winter\User\Models\User;
// Extend User relationships
public function boot(): void
{
User::extend(function ($model) {
$model->belongsToMany = array_merge(
$model->belongsToMany, [
'companies' => [
Company::class,
'table' => 'acme_myplugin_company_user',
'pivotModel' => 'Acme\MyPlugin\Models\CompanyUserPivot',
'scope' => 'isActive'
],
]
);
});
}
Last Step: Add a touch of magic
Don’t close our Plugin.php
model in your editor just yet!
Now we have to tell our User
model to access the SalePoint
one using a Has Many Through relationship, but not from Company
model, but rather our CompanyUserPivot
model.
// Plugins/Acme/MyPlugin/Plugin.php
// Add used class
use Acme\MyPlugin\Models\Company;
use Acme\MyPlugin\Models\SalePoint;
use Winter\User\Models\User;
// Extend User relationships
public function boot(): void
{
User::extend(function ($model) {
$model->belongsToMany = array_merge(
$model->belongsToMany, [
'companies' => [
Company::class,
'table' => 'acme_myplugin_company_user',
'pivotModel' => 'Acme\MyPlugin\Models\CompanyUserPivot',
'scope' => 'isActive'
],
]
);
$model->hasManyThrough = array_merge(
$model->hasManyThrough, [
'salepoints' => [
SalePoint::class,
'through' => CompanyUserPivot::class,
'key' => 'user_id',
'throughKey' => 'company_id',
'otherKey' => 'id',
'secondOtherKey' => 'company_id',
]
]
);
});
}
With that, you can access to the "SalePoints" from the User
model with no problems at all, no matter how many sale points it may have.
As a bonus, we aren’t creating any new class hard to maintain, only what Winter gives us.
There are no comments yet
Be the first one to comment