Fix Laravel scope warnings of phpstan (or larastan)?
Asked Answered
L

1

6

How to deal with scopes in Laravel when analyzing code with phpstan/larastan?

I'm getting this error:

Call to an undefined method Illuminate\Database\Eloquent\Builder::active().

Do I need to typehint it somehow?

I'm trying to avoid @phpstan-ignore-next-line

It's worth mentioning that said error only appears when I'm using a scope inside scope. In other cases, LaraStan is preventing PhpStan warnings successfully.

Lepper answered 20/3, 2023 at 6:23 Comment(3)
You could put such errors into ignore list within your config file (phpstan.org/user-guide/…)Reta
I'm thinking about creating a new class that would extend the Eloquent\Builder for each Model, but I'm not sure how to implement it yetLepper
Issues around "using a scope inside scope" or around "chaining a scope after another scope" can be fixed by doc-commenting; see my answer's edit.Spinthariscope
S
9

At time of writting, there are three approaches.

But I use both first and third approach in the same project, because there are many Composer packages which don't use my second or third approach.

First approach is a workaround

Simply ignore Laravel's "magic" in phpstan.neon file, like:

parameters:
    paths:
        - app

    excludePaths:
        - ./vendor/**

    reportUnmatchedIgnoredErrors: false
    ignoreErrors:
        - '#Call to an undefined method#'
        - '#Call to an undefined static method#'

Notes

You can customize above Regular-expression(s) to ignore only what's needed.

But PHP does not allow a class to have same name twice:

PHP Fatal error:  Cannot redeclare App\Models\MyModel::active()

While Laravel's scope "magic" allows the same name from both "static" and "instance" context, like:

$query = MyModel::active();
$query = MyModel::where('id', '!=', 1)->active();

Meaning, even if you use doc-comment, like:

/**
 * @method static active()
 * @method active()
 */
class MyModel { ... }

It only changes phpstan's message, to something like:

Static call to instance method App\Models\MyModel::active().

Second approach is a solution

For reasons mentioned in above note-section;
Prefix all uses of Laravel's "static scope magic" with "query()->", like change from:

MyModel::active();

Into:

MyModel::query()->active();

Finally, add doc-comment(s), like:

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @method Builder|static active()
 * @method static Builder|static query()
 */
class MyModel extends Model { ... }

Third approach is a trick

Basically, do everything that second approach says,
But instead of doc-comment(s), like most libraries, pair the scope-method with a normal-method which checks the same field(s) without any query, like:

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * @method static Builder|static query()
 */
class MyModel extends Model {

    /**
     * @return bool|Builder|static
     */
    public function active()
    {
        return $this->status == 'active';
    }

    public function scopeActive(Builder $query): void
    {
        /** @var Builder|static $query */
        $query->where('status', '=', 'active');

        // NOTE: above `@var` is only required to chain other scope(s).
    }

}

Note that above tricks both IDE and phpstan into thinking that ->active() may return Builder|static, which allows chaining, like:

$query = MyModel::query()
    ->active()
    ->where(...);
Spinthariscope answered 20/3, 2023 at 9:34 Comment(2)
I think PHPstan have exposed the major flaw in Laravel design, because laravel puts scopes in the Model, which is obviously a mistake, because scopes related to the Query Builder. Thus, I believe the description of scopes should be removed from the Model and there must be a Query Builder class for each model separately. I'm thinking of override a ->query() function to make it return a model specific builder... So basically I don't think we need to fight PhpStan, but rather we need to fight LaravelLepper
The second approach just like github.com/larastan/larastan/issues/918Meggie

© 2022 - 2024 — McMap. All rights reserved.