Description
Enhance Laravel queries with adaptable, customisable filters and intelligent caching to improve both performance and functionality.
1. Generate a filter
php artisan make:filter PostFilter --model=Post
--model wires the stub to your Eloquent model. Use --basic for an empty shell or --force to overwrite an existing class.
2. Implement filtering logic
<?php namespace App\Filters; use Filterable\Filter;use Illuminate\Database\Eloquent\Builder;use Illuminate\Http\Request;use Illuminate\Support\Carbon;use Illuminate\Validation\Rule; class PostFilter extends Filter{ /** * Request keys that map straight to filter methods. * * Methods follow camelCased versions of the keys (e.g. published_after → publishedAfter). */ protected array $filters = ['status', 'published_after', 'q']; public function __construct(Request $request) { parent::__construct($request); $this->enableFeatures([ 'validation', 'optimization', 'filterChaining', 'valueTransformation', ]); $this->setValidationRules([ 'status' => ['nullable', Rule::in(['draft', 'published'])], 'published_after' => ['nullable', 'date'], ]); $this->registerTransformer('published_after', fn ($value) => Carbon::parse($value)); $this->registerPreFilters(fn (Builder $query) => $query->where('is_visible', true)); $this->select(['id', 'title', 'status', 'published_at'])->with('author'); } protected function status(string $value): void { $this->getBuilder()->where('status', $value); } protected function publishedAfter(Carbon $date): void { $this->getBuilder()->whereDate('published_at', '>=', $date); } protected function q(string $term): void { $this->getBuilder()->where(function (Builder $query) use ($term) { $query->where('title', 'like', "%{$term}%") ->orWhere('body', 'like', "%{$term}%"); }); }}
Define protected array $filterMethodMap when you need to alias request keys to method names. Programmatic filters can be appended with appendFilterable('key', $value) before apply() runs. Supplying an Illuminate\Contracts\Cache\Repository or Psr\Log\LoggerInterface to the constructor immediately enables the caching and logging features.
3. Attach the scope to a model
<?php namespace App\Models; use Filterable\Traits\Filterable;use Illuminate\Database\Eloquent\Model; class Post extends Model{ use Filterable;}
4. Run the filter pipeline
<?php namespace App\Http\Controllers; use App\Filters\PostFilter;use App\Http\Resources\PostResource;use App\Models\Post;use Illuminate\Http\Request; class PostController{ public function index(Request $request, PostFilter $filter) { $posts = Post::query() ->filter( $filter ->forUser($request->user()) ->enableFeature('caching') ->setOptions(['chunk_size' => 500]) ) ->get(); return PostResource::collection($posts); }}
apply() may only be called once per instance; call reset() if you need to reuse a filter. Because the Filter base class uses Laravel's Conditionable trait, you can use helpers such as $filter->when($request->boolean('validate'), fn ($filter) => $filter->enableFeature('validation'));.