mnabialek/laravel-eloquent-filter
Installation:
composer require mnabialek/laravel-eloquent-filter
Publish the config (if extending defaults):
php artisan vendor:publish --provider="Mnabialek\EloquentFilter\FilterServiceProvider"
First Filter Class:
Create a filter for your model (e.g., app/Filters/UserFilter.php):
namespace App\Filters;
use Mnabialek\EloquentFilter\Filter;
class UserFilter extends Filter
{
protected $safeFields = ['name', 'email', 'active', 'role_id'];
public function apply($query)
{
if ($this->has('name')) {
$query->where('name', 'like', '%' . $this->get('name') . '%');
}
if ($this->has('active')) {
$query->where('active', $this->get('active'));
}
if ($this->has('role_id')) {
$query->where('role_id', $this->get('role_id'));
}
}
}
First Controller Usage:
use App\Filters\UserFilter;
public function index(Request $request)
{
$filter = new UserFilter($request->query());
$users = User::query()->filter($filter)->get();
return response()->json($users);
}
First API Endpoint: Test with:
GET /users?name=John&active=true
Request Validation Integration: Combine with Laravel’s validation for security:
public function index(Request $request)
{
$validated = $request->validate([
'name' => 'sometimes|string|max:255',
'active' => 'sometimes|boolean',
]);
$filter = new UserFilter($validated);
return User::filter($filter)->paginate(10);
}
Dynamic Filter Chaining: Build complex queries incrementally:
$query = User::query();
$query->filter(new UserFilter($request->all()))
->when($request->has('department'), function ($q) use ($request) {
return $q->where('department_id', $request->department);
});
Filter Traits for DRY Code:
trait Filterable
{
public function scopeFilter($query, Filter $filter)
{
return $filter->apply($query);
}
}
Apply to your model:
use App\Traits\Filterable;
class User extends Model
{
use Filterable;
}
Filter Groups:
Organize filters by feature (e.g., AdminFilters, PublicFilters):
namespace App\Filters\Admin;
class AdminUserFilter extends \Mnabialek\EloquentFilter\Filter
{
// ...
}
Filter Caching: Cache frequent filter combinations:
$cacheKey = md5(serialize($request->all()));
$users = Cache::remember($cacheKey, now()->addHours(1), function () use ($filter) {
return User::filter($filter)->get();
});
Filter Events: Dispatch events for filter application:
public function apply($query)
{
event(new FilterApplied($this, $query));
// ...
}
Filter Middleware: Centralize filter logic:
public function handle($request, Closure $next)
{
$request->merge(['filters' => new UserFilter($request->query())]);
return $next($request);
}
Filter Testing: Test filters in isolation:
public function test_active_filter()
{
$filter = new UserFilter(['active' => true]);
$query = User::query()->filter($filter);
$this->assertEquals('active = ?', $query->toSql());
}
API Resources: Use filters with API resources:
public function toArray($request)
{
return [
'data' => User::filter(new UserFilter($request->query()))->get(),
];
}
Livewire/Alpine.js: Dynamic filtering in frontend:
// Livewire
public $filters = [];
public function applyFilters()
{
return User::filter(new UserFilter($this->filters))->get();
}
Scout Integration: Filter search results:
$search = User::search($query)->filter(new UserFilter($request->query()));
Multi-Tenancy:
Combine with stancl/tenancy:
$query = User::forTenant($tenant)->filter(new UserFilter($request->query()));
SQL Injection Risks:
protected $safeFields = ['name', 'email']; // Only allow these columns
N+1 Query Problem:
with().User::with('posts')->filter($filter)->get();
Case Sensitivity:
like queries may fail due to collation.LOWER() or ILIKE:
$query->whereRaw('LOWER(name) LIKE LOWER(?)', ['%' . $this->get('name') . '%']);
Boolean Parsing:
'true' vs. true in requests.$active = in_array(strtolower($this->get('active')), ['1', 'true', 'yes']);
Pagination Conflicts:
User::filter($filter)->paginate(10);
Query Logging: Enable query logging to inspect generated SQL:
DB::enableQueryLog();
User::filter($filter)->get();
dd(DB::getQueryLog());
Filter Dumping: Debug filter inputs:
dd($filter->getAll());
Step-by-Step Query Building: Build queries incrementally to isolate issues:
$query = User::query();
if ($filter->has('name')) {
$query->where('name', 'like', '%' . $filter->get('name') . '%');
dd($query->toSql()); // Check SQL
}
Safe Fields:
$safeFields to restrict filterable columns.protected $safeFields = ['id', 'name', 'created_at'];
Default Values:
public function __construct(array $data = [])
{
$this->defaults = [
'per_page' => 15,
'sort' => 'name',
];
parent::__construct($data);
}
Custom Operators:
public function apply($query)
{
if ($this->has('created_after')) {
$query->where('created_at', '>', $this->get('created_after'));
}
}
Custom Filter Classes:
Extend Filter for reusable logic:
class DateFilter extends Filter
{
public function apply($query)
{
if ($this->has('after')) {
$query->where('created_at', '>', $this->get('after'));
}
}
}
Filter Macros: Add global filter methods:
Filter::macro('scopeActive', function ($query) {
return $query->where('active', true);
});
Filter Events: Listen for filter application:
Filter::listen('applying', function ($filter, $query) {
// Log or modify query
});
Filter Middleware: Centralize filter logic:
public function handle($request, Closure $next)
{
$request->merge(['filters' => new UserFilter($request->query())]);
return $next($request);
}
Index Filtered Columns: Ensure filtered columns are indexed:
CREATE INDEX idx_users_name ON users(name);
**Limit Filter
How can I help you explore Laravel packages today?