DEV Community

Cover image for Introducing Filterable: A Powerful, Modular Query Filtering System for Laravel
Jerome Thayananthajothy
Jerome Thayananthajothy

Posted on

Introducing Filterable: A Powerful, Modular Query Filtering System for Laravel

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();
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Creating Your First Filter

Create a filter class using the provided Artisan command:

php artisan make:filter PostFilter
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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',
]);
Enter fullscreen mode Exit fullscreen mode

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,
]);
Enter fullscreen mode Exit fullscreen mode

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;
});
Enter fullscreen mode Exit fullscreen mode

Smart Caching

Improve performance with intelligent caching:

$filter->setCacheExpiration(60);
$filter->cacheTags(['posts', 'api']);
$filter->cacheResults(true);
Enter fullscreen mode Exit fullscreen mode

Input Validation

Validate filter parameters before applying them:

$filter->setValidationRules([
    'status' => 'required|in:draft,published,archived',
    'category_id' => 'sometimes|integer|exists:categories,id',
]);
Enter fullscreen mode Exit fullscreen mode

Performance Monitoring

Track execution time and query performance:

$metrics = $filter->getMetrics();
$executionTime = $filter->getExecutionTime();
Enter fullscreen mode Exit fullscreen mode

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,
    ]);
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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,
];
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)