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.

Polymorphic Wherehas Diagram

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 sponsorables 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 the WhereHas 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!