How to use custom pivot tables in Laravel 6.x

November 18, 2019

Have you run into this before?

Your app’s database table structure is predefined and unalterable. There are lookup tables (called pivot table in Laravel) that create many to many relationships between tables using YOUR naming convention. It makes sense in the context of your application, but you quickly find out that Laravel’s relationship defaults do not match it well.

Lets take this scenario for example:

You have users who are part of organizations, and organizations can have groups in them, created by the users.

There are three primary tables:

  • users
  • organizations
  • groups

Additionally there is a lookup table called organization_groups. The lookup table contains not only the related id, but several other relevant data points, including a field called “created_by_user_id”.

This looks good in theory, however Laravel is going to require the pivot (lookup) table to be named groups_organizations out of the box. In your data structure it logically makes sense to have the table named organization_groups though. Luckily this is entirely possible, though the documentation on how to do it leaves a little to be desired.

Step One: The basic models

Let’s start out by creating the basic models. This can be easily done with Laravel’s artisan command

php artisan make:model Users
php artisan make:model Organizations
php artisan make:model Groups

It should be noted here that I used the build in Laravel users model that comes with the authentication mechanisms. I did not actually run the make:model command for that. You will not need to if you use Laravel’s users either.

Step Two: Related Groups to the Organizations model

This was the most annoying part to figure out, and the least well documented…..

When you load an organization you will be able to access all the groups via a class property. For example:

$org = Organization::find( $id );

$groups = $org->groups;

Laravel is doing some magic behind the scenes here- first, “groups” is not a property. When requested, Laravel checks to see if a corresponding method exists. In this case it looks for “public function groups()”. The method must return a relationship, such as “belongsToMany”.

By default, Laravel will use the “groups_organizations” table, but by messing with parameters I determined how to properly switch the table. Laravel does a lot of magic with parameters- depending on how many you use the meaning of the parameter changes. In our case it looks like

$this->belongsToMany( CLASS, TABLE, KEY-RELATING-THIS-MODEL, KEY-FOR-OTHER-MODEL )

which comes out to

$this->belongsToMany( Groups::class, ‘organization_groups’, ‘organization_id, ‘group_id’ )

Notice the keys are singular. Laravel is expecting them to be plural (like the table name I think) so have have to be explicitly specified here.

Here is the full code to create the relation. This code belongs in the Organizations model.

public function groups() {
   return $this->belongsToMany( Groups::class, ‘organization_groups’, ‘organization_id, ‘group_id’ );
}

Now we will be able to do things such as:

$org = Organizations::find( 42 );

foreach( $org->groups as $group ) {
    echo $group->name;
}

Step Three: Relate a User to the Group

Here each Group has a single User set in the “created_by_user_id” field.

Laravel’s many-to-one relationship used “hasOne”. In the Groups model class we add:

public function createdBy() {
    return $this->hasOne( User::class, 'id', 'created_by_user_id' );
}

Now we can find which user created a group with

$group->createdBy->name;

In the organizational context, we may have a list of all groups and who created them.

$org = Organizations::find( 42 );
foreach( $org->groups as $group ) {
    echo “Group: “ . $group->name . “ - Created by: “ . $group->createdBy->name ;
}

Hope that helps!

Leave a Reply

Your email address will not be published. Required fields are marked *