framework: whereHas() not working with MorphTo()

The whereHas() method for querying a polymorphic relationship doesn’t seem to be working.

Here are the relationships:

Transaction.php

public function transactionable()
    {
        return $this->morphTo('transactionable');
    }

Reservation.php

public function transactions()
    {
        return $this->morphMany('Transaction', 'transactionable');
    }

Project.php

public function transactions()
    {
        return $this->morphMany('Transaction', 'transactionable');
    }

And here is what I am trying to do:

return Transaction::whereHas('transactionable', function($q){
        return $q->where('id', '=', '1');
    })->get();

Result:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'transactions.' in 'where clause' (SQL: select * from `transactions` where (select count(*) from `transactions` where `transactions`.`transactionable_id` = `transactions`.`` and `id` = 1) >= 1)

Is this possible?

About this issue

  • Original URL
  • State: closed
  • Created 10 years ago
  • Reactions: 1
  • Comments: 78 (18 by maintainers)

Most upvoted comments

Looks like this is a no-fix then.

I came up with BelongsToMorph relationship type, and got whereHas working. Obviously, it only works on a single type, so you need to have explicit relationship declaration for each type.

// Model
class Comment extends Model
{
    public function post()
    {
        return BelongsToMorph::build($this, Post::class, 'commentable');
    }

    public function note()
    {
        return BelongsToMorph::build($this, Note::class, 'commentable');
    }
}

Source: https://gist.github.com/xxzefgh/3022fee8afa53e45a6b89da3f16b3815

As a temporary workaround, you could declare a relationship for each type.

Transaction.php

    public function transactionable()
    {
        return $this->morphTo('transactionable');
    }
    public function reservation()
    {
        return $this->belongsTo(Reservation::class, 'transactionable_id')
            ->where('transactions.transactionable_type', Reservation::class);
    }
    public function project()
    {
        return $this->belongsTo(Project::class, 'transactionable_id')
            ->where('transactions.transactionable_type', Project::class);
    }

Reservation.php

    public function transactions()
    {
        return $this->morphMany(Transaction::class, 'transactionable');
    }

Project.php

    public function transactions()
    {
        return $this->morphMany(Transaction::class, 'transactionable');
    }

And then query the model like this:

    return Transaction::whereHas('reservation', function($q){
        return $q->where('id', '=', '1');
    })->orWhereHas('project', function($q){
        return $q->where('id', '=', '1');
    )->get();

It’s not perfect but worked for me so I though I should share it in case someone has the same use case.

This party never going to end…

If someone is having this issue, here is a workaround : perform a filter on the collection after the where requests. Using the example given by nkeena at the beginning of this thread :

return Transaction::where(...)->get()->filter(function ($value, $key) {
    return $value->transactionable->myField == 'myValue';
});

It doesn’t do exactly the same thing as a whereHas would : it gets more Transaction items in database than necessary. But it works.

Same problem with 5.6

@dihalt This issue was closed while ago. I’m still using workaround code provided here by others, and modified by me. Laravel is now at version 5.4 and problem still exists. Maybe you could open new issue ?

Laravel 5.8.27 adds whereHasMorph(): #28928

Did you managed to get this working? I’m having the same problem.

Having had this problem a while ago, we wrote a composer package that extends the Eloquent Builder class and implements a work around for this issue. It’s not perfect but it does the trick.

https://github.com/rapidwebltd/Improved-Polymorphic-Eloquent-Builder

It’s currently designed for Laravel 5.1, but shouldn’t be too hard to adapt for Laravel 5.5. Contributions welcome!

So, no official fix for this? 😦 Now I hate polymorphic tables 😛

Another workaround:

Add to Transaction.php

public function reservation()
    {
        return $this->blongsTo('Reservation::class');
    }
public function project()
    {
        return $this->blongsTo('Project::class');
    }

And do:

return Transaction::where('transactionable', 'reservation')->whereHas('reservation', function($q){
        return $q->where('id', '=', '1');
    })->get();

Still having this issue in Laravel 5

I worked out a stupid solution:

Comment morphTo Post and Tutorial:

public function scopeCommentableExists($query)
{
    return $query->where(function ($query) {
        $query->where(function ($query) {
                $query->where('commentable_type', 'Post')
                    ->has('post');
            })
            ->orWhere(function ($query) {
                $query->where('commentable_type', 'Tutorial')
                    ->has('tutorial');
            });
    });
}

It’s very strange, that solution still is absent in official package. Hey, Taylor, want you think about this problem?

An easier workaround, without the need to change Laravel code, is to use $query->has('relation', '>', 0) instead of the (implicit) $query->has('relation', '>=', 1). That also avoids the exists query because shouldRunExistsQuery specifically checks for >= 1:

// From Illuminate\Database\Eloquent\Builder.php

protected function shouldRunExistsQuery($operator, $count)
{
    return ($operator === '>=' && $count === 1) || ($operator === '<' && $count === 1);
}

Note however that the performance will be bad! So to alleviate that, first limit your result set and then use count instead of exists:

MyModel::has('relation', '>', 0)->where(...)->get(); // slow!
MyModel::where(...)->has('relation', '>', 0)->get(); // much faster.

Or, when you use scopes like function scopeHavingRelations():

MyModel::where(...)->havingRelations()->get();

Hello Friends

I got solution in Laravel official document pleas check below link.

Solution Click Here >>>>

I hope this link will be usefull for you.

Just upgraded to Laravel 5.6, this bug is still present. Can please we re-open this issue?

I searched how to fix where query with morphed table, and come to here and not solved yet 🥉

I came across this issue today in Laravel 5.5. I’ll be joining the workaround crew for now.

Pleeease

@mk-relax This wont work.

I tried

$model = $model->has('advertable', '>', 0, 'and', function ($q) {
    $q->where('area', '>=', $this->fromArea);
});

And laravel does not know which polymorph table to use. It inserts laravel_reserved_0 instead of related table name. Attached error:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'area' in 'where clause' (SQL: select count(*) as aggregate from `adverts` where (select count(*) from `adverts` as `laravel_reserved_0` where `laravel_reserved_0`.`id` = `laravel_reserved_0`.`advertable_id` and `area` >= 40 and `laravel_reserved_0`.`deleted_at` is null) > 0 and `laravel_reserved_0`.`deleted_at` is null)

What about throwing a better exception when it happens? Also would like to see a work-around 😃 Also, what about maybe passing a parameter that’s an array of possible table/model names?

On Thu, Feb 5, 2015 at 10:52 AM, Sebastiaan Luca notifications@github.com wrote:

Err. Any workaround then?

— Reply to this email directly or view it on GitHub https://github.com/laravel/framework/issues/5429#issuecomment-73103451.

I think this kinda impossible currently, because if you see the MorphTo class were derived from BelongsTo relation class, but in BelongsTo model relationship declaration of the model or foreign key were explicitly configured, whereby in MorphTo relationship is not pointing to related model.

This polymorphism relationship only fully capable when the model instance/object were loaded whereby the data_type value that pointing to related model class can be obtained. And in whereHas situation, the builder class need to gather all the query related information such as filter, binding and etc to be use later on query generation and execution. It’s impossible to create a queries without knowing the kind row/data type that will be retrieve later

You can have a look at file Eloquent\Relations\BelongsTo.php, method ‘getRelationCountQuery’ this is where everything is falling apart.

I’m sure @taylorotwell and others core developer were fully aware about this issue, but it might be too hacky to make this possible. Anyway it’s just my opinion.