Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

whereHas() not working with MorphTo() #5429

Closed
nkeena opened this issue Aug 12, 2014 · 78 comments
Closed

whereHas() not working with MorphTo() #5429

nkeena opened this issue Aug 12, 2014 · 78 comments
Labels

Comments

@nkeena
Copy link

nkeena commented Aug 12, 2014

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?

@dorcari
Copy link

dorcari commented Oct 10, 2014

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

@gpyrbris
Copy link

gpyrbris commented Dec 4, 2014

Yep, having the same problem!

@carlosrochap
Copy link

same here

@sebastiaanluca
Copy link
Contributor

Running into the same issue. It doesn't even seem to look at the morph type, it just does a select from the same table.

Any more information on it?

@blankhua
Copy link

I have the same problem. Very sad.

@blankhua
Copy link

I ran the code:

$tasksQuery->whereHas('taskable', function ($q) use ($search) {
    $q->where('title', 'like', "%{$search}");
});

but it throw an exception.

@sebastiaanluca
Copy link
Contributor

I reaallly need this asap, so I'm going to look into it in a few days and try to debug it. I don't know anything about the code, so if anyone could clarify what I should be looking for, go ahead.

@sgelbart
Copy link

sgelbart commented Feb 3, 2015

Yup same here. Wonder if this was ever working?

@wajatimur
Copy link

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.

@GrahamCampbell
Copy link
Member

Looks like this is a no-fix then.

@sebastiaanluca
Copy link
Contributor

Err. Any workaround then?

@sgelbart
Copy link

sgelbart commented Feb 5, 2015

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
#5429 (comment).

@wajatimur
Copy link

You could try to have the Builder class to look ahead for the value of the morphed relation class. Code provided is the only proof of concept, it's not a workaround or a suitable solution. Please do not use in your code/project.

As you can see the line with variable $look_ahead is trying to fetch($parent::first) the data/model instance and get a morphed relation of it.

illuminate/database/Illuminate/Database/Eloquent/Builder.php

    protected function getHasRelationQuery($relation_name)
    {
        return Relation::noConstraints(function() use ($relation_name)
        {
            $relation =  $this->getModel()->$relation_name();

            if( get_class($relation) == 'Illuminate\Database\Eloquent\Relations\MorphTo'){
                $parent = $relation->getParent();
                $look_ahead = $parent::first();
                $relation = $look_ahead->{$relation_name}();
            };

            return $relation;
        });
    }

By using this the whereHas execution will return a value without failing, although it might not returning the right data due to the poor look ahead mechanism. It might be a good start point to explore other possibilities.

@sebastiaanluca
Copy link
Contributor

Thanks @wajatimur! By using an adjusted version of your code, I can use whereHas by providing the …able_type. It only works for a single type, but in a sense, that's only logical. Does what it needs to for me.

/**
 * Get the "has relation" base query instance.
 *
 * @param  string  $relation
 * @return \Illuminate\Database\Eloquent\Builder
 */
protected function getHasRelationQuery($relation)
{
    return Relation::noConstraints(function() use ($relation)
    {
        $name = $relation;
        $relation = $this->getModel()->$relation();

        if (get_class($relation) === 'Illuminate\Database\Eloquent\Relations\MorphTo') {
            $lookAhead = $this->getModel()->where($name . '_type', '=', 'App\Models\Auth\Users\Admin')->first();
            $relation = $lookAhead->{$name}();
        };

        return $relation;
    });
}

Feedback is appreciated.

@wajatimur
Copy link

Hi @quagh, glad its work! A whereHas method will construct COUNT SQL query for each level of whereHas query plus with WHERE query using addWhere method in Builder class if I'm were not mistaken.

By not having to scheme deeper on the Builder class code and i think the only possible solution is to place a placeholder in the generated query and it will refer back to some special method such as getHasRelationQueryMorphed to get an appropriate sub query to be inject. But anyway, its just a theory.

@sebastiaanluca
Copy link
Contributor

I'm not really following when you mention to put a placeholder in the generated query. Can you elaborate on that?

Anyway, I forked the Laravel repo and applied my changes to them. If anyone's interested, check out https://github.com/Quagh/framework/tree/database/fix_whereHas_with_morphTo.

Here's an example of how to use it. You just pass a string or array of strings of types it should look for. In my opinion, that's the only workable way the query builder can gather all the necessary elements to apply the whereHas. Another way would be to query the whole table, check for all different able_types, find the models, check the fields/relationships on there, etc. But not really do-able.

 $this->whereHas('userable', function ($query) use ($id) {
    $query->where('id', '=', $id);
}, '>=', 1, ['App\Models\Contact', 'App\Models\Admin']);

Composer.json:

