- Laravel's model scopes don't work with Scout (search).
- Reusing scopes across models means copying code OR using traits (but Scout still breaks).
- Leads to duplicate query logic.
Instead of defining scopes inside models, create scope classes that can be used in both Eloquent & Scout queries!
FilterScope.php
<?php
declare(strict_types=1);
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Laravel\Scout\Builder as ScoutBuilder;
final class FilterScope {
/**
* @template TModel of \Illuminate\Database\Eloquent\Model
*
* @param Builder<TModel>|ScoutBuilder<TModel> $query
* @return Builder<TModel>|ScoutBuilder<TModel>
*/
public function __invoke(Builder|ScoutBuilder $query): Builder|ScoutBuilder
{
return $query->where(...); // Apply your query logic here
}
}
Post.php
/**
* @param Builder<Post> $query
*/
public function scopeFilter(Builder $query, User $user): void
{
$query->tap(new FilterScope());
}
Post::search($search)
->tap(new FilterScope())
->get();
- Works with both Eloquent & Scout.
- Reuses query logic across models.
- No need for duplicate code or workarounds.
- Keeps models clean & maintainable.
Tappable Scopes help keep Laravel applications clean, flexible & DRY.