A secure and optimized filter object for Laravel/Eloquent models based on the laracasts approach.
- Simple and fluent API for filtering Eloquent models
- Security-focused with protection against SQL injection
- Sortable trait for complex sorting with relation support
- Range filter support for dates and numeric values
- Performance optimizations for large datasets
- Filter whitelisting for controlled access
- Comprehensive test suite
- Install this package
- Define your filters
- Apply them to your models
Require this package with composer:
$ composer require coderscantina/filter
Define a filter:
<?php namespace App;
use CodersCantina\Filter\ExtendedFilter;
class TestFilter extends ExtendedFilter
{
public function name($name)
{
return $this->builder->where('name', $name);
}
public function latest()
{
return $this->builder->latest();
}
}
In your model:
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
use CodersCantina\Filter\Filterable;
class TestModel extends Model
{
use Filterable;
}
In your controller:
<?php namespace App\Http\Controllers;
use App\TestModel;
use App\TestFilter;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Collection;
class LessonsController extends Controller
{
/**
* Show all lessons.
*
* @param Request $request
* @return Collection
*/
public function index(Request $request)
{
$filter = new TestFilter($request->all());
// For enhanced security, whitelist allowed filters
$filter->setWhitelistedFilters(['name', 'latest']);
return TestModel::filter($filter)->get();
}
}
The package supports advanced filtering with operator-based syntax for more expressive queries. This feature allows you to easily implement complex filtering logic with minimal code.
The following operators are supported:
Operator | SQL Equivalent | Description |
---|---|---|
null |
IS NULL |
Matches null values |
!null |
IS NOT NULL |
Matches non-null values |
eq |
= |
Equals (default if no operator specified) |
neq |
!= |
Not equals |
empty |
Custom | Matches empty strings or null values |
!empty |
Custom | Matches non-empty and non-null values |
like |
LIKE |
Contains (adds wildcards around value) |
!like |
NOT LIKE |
Does not contain (with wildcards) |
^like |
LIKE |
Starts with (adds wildcard after) |
like$ |
LIKE |
Ends with (adds wildcard before) |
lt |
< |
Less than |
gt |
> |
Greater than |
lte |
<= |
Less than or equal to |
gte |
>= |
Greater than or equal to |
in |
IN |
In a list of values (comma-separated) |
!in |
NOT IN |
Not in a list of values (comma-separated) |
To use advanced operators, extend from the AdvancedFilter
class instead of ExtendedFilter
:
<?php namespace App;
use CodersCantina\Filter\AdvancedFilter;
class ProductFilter extends AdvancedFilter
{
public function name($value)
{
// Automatically handles operator syntax
$this->applyDynamicFilter('name', $value);
}
public function price($value)
{
// Supports both operators and range syntax
$this->applyAdvancedRangeFilter('price', $value);
}
public function created_at($value)
{
// Supports both operators and date range syntax
$this->applyAdvancedDateFilter('created_at', $value);
}
}
// Find products with names containing "phone"
$filter = new ProductFilter(['name' => 'like:phone']);
// Find products with price greater than or equal to 100
$filter = new ProductFilter(['price' => 'gte:100']);
// Find products created after January 1, 2023
$filter = new ProductFilter(['created_at' => 'gte:2023-01-01']);
// Find products in specific categories
$filter = new ProductFilter(['category' => 'in:electronics,phones,accessories']);
// Find products that are either active or featured
$filter = new ProductFilter(['status' => ['eq:active', 'eq:featured']]);
// Find non-empty descriptions
$filter = new ProductFilter(['description' => '!empty:']);
The advanced filtering still supports the range filter syntax (...
) while adding operator capabilities:
// Traditional range syntax still works
$filter = new ProductFilter(['price' => '100...500']);
// Using operators for precise comparisons
$filter = new ProductFilter(['price' => 'gte:100']);
Similarly, date filters can use both traditional range and operator syntax:
// Traditional date range
$filter = new ProductFilter(['created_at' => '2023-01-01...2023-12-31']);
// Using operators
$filter = new ProductFilter(['created_at' => 'gte:2023-01-01']);
You can combine multiple operator conditions for the same field:
// Products that are either active or pending
$filter = new ProductFilter([
'status' => ['eq:active', 'eq:pending']
]);
You can extend the supported operators by adding your own:
$filter = new ProductFilter(['price' => 'between:10,50']);
$filter->setCustomOperators(['between' => 'BETWEEN']);
If you're integrating the advanced filter functionality into an existing filter:
- Use the
AdvancedFilterable
trait in your filter class - Extend from
AdvancedFilter
or add the trait to your custom filter class - Update your filter methods to use
applyDynamicFilter()
,applyAdvancedRangeFilter()
, orapplyAdvancedDateFilter()
<?php namespace App;
use CodersCantina\Filter\ExtendedFilter;
use CodersCantina\Filter\AdvancedFilterable;
class CustomFilter extends ExtendedFilter
{
use AdvancedFilterable;
public function status($value)
{
$this->applyDynamicFilter('status', $value);
}
}
The AdvancedFilter
class maintains all the security features of the base Filter
class:
- Column name validation to prevent SQL injection
- Input sanitization for filter values
- Support for filter whitelisting
- Proper handling of null and empty values
Always use filter whitelisting in production to control which filters can be applied from user input:
$filter->setWhitelistedFilters(['name', 'price', 'category', 'status']);
To enhance security, always specify which filters are allowed:
$filter->setWhitelistedFilters(['name', 'price', 'category']);
The package automatically sanitizes input to prevent SQL injection attacks. However, you should still validate your input in controllers using Laravel's validation system.
The Sortable
trait which is included in the ExtendedFilter
offers sorting abilities:
['sort' => '+foo,-bar']; // -> order by foo asc, bar desc
Sort using foreign key relations:
['sort' => '+foo.bar']; // -> left join x on x.id = foo.id order by foo.bar asc
Limit the number of sort columns for performance:
$filter->setMaxSortColumns(3);
Restrict sortable columns:
protected array $sortableColumns = ['name', 'price', 'created_at'];
Apply range filters in various formats:
['price' => '10...']; // -> price >= 10
['price' => '...50']; // -> price <= 50
['price' => '10...50']; // -> price >= 10 and price <= 50
Filter by date ranges with automatic formatting:
['created_at' => '2023-01-01...']; // -> created_at >= '2023-01-01 00:00:00'
['created_at' => '...2023-12-31']; // -> created_at <= '2023-12-31 23:59:59'
['created_at' => '2023-01-01...2023-12-31']; // -> Between Jan 1 and Dec 31, 2023
Apply limit and offset for pagination:
['limit' => 10, 'offset' => 20]; // -> LIMIT 10 OFFSET 20
The package includes several optimizations:
- Join caching for repeated relation sorting
- Maximum sort column limits
- Efficient array handling
- Targeted query building
Create custom filter methods in your filter class:
public function active($value = true)
{
$this->builder->where('active', $value);
}
public function priceRange($value)
{
$this->applyRangeFilter('price', $value);
}
public function dateCreated($value)
{
$this->applyDateFilter('created_at', $value);
}
You can override core methods for custom behavior:
protected function isValidColumnName(string $column): bool
{
// Your custom validation logic
return parent::isValidColumnName($column) && in_array($column, $this->allowedColumns);
}
The package includes a comprehensive test suite:
$ composer test
- Always use filter whitelisting with
setWhitelistedFilters()
- Validate input in your controllers
- Limit sortable columns to prevent performance issues
- Use type-hinting in your filter methods
- Test your filters thoroughly
Please see CHANGELOG for more information on what has changed recently.