Using WhereHas in Laravel Polymorphic Relations
It seems like once every project I find myself googling "Laravel Polymorphic WhereHas", combing through one result after another, frustratedly repeating "How do I do this???".
While there isn't a first-party supported solution, I wanted to document what has been working for me, so I don't have to continue searching StackOverflow!
Disclaimer: You will not want to use the following approach if your polymorphic relationship is linking many different model types, for reasons you will soon see. This works best in situations where you are only connecting a small, limited number of model variations.
If you are reading this, you already know the beauty of a polymorphic relationship is that it isn't constrained to a single model. By using two columns, related_id
and related_type
, we are able to link up any number of different models.
Caveat
It appears that the reason Laravel doesn't natively support WhereHas
on polymorphic relationships is that the very nature of a polymorphic relationship doesn't limit the type, or more specifically, the number of types, the relationship links to. Therefore, it has no idea which tables to query, or whether the tables even have the column(s) you are desiring to query against. It's a logical limitation, not necessarily just a coding challenge.
Scenario
Even though there isn't a prescribed Laravel approach, there are ways we can use a query constraint on a polymorphic relationship. For example, recently I needed to fetch all the Sponsorable
models that had either a Bill
or a List
tied to a particular Term
. Because the Sponsorable
model was referencing other models that I considered "sponsorable", specifically a Bill
or a List
, a sponsorable
polymorphic relationship made sense for the database structure.
However, in the cases where you only have a handful of potential polymorphised1 models, AND you know that the column(s) are available on all the tables, you can use the following to simulate the WhereHas
.
See the sponsorable_type
and sponsorable_id
in the diagram below? That's the polymorphic relationship we are going to talk about.
Sponsorable model setup
On the Sponsorable
model we add the following relationships, one for each of the potential polymorphic types:
public function bills()
{
return $this->belongsTo(Bill::class, 'sponsorable_id')
->whereSponsorableType(Bill::class);
}
public function lists()
{
return $this->belongsTo(List::class, 'sponsorable_id')
->whereSponsorableType(List::class);
}
Polymorphic WhereHas
Then, when we want to get all sponsorable
s that have either a Bill
or a List
related to a specific Term
, we may use the following Eloquent call:
Sponsorable::with('sponsorable')
->whereHas('bills', function ($query) use ($term) {
$query->where([
'term_id' => $term->id
]);
})->orWhereHas('lists', function ($query) use ($term) {
$query->where([
'term_id' => $term->id
]);
})->get()
A couple things to note:
- We are eager-loading the
sponsorable
MorphTo
to prevent duplicate queries. - Each potential "type" of polymorphic requires an additional
BelongsTo
relationship definition. This is what allows us access to theWhereHas
statement.
Conclusion
As you can see, this won't work for every situation, especially for when you have an unknown number of potential linked model types, but when you only have a few models, and you know the column(s) like I've demonstrated, it works well!
Update - May 21, 2019
Tim MacDonald, a friend from Twitter, added a gist that illustrates a way to implement the techniques above in a more dynamic fashion. Check it out here!
Want to read more tips and insights on working with a Laravel development team that wants to help your organization grow for good? Sign up for our bimonthly newsletter.
By Jesse Schutt
Director of Engineering
Jesse is our resident woodworker. His signature is to find the deeper meaning in a project and the right tool for the job.