2

I'm trying to define a Laravel Eloquent model with a custom scope that filters categories by a related product's vendor_id. Here's my setup:

class Category extends Model
{
    public function productTemplates(): HasMany
    {
        return $this->hasMany(ProductTemplate::class, 'category_id');
    }

    #[Scope]
    protected function byVendor(Builder $query, ?int $vendorId = null): void
    {
        $query->whereHas('productTemplates', function (Builder $productQuery): void {
            /** @var Builder<ProductTemplate> $productQuery */
            $productQuery->byVendor($vendorId);
        });
    }
}
class ProductTemplate extends Model
{
    #[Scope]
    protected function byVendor(Builder $query, ?int $vendorId = null): void
    {
        if ($vendorId) {
            $query->where('vendor_id', $vendorId);
        }
    }
}

When I run PHPStan, I get this error:

PHPDoc tag @var with type Illuminate\Database\Eloquent\Builder<App\Models\ProductTemplate> 
is not subtype of native type Illuminate\Database\Eloquent\Builder<Illuminate\Database\Eloquent\Model>.

How can I fix the PHPDoc or type hinting so PHPStan recognizes the $productQuery type correctly in the scope?

New contributor
melkmeshi is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

1 Answer 1

1

PHPStan complains because the closure gets a generic Builder<Model> and your docblock says Builder<ProductTemplate>, so it thinks the types don’t match. Laravel knows the relation returns ProductTemplate, but PHPStan doesn’t infer that on its own inside whereHas().

You can just hint it directly in the closure:

$query->whereHas('productTemplates', function (Builder $productQuery) use ($vendorId) {
    /** @var Builder<ProductTemplate> $productQuery */
    $productQuery->byVendor($vendorId);
});

This basically tells PHPStan “yes, this builder is for ProductTemplate” and it stops complaining. Nothing else in your scope has to change.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.