N3rdwar3's avatar

Laravel 12.x Dynamic Scopes

Hi, I'm working on setting up a dynamic-scope for an API filter on my model, but running into an error that I can't seem to figure out, any help would be greatly appreciated.

In my Recipes Model I have

    use Illuminate\Database\Eloquent\Attributes\Scope;
    use Illuminate\Database\Eloquent\Builder;
    use App\Http\Filters\V1\QueryFilter;
    .....
    #[Scope]
    public function filter(Builder $builder, QueryFilter $filters): Builder {
        return $filters->apply($builder);
    }

And in my Controller I am making the following call

    /**
     * Display a listing of the resource.
     */
    public function index(RecipeFilter $filters)
    {
        return RecipeResource::collection(Recipe::filter($filters)->paginate());
    }

The error I am getting is

Non-static method App\\Models\\Recipe::filter() cannot be called statically

OKAY, so just before posting this I realized my method deviates somewhat from the documentation in that I was calling 'public function filter' instead of 'protected function filter'. So, maybe instead someone could explain to me why that difference is what fixes it...?

0 likes
1 reply
LaryAI's avatar

Great question! The core of your issue is about how Eloquent dynamic scopes work in Laravel 12.x.

Why does changing public to protected fix the error?

When you use the #[Scope] attribute in your Eloquent model, Laravel registers that method as a dynamic scope. This means you can call it like a query builder method (e.g., Recipe::filter($filters)), but only if the method is protected.

How does it work?

  • Protected methods with the #[Scope] attribute are automatically registered as dynamic scopes.
  • Public methods are not registered as dynamic scopes, so calling them statically (Recipe::filter($filters)) will try to call a non-static method statically, causing the error you saw.

Example from your code:

// This will NOT work for dynamic scopes:
public function filter(Builder $builder, QueryFilter $filters): Builder {
    return $filters->apply($builder);
}

// This WILL work:
#[Scope]
protected function filter(Builder $builder, QueryFilter $filters): Builder {
    return $filters->apply($builder);
}

Summary

  • Use protected for methods you want to register as dynamic scopes with #[Scope].
  • Laravel scans for protected methods with the #[Scope] attribute and makes them available as query builder methods.
  • If you use public, Laravel doesn't register them as dynamic scopes, so static calls fail.

References

In short:
Change your method to protected and keep the #[Scope] attribute. That will register it as a dynamic scope and allow Recipe::filter($filters) to work as expected!

2 likes

Please or to participate in this conversation.