indexzer0/eloquent-filtering
Define allowed filters on your Eloquent models and apply them from simple arrays or request data—no custom query logic. Supports complex, type-based filtering for APIs and dashboards on Laravel 10+ / PHP 8.2+.
Install the package:
composer require indexzer0/eloquent-filtering
php artisan eloquent-filtering:install
Implement IsFilterable in your model:
use IndexZer0\EloquentFiltering\Contracts\IsFilterable;
use IndexZer0\EloquentFiltering\Traits\Filterable;
class Product extends Model implements IsFilterable
{
use Filterable;
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::field('name', [FilterType::EQUAL]),
Filter::field('price', [FilterType::GREATER_THAN, FilterType::LESS_THAN])
);
}
}
Apply filters via HTTP request:
$filters = request()->input('filters', []);
$products = Product::filter($filters)->get();
API Endpoint Example:
// routes/api.php
Route::get('/products', function () {
$products = Product::filter(request()->input('filters', []))
->paginate(15);
return response()->json($products);
});
Request:
{
"filters": [
{"type": "$eq", "target": "name", "value": "Laptop"},
{"type": "$gt", "target": "price", "value": 500}
]
}
Filter::field('status', [FilterType::EQUAL, FilterType::NOT_EQUAL])
Filter::field('user->name', [FilterType::EQUAL])
Filter::field('metadata->*.price', [FilterType::GREATER_THAN])
$filters = [
["type": "$or", "filters": [
["type": "$eq", "target": "status", "value": "active"],
["type": "$eq", "target": "status", "value": "pending"]
]]
];
public function toArray($request)
{
$this->applyFilters($request->input('filters', []));
return parent::toArray($request);
}
public function rules()
{
return [
'filters' => 'sometimes|array',
];
}
public function authorize()
{
return true;
}
public function scopeActive($query)
{
return $query->where('active', true);
}
// Usage:
Product::filter($filters)->active()->get();
Extend FilterType for domain-specific logic:
class CustomFilterType extends FilterType
{
public static function handle($query, $value, $operator, $target)
{
return $query->where($target, 'LIKE', "%{$value}%");
}
}
Target Mismatch:
target in filters matches exact column names (including relationships).user.name vs user->name (use -> for relationships).JSON Path Wildcards:
->* for arrays in JSON fields (e.g., metadata->*.price).JSON_LENGTH and wildcards.Case Sensitivity:
LIKE is case-insensitive by default in most databases. Use ILIKE for PostgreSQL if needed.Performance:
->select(['id', 'name']) to limit columns in filtered queries.Sorting Limitation:
$query = Product::filter($filters);
dd($query->toSql(), $query->getBindings());
Filter::validate() to check allowed filters before applying:
if (!Filter::validate($filters, $this->allowedFilters())) {
abort(400, 'Invalid filters');
}
Default Behavior:
allowedFilters() must return an AllowedFilterList (use Filter::only() or Filter::all()).$or and $and are always allowed (no need to define them).Customizing Filter Types:
Override FilterType::handle() for global changes or register new types via service provider:
FilterType::macro('custom', function ($query, $value, $operator, $target) {
// Custom logic
});
Filter Validation:
Override validateFilter() in your model for custom rules:
protected function validateFilter(array $filter): bool
{
if ($filter['type'] === '$eq' && $filter['target'] === 'name') {
return strlen($filter['value']) > 2;
}
return true;
}
Filter Transformation:
Use Filter::transform() to modify incoming filters:
$transformed = Filter::transform($filters, function ($filter) {
if ($filter['type'] === '$like') {
$filter['value'] = strtoupper($filter['value']);
}
return $filter;
});
Event Listeners:
Listen for eloquent.filtering events to log or modify queries:
Event::listen('eloquent.filtering', function ($query, $filters) {
// Log or modify $query before execution
});
Reusable Filter Sets: Cache allowed filters for complex models:
private static $allowedFilters;
public function allowedFilters(): AllowedFilterList
{
return self::$allowedFilters ??= Filter::only(
// ... define once
);
}
API Documentation:
Auto-generate OpenAPI specs for filters using Filter::toOpenApiSchema().
Testing:
Use FilterTestCase for unit tests:
use IndexZer0\EloquentFiltering\Tests\FilterTestCase;
class ProductFilterTest extends FilterTestCase
{
public function testNameFilter()
{
$this->assertFiltered('name', '$eq', 'Laptop');
}
}
How can I help you explore Laravel packages today?