Winter CMS resources and help articles

Simple and to the point. Optimized by the community.

Has Many Through ... Pivot

1
by DamsFX, last modified on May 17th, 2025

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 many SalePoint

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.

Discussion

0 comments

We use cookies to measure the performance of this website. Do you want to accept these cookies?