Framework: Error on `whereHasMorph` when morph columns are null

Created on 20 Apr 2020  ·  12Comments  ·  Source: laravel/framework

  • Laravel Version: 7.6.2
  • PHP Version: 7.2.25
  • Database Driver & Version: MySQL 5.5.5-10.4.10-MariaDB

Description:

Using whereHasMorph and orWhereHasMorph when there are records with null values on morph columns, laravel produces this error:

Search Filter (Ambengers\QueryFilter\Tests\Feature\SearchFilter)
 ✘ It can search through polymorphic relation
   ┐
   ├ Error: Class name must be a valid object or a string
   │
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php:718
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php:179
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php:245
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php:90
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php:247
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php:217
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:228
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:266
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php:227
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:228
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php:229
   ╵ /Users/dignity/codes/query-filter/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php:321

Steps To Reproduce:

For simplicity's sake, let's use the comments example we have on docs:

https://laravel.com/docs/7.x/eloquent-relationships#querying-polymorphic-relationships

  1. Setup a couple comment records in the database. Let couple of records be null on commentable_type and commentable_id columns.
  2. Run the whereHasMorph query against comments like so:
$comments = App\Comment::whereHasMorph('commentable', '*', function (Builder $query) {
    $query->where('title', 'like', 'foo%');
})->get();
bug

Most helpful comment

Fixed. I don't think when using whereHasMorph with * you should get back nullable morph column models. They don't have a morph parent and if you want all comments, including null morphs... seems like you can just issue your own custom query for the comments.

All 12 comments

I can reproduce this bug.

@driesvints I am also using this for morphTo and it's still not working.

@driesvints I don't think the solution on #29357 is the same solution for this issue, if that's the reason this issue is being closed.

@driesvints I can provide a more detailed explanation as this bug occurs specifically when querying morphs of any type (using *).

Migration:

...
$table->nullableMorphs('service');
...

Model:

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

When there is a ModelName record with actually null service_id and service_type columns, it works like this:

This one WORKS:

ModelName::whereHasMorph('service', [RelatedOne::class, RelatedTwo::class])

This one does not:

ModelName::whereHasMorph('service', '*')

When there are no null morphs in the database, both actually work as expected.

Therefore, there appears to be a bug in the handling of "any" type.

Okay re-opening.

@staudenmeir do you might know what's going on here?

@DSpeichert or @driesvints - is it correct to say that when we use wildcard ("*") on whereHasMorph, we also expect to get rows with null morph columns?

I'll submit a fix.

@ambengers Right now it looks like undefined behavior, but in my opinion, when using whereHasMorph('morph', '*'), I have an expectation of NOT getting morphs with NULL values. If they are NULL, we don't actually "have morph".

I'd be curious to see what is the "desired" behavior for soft-deleted morphs. I guess that by following Laravel's prior pattern, those would be skipped as well, although that would make it impossible to decide through the Closure (one could do ->whereNull('deleted_at') in the Closure).

@DSpeichert The wildcard includes soft-deleted morph types.

A query like this works correctly even if all comments of a certain type are soft-deleted:

Comment::whereHasMorph('commentable', '*', function (Builder $query) {
    $query->where('title', 'like', 'foo%');
})->withTrashed()->get();

Fixed. I don't think when using whereHasMorph with * you should get back nullable morph column models. They don't have a morph parent and if you want all comments, including null morphs... seems like you can just issue your own custom query for the comments.

This doesn't consider if all morph records in a table are null and, since we're not even hitting the loop here, will return all records:

https://github.com/laravel/framework/blob/92957206252cd4e40430f6f9eb2560566fa34ba3/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php#L216

I would expect something like:

if (count($types) > 0) {
     // foreach ($types as $type)...
} else {
     $query->whereNotNull(this->query->from.'.'.$relation->getMorphType());
}

...for consistency. Thoughts?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lzp819739483 picture lzp819739483  ·  3Comments

Anahkiasen picture Anahkiasen  ·  3Comments

gabriellimo picture gabriellimo  ·  3Comments

JamborJan picture JamborJan  ·  3Comments

YannPl picture YannPl  ·  3Comments