Defining Relationships In Laravel 5.6 and 6.x
I wanted to take a quick break in my barrage of Artisan posts to write about defining one-to-many and many-to-many relationships in Laravel’s Eloquent. Eloquent also supports one-to-one relationships but I felt like those are used so rarely that I could skip it.
One-to-Many
In this example, we’re going to work through setting up a very basic one-to-many relationship where a User
must belong to a Subscriber
.
The Migrations
We’re going to create a single migration for each step of this process. Our first step is to create our subscribers
table.
$ php artisan make:migration create_subscribers_table
Created Migration: 2019_11_02_012306_create_subscribers_table
To keep it simple we’ll create the table with just a name plus the default columns.
public function up()
{
Schema::create('subscribers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->string('name');
});
}
Now we’ll create a migration to add the subscriber_id
column to the users
table.
$ php artisan make:migration add_subscriber_id_to_users_table
Created Migration: 2019_11_02_012405_add_subscriber_id_to_users_table
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->unsignedBigInteger('subscriber_id');
$table
->foreign('subscriber_id')
->references('id')
->on('subscribers');
});
}
The Models
We’re going to be using the User
model that comes with Laravel but we do need to create a Subscriber
model.
$ php artisan make:model Subscriber
Model created successfully.
Now that we have our models we’re going to create a test Subscriber
and User
.
$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.3.9-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman
>>> $item = new \App\Subscriber();
=> App\Subscriber {#3007}
>>> $item->name = "Test";
=> "Test"
>>> $item->save();
=> true
>>>
>>> $user = new \App\User();
=> App\User {#3017}
>>> $user->name = "Scott";
=> "Scott"
>>> $user->email = "Scott";
=> "Scott"
>>> $user->password = "junk";
=> "junk"
>>> $user->subscriber_id = 1;
=> 1
>>> $user->save();
=> true
>>> $user->id
=> 2
In our Subscriber
model we’re going to create a function named users
that uses the hasMany()
helper function to define the relationship.
class Subscriber extends Model
{
/**
* Get the users for the subscriber
*/
public function users()
{
return $this->hasMany('App\User');
}
}
There’s a little bit of magic going on behind the scenes because the hasMany
function call above has some “hidden” parameters so it’s equivalent to:
return $this->hasMany('App\User', 'subscriber_id', 'id');
The subscriber_id
string is determined by taking the name of the class (“subscriber”) and appending “id”. The “id” string is assumed because it’s the default for Eloquent models’ auto incrementing column.
Let’s say we name our subscriber_id
column subId
then we could replace the function call above with:
return $this->hasMany('App\User', 'subId');
Personally, my preference is being able to only pass one parameter so it’s as clean as possible but I like the fact we can override what Eloquent is expecting.
Now that we’ve defined our users()
function we can get an instance of the Subscriber
model and then query for the User
s associated with it using the users
attribute.
>>> $subscriber = \App\Subscriber::find(1);
=> App\Subscriber {#3021
id: 1,
created_at: "2019-11-02 01:38:34",
updated_at: "2019-11-02 01:38:34",
name: "Test",
}
>>> $subscriber->users;
=> Illuminate\Database\Eloquent\Collection {#3003
all: [
App\User {#3018
id: 2,
name: "Scott",
email: "Scott",
email_verified_at: null,
created_at: "2019-11-02 01:41:34",
updated_at: "2019-11-02 01:41:34",
subscriber_id: 1,
},
],
}
>>>
The other thing we need to do is define the inverse of this relationship. So in the User
model we need to add a subscriber()
function:
class User extends Authenticatable
{
/**
* Get the subscriber
*/
public function subscriber()
{
return $this->belongsTo('App\Subscriber');
}
}
Again, there are two default parameters that have sensible defaults and this above function is equivalent to:
return $this->belongsTo('App\Subscriber', 'subscriber_id', 'id');
We’ve now defined the inverse of the relationship we can access the Subscriber
thorough any of it’s User
s:
>>> $subscriber = \App\Subscriber::find(1);
=> App\Subscriber {#3014
id: 1,
created_at: "2019-11-02 01:38:34",
updated_at: "2019-11-02 01:38:34",
name: "Test",
}
>>> $subscriber->users[0]->subscriber;
=> App\Subscriber {#3002
id: 1,
created_at: "2019-11-02 01:38:34",
updated_at: "2019-11-02 01:38:34",
name: "Test",
}
>>>
Many-to-Many
In this example, we’re going to work through setting up a very basic many-to-many relationship where a User
might have multiple Account
s they’re responsible for.
The Migrations
I’m going to be lazy and create the model and migration all in one go.
$ php artisan make:model -m Account
Model created successfully.
Created Migration: 2019_11_02_015336_create_accounts_table
And the related migration is again super simple.
public function up()
{
Schema::create('accounts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->string('companyName');
});
}
Now here is where the magic starts to come in. When Eloquent does it’s many-to-many functions it automatically looks for a table named [firstModel]_[secondModel] where the order is determined by alphabetically. In this example, we’re going to have accounts and users so our pivot table name will need to be account_user
to minimize the amount of work we have to do later. Again, I recommend sticking to the process that generates the cleanest code.
$ php artisan make:migration create_account_user
Created Migration: 2019_11_02_015509_create_account_user
We also need to make sure we have a [firstModel]_id and a [secondModel]_id column in the table. I also setup the foreign key relationships but that’s not explicitly required.
public function up()
{
Schema::create('account_user', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('account_id');
$table
->foreign('user_id')
->references('id')
->on('users');
$table
->foreign('account_id')
->references('id')
->on('accounts');
});
}
I also created three test Account
s for our testing:
$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.3.9-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman
>>> \App\Account::create(['companyName'=>'The Big One']);
=> App\Account {#3003
companyName: "The Big One",
updated_at: "2019-11-02 02:00:57",
created_at: "2019-11-02 02:00:57",
id: 1,
}
>>> \App\Account::create(['companyName'=>'Scott's Awesome Company']);
PHP Parse error: Syntax error, unexpected T_STRING, expecting ',' or ')' or ']' on line 1
>>> \App\Account::create(['companyName'=>'Scott\'s Awesome Company']);
=> App\Account {#3004
companyName: "Scott's Awesome Company",
updated_at: "2019-11-02 02:01:13",
created_at: "2019-11-02 02:01:13",
id: 2,
}
>>> \App\Account::create(['companyName'=>'Awesome Company']);
=> App\Account {#3011
companyName: "Awesome Company",
updated_at: "2019-11-02 02:01:32",
created_at: "2019-11-02 02:01:32",
id: 3,
}
>>>
The Models
In our User
model we’re going to create an accounts()
function that calls the belongsToMany
function with the other model (App\Account in this case) as the parameter.
class User extends Authenticatable
{
/**
* Get the accounts
*/
public function accounts()
{
return $this->belongsToMany('App\Account');
}
}
We also need to create a users()
function in our Account
model that also calls the belongsToMany
function so we can reference these both ways.
class Account extends Model
{
/**
* Get the users
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
Again, this is shorthand for the following:
return $this->belongsToMany('App\User', 'account_user', 'user_id', 'account_id');
Where account_user
is the pivot table and user_id
and account_id
are the columns that determine the relationship.
In case you’re wondering why we didn’t create an Account_User
model it’s because we don’t need one. Eloquent is smart enough to handled this all on it’s own without an extra class mucking up our source code.
Now that we have all these functions setup to define the relationships between the models we can use the attach()
function to link a user to an account:
$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.3.9-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman
>>> $user = \App\User::find(2);
=> App\User {#3014
id: 2,
name: "Scott",
email: "Scott",
email_verified_at: null,
created_at: "2019-11-02 01:41:34",
updated_at: "2019-11-02 01:41:34",
subscriber_id: 1,
}
>>> $user->accounts()->attach(\App\Account::find(3));
=> null
And then we can use the accounts
property to see all the Account
s tied to the User
.
>>> $user->accounts
=> Illuminate\Database\Eloquent\Collection {#3023
all: [
App\Account {#3013
id: 3,
created_at: "2019-11-02 02:01:32",
updated_at: "2019-11-02 02:01:32",
companyName: "Awesome Company",
pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3021
user_id: 2,
account_id: 3,
},
},
],
}
>>>
And the opposite:
>>> $account = \App\Account::find(1);
=> App\Account {#3029
id: 1,
created_at: "2019-11-02 02:00:57",
updated_at: "2019-11-02 02:00:57",
companyName: "The Big One",
}
>>> $account->users()->attach($user);
=> null
>>> $account->users
=> Illuminate\Database\Eloquent\Collection {#3032
all: [
App\User {#3025
id: 2,
name: "Scott",
email: "Scott",
email_verified_at: null,
created_at: "2019-11-02 01:41:34",
updated_at: "2019-11-02 01:41:34",
subscriber_id: 1,
pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3011
account_id: 1,
user_id: 2,
},
},
],
}
>>>
There is also a detach()
function that’s the opposite of the attach()
function:
>>> $account->users()->detach($user);
=> 1
>>> $account->users
=> Illuminate\Database\Eloquent\Collection {#3029
all: [],
}
>>>
attach()
and detach()
will also work with an id instead of an object:
$shop->products()->attach($product_id);
And you can also use the sync()
function to specify a list of relationships and Eloquent will insert the missing values:
// adds missing values
$shop->products()->sync([1,2,3]);
Order By
As a quick note this process allows you to order the results in case you need them sorted:
>>> $user->accounts()->orderBy('companyName')->get()
=> Illuminate\Database\Eloquent\Collection {#3002
all: [
App\Account {#3023
id: 3,
created_at: "2019-11-02 02:01:32",
updated_at: "2019-11-02 02:01:32",
companyName: "Awesome Company",
pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3011
user_id: 2,
account_id: 3,
},
},
App\Account {#3028
id: 1,
created_at: "2019-11-02 02:00:57",
updated_at: "2019-11-02 02:00:57",
companyName: "The Big One",
pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3004
user_id: 2,
account_id: 1,
},
},
],
}
>>>
Where
What if you have a slightly more complicated relationship with your Account
s? For example, you might have a sales representative who closed the Account
and an account representative who helps them if they have problems. Eloquent has a solution for this.
First where going to add two columns to our account_user
table.
$ php artisan make:migration add_fields_to_account_user
Created Migration: 2019_11_02_105825_add_fields_to_account_user
public function up()
{
Schema::table('account_user', function (Blueprint $table) {
$table->boolean('sales_rep')->default('0');
$table->boolean('account_rep')->default('0');
});
}
Now in our account table we can define these relationship in some new functions:
public function salesReps()
{
return $this->belongsToMany('App\User')->wherePivot('sales_rep', 1);
}
public function accountReps()
{
return $this->belongsToMany('App\User')->wherePivot('account_rep', 1);
}
Then when we attach the User
s to the Account
s we need to specify the column values:
>>> $account->users()->attach($salesRep, ['sales_rep' => 1]);
=> null
>>> $account->users()->attach($accountRep, ['account_rep' => 1]);
=> null
>>>
Then we can use the salesReps
and accountReps
attributes to access the User
s just like we used the users
before:
>>> $account->salesReps
=> Illuminate\Database\Eloquent\Collection {#3030
all: [
App\User {#3020
id: 3,
name: "Sales Rep",
email: "Sales Rep",
email_verified_at: null,
created_at: "2019-11-02 11:04:16",
updated_at: "2019-11-02 11:04:16",
subscriber_id: 1,
pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3021
account_id: 1,
user_id: 3,
},
},
],
}
>>> $account->accountReps
=> Illuminate\Database\Eloquent\Collection {#3044
all: [
App\User {#3034
id: 5,
name: "Account Rep",
email: "Account Rep",
email_verified_at: null,
created_at: "2019-11-02 11:05:05",
updated_at: "2019-11-02 11:05:05",
subscriber_id: 1,
pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3038
account_id: 1,
user_id: 5,
},
},
],
}
>>>
This is really only scratching the surface of what can be done with relationship in Eloquent but I think it covers most of the important parts.
Scott Keck-Warren
Scott is the Director of Technology at WeCare Connect where he strives to provide solutions for his customers needs. He's the father of two and can be found most weekends working on projects around the house with his loving partner.
Top Posts
- Working With Soft Deletes in Laravel (By Example)
- Fixing CMake was unable to find a build program corresponding to "Unix Makefiles"
- Upgrading to Laravel 8.x
- Get The Count of the Number of Users in an AD Group
- Multiple Vagrant VMs in One Vagrantfile
- Fixing the "this is larger than GitHub's recommended maximum file size of 50.00 MB" error
- Changing the Directory Vagrant Stores the VMs In
- Accepting Android SDK Licenses From The OSX Command Line
- Fixing the 'Target class [config] does not exist' Error
- Using Rectangle to Manage MacOS Windows