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

Can't get doesntHaveMorph to work #29138

Closed
denitsa-md opened this issue Jul 11, 2019 · 6 comments
Closed

Can't get doesntHaveMorph to work #29138

denitsa-md opened this issue Jul 11, 2019 · 6 comments

Comments

@denitsa-md
Copy link

denitsa-md commented Jul 11, 2019

  • Laravel Version: 5.8.28
  • PHP Version: 7.3
  • Database Driver & Version: mysql 8.0.16

Description:

I cannot seem to get the new doesntHaveMorph to work. whereHasMorph works fine though.

Steps To Reproduce:

Simple example:

I have a Link model and different models can have links (e.g. subscription, license period, product).

This is the relation on the Link model:

    /**
     * Get all of the owning models
     *
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
     */
    public function linkable()
    {
        return $this->morphTo();
    }

I have seeded these 3 links to the database: (posting a picture since it's easier to look at)

image

Then the code using doesntHaveMorph:

Link::doesntHaveMorph('linkable', [Subscription::class])->get()

-> returns an empty collection, where I expect to get the links for the License period and Product.

On the other hand Link::whereHasMorph('linkable', [Subscription::class])->get()

-> correctly returns a collection with one item -> the link for the Subscription.

@denitsa-md
Copy link
Author

denitsa-md commented Jul 11, 2019

Okay, maybe my understanding of the function is completely wrong, because it seems the behavior would match that of what the test is doing. https://github.com/laravel/framework/pull/28928/files#diff-189f859ae631c6808f0f151fbaf1f985R170

   public function testDoesntHaveMorph()
    {
        $comments = Comment::doesntHaveMorph('commentable', Post::class)->get();
        $this->assertEquals([3], $comments->pluck('id')->all());
    }

So the setUp in the test creates 6 models - Posts with ids [1, 2, 3], and Videos with ids [4, 5, 6]. The 3rd Post is soft deleted.

Then

Comment::doesntHaveMorph('commentable', Post::class)->get(); returns the soft deleted Post.

But isn't it supposed to return all the Video models since they belong to the set of "Comments that don't have Post models" 🤔

If that is not the idea with this function, Is there any way for me to then get all the Comments that don't belong to a Post model?

Basically, I was expecting something like this from the doesntHaveMorph function:

   public function testDoesntHaveMorph()
    {
        $comments = Comment::doesntHaveMorph('commentable', Post::class)->get();
        $this->assertEquals([4, 5, 6], $comments->pluck('id')->all());
    }

@staudenmeir
Copy link
Contributor

This is indeed the correct behavior, but I can see how it's not the most intuitive.

doesntHaveMorph() is only useful for querying related models with global scopes (e.g. SoftDeletes) or looking for inconsistent data (foreign keys that don't actually exist).

Possible solutions for your situation:

  • If you know all the other polymorphic types:
Link::hasMorph('linkable', [LicensePeriod::class, Product::class])->get();
  • If none of the related models uses global scopes and all the foreign keys are valid:
Link::where('linkable_type', '!=', Subscription::class)->get();
  • If you can't make these assumptions, you need to the get the possible types manually:
$types = (new Link)->newModelQuery()->where('linkable_type', '!=', Subscription::class)
    ->distinct()->pluck('linkable_type')->all();

Link::hasMorph('linkable', $types)->get();

@denitsa-md
Copy link
Author

@staudenmeir I see. Yes, at least I found it slightly counter intuitive but now I know. :)

It would be cool to be able to do the other thing as well though!

Thank you for the suggestions!

@denitsa-md
Copy link
Author

@staudenmeir On second thought, I think the function name could either be changed, or the documentation updated to better explain what you explained to me.

Right now doesntHaveMorph sits right below whereHasMorph https://laravel.com/docs/5.8/eloquent-relationships#querying-polymorphic-relationships so I believe many people will expect what I expected from doesnt :)

Also, the docs only mention:

whereHasMorph

doesntHaveMorph

If we were to match the behaviour it would be:

hasMorph

doesntHaveMorph

whereHasMorph

whereDoesntHaveMorph

plus the orWhere's

@staudenmeir
Copy link
Contributor

It would be cool to be able to do the other thing as well though!

The idea behind the current implementation is that Laravel can get the possible types for you, but you can also provide them yourself. With your suggestion, Laravel would always have to execute an additional query.

Also, the docs only mention:

I don't think we have to list all methods, "corresponding methods" should cover them.

the documentation updated to better explain what you explained to me

An example with whereDoesntHaveMorph() would probably make more sense. We can also add explanations like some other examples have ("Retrieve...").

@driesvints
Copy link
Member

Feel free to send in a pr to the docs, thanks.

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

No branches or pull requests

3 participants