Let’s say I have a model called Research
. Each research belongsToMany Location
models. And each Location
model BelongsToMany Contact
models. BUT, each Contact
is also related to Research
.
class Research extends Model {
protected $table = 'researches';
public function locations()
{
return BelongsToMany( Location::class, 'research_locations_list', 'research_id', 'location_id' );
}
}
class Location extends Model {
protected $table = 'locations';
public function researches()
{
return BelongsToMany( Research::class, 'research_locations_list', 'research_id', 'location_id' );
}
public function contacts()
{
return BelongsToMany( Contact::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
}
class Contact extends Model {
protected $table = 'contacts';
public function locations()
{
return BelongsToMany( Location::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
}
researches
table:
+----+------------+
| id | research |
+----+------------+
| 1 | Research 1 |
| 2 | Research 2 |
+----+------------+
locations
table:
+----+---------------+
| id | location |
+----+---------------+
| 1 | United States |
| 2 | Great Britain |
| 3 | Germany |
+----+---------------+
contacts
table:
+----+---------+
| id | contact |
+----+---------+
| 1 | Jack |
| 2 | John |
| 3 | Hanz |
+----+---------+
research_locations_list
table:
+----+-------------+-------------+
| id | research_id | location_id |
+----+-------------+-------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 2 |
| 4 | 2 | 3 |
+----+-------------+-------------+
So Research 1 is being conducted in United States and Great Britain, Research 2 in Great Britain and Germany
location_contacts_list
table:
+----+-------------+------------+-------------+
| id | location_id | contact_id | research_id |
+----+-------------+------------+-------------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 2 | 1 | 2 |
| 4 | 3 | 3 | 2 |
+----+-------------+------------+-------------+
Research 1 should have Jack and John as contacts in United States and no contacts elsewhere;
Research 2 should have John as contact in Great Britain and Hanz in Germany;
Now, with lazy load I can achieve that:
$researches = Research::all();
foreach( $researches as $research )
{
foreach( $research->locations as $location )
{
$contacts = $location->contacts()->wherePivot( 'research_id', $research->id )->get();
// Will return John and Jack in United States for Research 1 and John in Great Britain and Hanz in Germany for Research 2
}
}
Now, the question is: how do I achieve this with eager loading?
$researches = Research::with( 'locations.contacts' )->all();
foreach( $researches as $research )
{
foreach( $research->locations as $location )
{
$contacts = $location->contacts;
// Will return John and Jack in United States, John in Great Britain ( which is not supposed to happen ) for Research 1 and John in Great Britain and Hanz in Germany for Research 2
}
}
Perhaps I can instruct somehow for contacts to respect ancestor id? Like:
$research = Research::with( 'locations.contacts' )->where( 'researches.id = location_contacts_list.research_id' )->all();
UPDATE
The closest I came up to solving this is modifying the Location
model like this:
class Location extends Model {
protected $table = 'locations';
public function researches()
{
return BelongsToMany( Research::class, 'research_locations_list', 'research_id', 'location_id' );
}
public function contacts()
{
return BelongsToMany( Contact::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
// Modify contacts attribute getter
public function getContactsAttribute()
{
$contacts = $this->contacts();
if( !empty( $this->pivot->research_id ) )
{
$contacts = $contacts->wherePivot( 'research_id', $this->pivot->research_id );
}
return $contacts->get();
}
}
But it looks kind of dirty…
3
Answers
If I got it right, you want to add some conditions inside your
with
statement. If you want to use eloquent syntax, you can do it like this:Keep in mind that since inside
with
you use nested relationships, likelocations.contacts
, thewhere
function inside the query, will filter only the last model (in this case that would becontacts
). If you want to filter both locations and contacts based on some conditions, you have to write something similar to this (just an example):In order to do that though, you need to create a relationship also with your pivot table (if you want to use it also inside the conditions). Otherwise, you have to use a different syntax, using joins. Check this page from docs for query builders https://laravel.com/docs/9.x/queries#main-content
In your solution you get N+1 query problem. I can suggest the following solution:
here there will always be only 3 queries to the database. And only necessary models will be loaded
Perhaps, this https://laravel.com/docs/9.x/eloquent-relationships#has-many-through helps you. You should try to this