If you've built Laravel applications that require complex filtering of your Eloquent models, you've likely faced the challenge of keeping your controller code clean while implementing robust query filters. Today, I'm excited to introduce Filterable, a new open-source package that makes dynamic query filtering both powerful and simple with its modular, trait-based architecture.
The Problem: Messy Controller Code
We've all been there. Your controller starts simple, but as filtering requirements grow, it quickly becomes cluttered:
public function index(Request $request)
{
$query = Post::query();
if ($request->has('status')) {
$query->where('status', $request->status);
}
if ($request->has('category')) {
$query->where('category_id', $request->category);
}
if ($request->has('search')) {
$query->where('title', 'like', "%{$request->search}%");
}
// More conditional filters...
return $query->paginate();
}
As your application grows, this approach leads to bloated controllers, duplicated code, and difficult-to-maintain filter logic. That's where Filterable comes in.
The Solution: Filterable
Filterable provides a clean, modular approach to filter your Laravel Eloquent queries based on request parameters. But it doesn't stop there - it offers a rich set of features through its trait-based architecture, allowing you to pick and choose functionality based on your needs.
Key Features
- Dynamic Filtering: Apply filters based on request parameters with ease
- Modular Architecture: Customize your filter implementation using traits
- Smart Caching: Intelligent caching with automatic cache key generation
- User-Specific Filtering: Easily implement user-scoped filters
- Rate Limiting: Control filter complexity to prevent abuse
- Performance Monitoring: Track execution time and query performance
- Memory Management: Optimize memory usage for large datasets
- Query Optimization: Select specific columns and eager-load relationships
- Filter Chaining: Chain multiple filter operations with a fluent API
- Laravel 12 Support: Ready for the latest Laravel version
Getting Started
Installation is simple with Composer:
composer require jerome/filterable
Creating Your First Filter
Create a filter class using the provided Artisan command:
php artisan make:filter PostFilter
This generates a well-structured filter class:
namespace App\Filters;
use Filterable\Filter;
use Illuminate\Database\Eloquent\Builder;
class PostFilter extends Filter
{
protected array $filters = ['status', 'category'];
protected function status(string $value): Builder
{
return $this->builder->where('status', $value);
}
protected function category(int $value): Builder
{
return $this->builder->where('category_id', $value);
}
}
Implementing in Your Model
Add the trait to your model:
namespace App\Models;
use Filterable\Traits\Filterable;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use Filterable;
}
Using in Your Controller
Your controller code becomes clean and maintainable:
public function index(Request $request, PostFilter $filter)
{
$posts = Post::filter($filter)->paginate();
return response()->json($posts);
}
That's it! Your API now supports filters like /posts?status=published&category=5
.
What Makes Filterable Different?
Filterable stands out from other filtering packages due to its modular, trait-based architecture. Let's explore some of its most powerful features.
Feature Management
Enable only the features you need:
$filter->enableFeatures([
'validation',
'caching',
'performance',
]);
Rate Limiting and Complexity Control
Protect your application from overly complex filter requests:
$filter->setMaxFilters(10);
$filter->setMaxComplexity(100);
$filter->setFilterComplexity([
'complex_filter' => 10,
'simple_filter' => 1,
]);
Memory Management for Large Datasets
Handle large datasets efficiently:
// Process with lazy loading
$filter->lazy()->each(function ($post) {
// Process each post with minimal memory
});
// Map over results without loading all records
$titles = $filter->map(function ($post) {
return $post->title;
});
Smart Caching
Improve performance with intelligent caching:
$filter->setCacheExpiration(60);
$filter->cacheTags(['posts', 'api']);
$filter->cacheResults(true);
Input Validation
Validate filter parameters before applying them:
$filter->setValidationRules([
'status' => 'required|in:draft,published,archived',
'category_id' => 'sometimes|integer|exists:categories,id',
]);
Performance Monitoring
Track execution time and query performance:
$metrics = $filter->getMetrics();
$executionTime = $filter->getExecutionTime();
Real-World Example
Let's see how Filterable brings it all together in a real-world controller:
public function index(Request $request, PostFilter $filter)
{
// Enable features
$filter->enableFeatures(['validation', 'caching', 'performance']);
// Set validation rules
$filter->setValidationRules([
'status' => 'sometimes|in:draft,published,archived',
'category_id' => 'sometimes|integer|exists:categories,id',
]);
// Apply user scope if needed
if ($request->has('my_posts')) {
$filter->forUser($request->user());
}
// Apply pre-filters
$filter->registerPreFilters(function ($query) {
return $query->where('is_deleted', false);
});
// Apply custom filter chain
$filter->where('is_featured', true)
->orderBy('created_at', 'desc');
// Apply filters to the query
$query = Post::filter($filter);
// Get paginated results
$posts = $request->has('paginate')
? $query->paginate($request->query('per_page', 20))
: $query->get();
// Return performance metrics for debugging
$metrics = null;
if (app()->environment('local') && $filter->hasFeature('performance')) {
$metrics = $filter->getMetrics();
}
return response()->json([
'data' => $posts,
'metrics' => $metrics,
]);
}
With just a few lines of code, you've implemented a sophisticated filtering system with validation, caching, user-scoping, and performance monitoring.
Extending Filterable
One of Filterable's greatest strengths is its extensibility. Need a custom filter behavior? Simply extend the base filter class:
class PostFilter extends Filter
{
// Override or extend methods as needed
protected function handleFilteringException(Throwable $exception): void
{
Log::channel('filter-errors')->error('Filter error', [
'exception' => $exception->getMessage(),
]);
// Custom handling logic
}
}
Laravel 12 Support
Filterable is fully compatible with Laravel 12's new application structure. If you're using Laravel 12 with its minimal setup, you can register the service provider in your bootstrap/providers.php
file:
return [
// Other providers...
Filterable\Providers\FilterableServiceProvider::class,
];
Conclusion
Filterable offers a clean, modular approach to handling query filters in Laravel applications. With its trait-based architecture, you get exactly the features you need without unnecessary overhead. Whether you're building a simple blog or a complex data platform, Filterable helps you keep your code clean and maintainable while offering powerful filtering capabilities.
Getting Started Today
Ready to try Filterable in your Laravel application? Check out the GitHub repository and give it a star if you find it useful!
composer require jerome/filterable
We welcome contributions and feedback as we continue to improve the package. Let us know what features you'd like to see next!
Top comments (0)