Belongs To Through Relations in Laravel 9
How can we "fake" belongs-to-through relations in Laravel using a native has-one-through relationship.
Take the following 3 models:
customer
id - integer
name - string
invoices
id - integer
status - string
customer_id - integer
credit_notes
id - integer
status - string
invoice_id - integer
Laravel gives us an easy way to get all Credit Notes that belong to a particular Customer through the invoice table. What you need to do, is to define a hasManyThrough
relationship on the Customer model, as in:
class Customer extends Model
{
/**
* Get all credit notes of the customer
*/
public function creditNotes()
{
// we could call the below function with just first 2 arguments,
// but I like being explicit when defining 'through' relations.
return $this->hasManyThrough(
CreditNote::class,
Invoice::class,
'customer_id',
'invoice_id',
'id',
'id'
);
}
}
What if we want to find out which Customer a particular Credit Note belongs to? We could call $creditNote->invoice->customer
, but what if we don't want to load the Invoice model in the process? We can use the hasOneThrough
relationship that can also act as a "belongs to through" relationship. The "trick" is to use custmomize arguments 3-6 of the relationship as below:
class CreditNote extends Model
{
/**
* Get the customer for whom the credit note was issued.
*/
public function customer()
{
return $this->hasOneThrough(
Customer::class,
Invoice::class,
'id', // refers to id column on invoices table
'id', // refers to id column on customers table
'invoice_id', // refers to invoice_id column on credit_notes table
'customer_id' // refers to customer_id column on invoices table
);
}
}
Why do we need "Belongs To Through" relationships?
In my case, I use this "trick" a lot when I need to eager-load deep relationship, as in:
App\CreditNote::all()->load('customer');
This saves us from unnecessarily loading Invoices associated with each Credit Note.
So far, I haven't run into any issues with defining "Belongs To Through" relationship this way, but since it's not something that you can find in the official Laravel Docs, I supposed there may be some "gotchas". If you have run into any of them, please let me know in the comments below.