"require": {
    "laravel/framework": "dev-database/fix_whereHas_with_morphTo as 4.2.17"
},

"repositories": [
    {
        "type": "vcs",
        "url": "https://github.com/Quagh/framework.git"
    }
],

And this is the query it produces when executing the code above (in my app);

select * from `users` where `users`.`deleted_at` is null and ((select count(*) from `admins` where `users`.`userable_id` = `admins`.`id` and `id` = ? and `admins`.`deleted_at` is null and `users`.`userable_type` = ?) >= 1 or (select count(*) from `contacts` where `users`.`userable_id` = `contacts`.`id` and `id` = ? and `contacts`.`deleted_at` is null and `users`.`userable_type` = ?) >= 1)

As you see, it wraps a single whereHasquery in parenthesis. This to make sure where 'users'.'deleted_at' is null applies to all has queries when providing multiple able types.

Trying to run phpunit tests right now to verify it doesn't break anything else. I don't have much experience writing tests myself, so if anyone feels like it, go right ahead :)

Again, feedback / testing appreciated.


OK (1349 tests, 2777 assertions)


@wajatimur @GrahamCampbell

@alexw23
Copy link

alexw23 commented Feb 19, 2015

👍 Same here

@fnfilho
Copy link

fnfilho commented Feb 20, 2015

Yep! Same here!

@blankhua
Copy link

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');
            });
    });
}

@wajatimur
Copy link

Nice try @blankhua, that's a hard-coded recursive query. Anyway can anybody confirm, does this issue still exist in Laravel 5?

@threesquared
Copy link

Still having this issue in Laravel 5

@fourstacks
Copy link

I'm also seeing the same issue in L5 (5.0.30). Would be great to have an official fix soon as the bugs been around for a while. Thanks to @quagh for the interim solution though - I'll check that out

@GrahamCampbell
Copy link
Member

We're open to pull requests. :)

@fourstacks
Copy link

Would love to but it's a level of voodoo slightly above my pay grade! Sorry if I sounded demanding... ; )

@sebastiaanluca
Copy link
Contributor

@GrahamCampbell I think I can do a pull request, though I'd love some feedback on my implementation. It requires an extra variable (and clutters the morph class a bit), but it's the only viable solution I've come up with so far. It works though, which I think is the most important part :)

@lukasgeiter
Copy link
Contributor

@quagh Just send the PR and you'll get feedback 😉

@cmburcus
Copy link

Are there any news for the PR? :(

@zlodes
Copy link
Contributor

zlodes commented Sep 12, 2017

Pleeease

@rtroost
Copy link

rtroost commented Dec 21, 2017

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

@DivineOmega
Copy link
Contributor

DivineOmega commented Dec 21, 2017

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!

@acepsaepudin
Copy link

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

@amrelnaggar
Copy link

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.

@flyrmyr
Copy link

flyrmyr commented May 9, 2018

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

@ko1eda
Copy link

ko1eda commented May 17, 2018

Just ran into this issue today as well laravel 5.6.20

@vjoao
Copy link

vjoao commented May 17, 2018

@flyrmyr this is a wont-fix. There is no non-hacky way of doing this.

@percymamedy
Copy link

percymamedy commented May 26, 2018

Ran in the same problem today. If there is no fix for now maybe this could be mentioned in the documentation.

@pmochine
Copy link

Same problem with 5.6

@Braunson
Copy link

Braunson commented Jul 10, 2018

@pmochine

@thisdotvoid's solution here with BelongsToMorph seems to work without issue in 5.6.x

@pmochine
Copy link

@Braunson yes it works but only for explicit relationships.

@AdrianHL
Copy link

AdrianHL commented Aug 8, 2018

@thisdotvoid's solution works in Laravel 5.6.29 (implemented a few minutes ago). It will be nice to have an official way to do this in a close future :)

@groax
Copy link

groax commented Aug 22, 2018

polymorphic relations.... same problem here! they are a pain in my ass...

@pr-baernholdt
Copy link

Same problem here 5.7.9

@solofeed
Copy link

solofeed commented Jan 9, 2019

Same here

@teomanofficial
Copy link

This party never going to end..

@staudenmeir
Copy link
Contributor

staudenmeir commented Jun 27, 2019

Laravel 5.8.27 adds whereHasMorph(): #28928

@kakenbok
Copy link

woooooo ... i hardly can believe this! thank you so much

@Hoheckell
Copy link

stops the warning but not result set
return $this->morphMany(MODEL::class, 'name','type','pk');

@jefersonalmeida
Copy link

this works for me.

Transaction.php

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

Reservation.php

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

Project.php

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

@dilipprajapati94
Copy link

dilipprajapati94 commented May 31, 2022

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.

@maker247
Copy link

thanks bro you are god

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests