How to prevent a false-warning in PhpStorm when the return-type is deduced incorrectly?

Viewed 1182

Consider the following code:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
/**
 * Class MyModel
 * @package App\Models
 * @mixin Builder
 */
class MyModel extends Model
{
    public static function getGreens(): Builder
    {
        return (new self())->where('color', '=', 'green');
    }
}

On the return statement, the PhpStorm (2020.3) complains that:

Return value is expected to be '\Illuminate\Database\Eloquent\Builder', 'MyModel' returned

And suggest to:

Change return type from '\Illuminate\Database\Eloquent\Builder' to 'MyModel'

which is weirdly incorrect (the where method does return an instance of \Illuminate\Database\Eloquent\Builder, while the IDE deduces the return type as being of MyModel type). By removing the return type, the IDE issues another warning:

Missing function's return type declaration

The code works without any problems, but the IDE shouldn't report any false warnings! How should I avoid these warnings in PhpStorm?

PhpStorm (2020.3) complains that: "Return value is expected to be '\Illuminate\Database\Eloquent\Builder', 'MyModel' returned"

2 Answers

It's a result of not following the "best practices". The class-hierarchy of MyModel does not provide a method of where; in other words, such a method does not exist in the class-hierarchy. But! The parent class of Model does provide a magic method of __call() which gets triggered when an inaccessible method in the object context is invoked (in your case, the method of where). It essentially forwards the "call" to a new instance of \Illuminate\Database\Eloquent\Builder, that has the implementation of the requested method (it's acquired from invoking the method of newQuery()). This mechanism is not only IDE-unfriendly, but also slower.

Thus; drop the @mixin tag, and instead of utilizing the "magic methods", use "native access":

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class MyModel extends Model
{
    public static function getGreens(): Builder
    {
        return (new self())->newQuery()->where('color', '=', 'green');
        //                 ^^^^^^^^^^^^
    }
}

As I understand (based on how Laravel works) it's because of @mixin line.

@mixin tag works similar to how PHP's native trait works. So if you have a method in a trait that returns $this / self and then use that trait in a class, then return of that method ($this/self) points to the class where it is used.

Now, Builder::where() method also returns $this or self ... but it's not actually a trait but Laravel magically makes that where() method available in this class.

And here comes the problem: that @return $this actually points to the Builder class but when used "as a trait" (because of @mixin) it gets resolved to the current MyModel class by the IDE.


You either use @mixin and live with ignoring the issue (you can use error suppression via Alt + Enter quick fix menu -- it will add a comment for IDE to tell to ignore that specific issue here) .. or remove @mixin and declare those methods differently.

AFAIK Laravel helper package should add all such Builder methods to the Model class via @method PHPDoc lines (look into that for details, go through past issues there to see how and why it does that etc, e.g. #541).

Another suggestion: try Laravel Idea plugin -- it's a PAID plugin but it makes working with Laravel code much easier and AFAIK it should cover such basic stuff.


For reference:

Related