** Updated post: I think this is actually impossible using Eloquent, so I have taken a different approach, thanks anyway! **
Consider the following simplified tables:
id | name |
---|---|
1 | news |
2 | review |
id | name |
---|---|
8 | halo |
9 | gta |
id | name | game_id |
---|---|---|
14 | halo for ps3 | 8 |
15 | halo for wii | 8 |
16 | gta for ps4 | 9 |
content_id | release_id | game_id |
---|---|---|
1 | 14 | 8 |
1 | 15 | 8 |
2 | 15 | 8 |
2 | 16 | 9 |
content
items can be linked to many releases
via the content_games
table.releases
belong to one game
content
items to releases
they are also inherently linked to games
.My models look like this:
class Content extends \Eloquent
{
public function games()
{
return $this->belongsToMany('Models\Game', 'content_game');
}
}
class Game extends \Eloquent
{
public function releases()
{
return $this->belongsToMany('Models\Release', 'content_game');
}
}
class Release extends \Eloquent
{
public function content()
{
return $this->belongsToMany('Models\Content', 'content_game');
}
}
I am trying to write a relatively quick lazy-loaded eloquent query that can get me content, with related games nested under that, and then related releases under that. So I want the output array to look something like this:
Array
(
[0] => Array
(
[id] => 1
[name] => News
[games] => Array
(
[0] => Array
(
[id] => 8
[name] => Halo
[releases] => Array
(
[0] => Array
(
[id] => 14
[name] => Halo for PS3
)
[1] => Array
(
[id] => 15
[name] => Halo for Wii
)
)
)
)
)
[1] => Array
(
[id] => 2
[name] => Review
[games] => Array
(
[0] => Array
(
[id] => 8
[name] => Halo
[releases] => Array
(
[0] => Array
(
[id] => 15
[name] => Halo for Wii
)
)
)
[1] => Array
(
[id] => 9
[name] => GTA
[releases] => Array
(
[0] => Array
(
[id] => 16
[name] => GTA for PS4
)
)
)
)
)
)
Content::with('games', 'games.releases')->toArray();
does not work sufficiently with the 3 column table content_releases
joining them all together, as Eloquent only queries based on two of the columns. As such I need to add a constraint games.releases.
It works (code below), but it's not at all efficient:
Content::with(['games' => function ($query) {
$query->groupBy(['game_id','game.id','pivot_content_id']);
},
'games.releases' => function ($query) {
$query->has('content');
}]);
It produces the following queries:
select * from "content" limit 10 offset 0
select "game".*, "content_game"."content_id" as "pivot_content_id", "content_game"."game_id" as "pivot_game_id"
from "game"
inner join "content_game" on "game"."id" = "content_game"."game_id"
where "content_game"."content_id" in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
group by "game_id", "game"."id", "pivot_content_id"
select "releases".*, "content_game"."game_id" as "pivot_game_id", "content_game"."release_id" as "pivot_release_id"
from "releases"
inner join "content_game" on "releases"."id" = "content_game"."release_id"
where "content_game"."game_id" in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
and (
select count(*)
from "content"
inner join "content_game" on "content"."id" = "content_game"."content_id"
where "content_game"."release_id" = "releases"."id"
) >= 1
Can anyone suggest a more efficient way to approach this with the same output?
Thanks.
yeah, this question is a little bit dated but I struggled with this myself a while ago and a proper answer might be useful to others :)
There are a few issues with the way you setup your database tables and relationships so lets get them out of the way first:
If I understand correctly, there is a many-to-many relationship between content and games, and a one-to-many relationship between games and releases. Therefor you would create a pivot table for the relationship between content and games and use a foreign key on the releases table. This means renaming the pivot (for clarity) and dropping the release_id column.
Once that is done we have to update the model relationships. Content belongsToMany Game, Game belongsToMany Content and Game hasMany Release.
Now it is just a matter of using nested eloquent relationships, something that might have been added to the documentation since then and.. we are done.
Content::with(['games', 'games.releases'])->get()->toArray();
Try this..
Content::with('games')->with('releases')->with('content')->get()->toArray();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With