Installation:
composer require cerbero/query-filters
Publish the config (if needed):
php artisan vendor:publish --tag=query_filters_config
Generate a Filter Class:
php artisan make:query-filter UserFilter --model=User
This creates app/QueryFilters/UserFilter.php (or your configured path).
Define Filters: Edit the generated file to add rules. Example:
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'));
}
}
Use in Controller:
use Cerbero\QueryFilters\Traits\FiltersRequests;
use App\QueryFilters\UserFilter;
class UserController extends Controller
{
use FiltersRequests;
public function index(UserFilter $filter)
{
$users = $filter->apply($this->userModel->newQuery());
return response()->json($users->get());
}
}
Route Request:
Route::get('/users', [UserController::class, 'index']);
Now call /users?name=John&active=1 to filter.
/users API endpoint with dynamic filtering.UserFilter.name, email, role, etc.?role=admin&active=true.Filter Generation:
make:query-filter for each model.App\QueryFilters).Rule Definition:
has() and get() to check and retrieve params.
if ($this->has('status')) {
$query->where('status', $this->get('status'));
}
if ($this->has('created_at')) {
$query->whereDate('created_at', $this->get('created_at'));
}
rules() method.
public function rules()
{
return [
'name' => 'sometimes|string|max:255',
'active' => 'sometimes|boolean',
];
}
Controller Integration:
FiltersRequests trait to auto-resolve filters.public function index(UserFilter $filter)
{
$query = $filter->apply(User::query());
return $query->paginate(10);
}
API Resource Integration:
return UserResource::collection($filter->apply(User::query())->paginate());
Dynamic Filtering:
filter() method to apply filters conditionally:
$filter->filter($query, ['name', 'email']);
Nested Filters:
PostFilter with a UserFilter for author filtering.Caching:
return Cache::remember("users_{$filter->getCacheKey()}", now()->addHours(1), function () use ($filter) {
return $filter->apply(User::query())->get();
});
Testing:
TestCase and mock requests:
$response = $this->get('/users?name=John');
$response->assertJsonCount(1, 'data');
Frontend Integration:
Case Sensitivity:
strtolower() if needed:
if ($this->has('role')) {
$query->where('role', strtolower($this->get('role')));
}
SQL Injection:
where, orWhere) instead of raw SQL or concatenation.Empty Parameters:
if ($this->has('name') && $this->get('name') !== '') {
$query->where('name', 'like', '%' . $this->get('name') . '%');
}
Performance:
LIKE on large columns (e.g., description) without a full-text index.select() to limit columns if only specific fields are needed.Validation Overrides:
rules(), ensure sometimes is included for optional params:
'name' => 'sometimes|string|max:255', // Optional
'active' => 'required|boolean', // Required
Log Filter Inputs: Add debug logs to see raw params:
public function apply($query)
{
\Log::info('Filter params:', $this->all());
// ...
}
Check SQL Queries:
Use Laravel Debugbar or DB::enableQueryLog():
DB::enableQueryLog();
$filter->apply(User::query());
\Log::info(DB::getQueryLog());
Validate Requests: Ensure params are being passed correctly. Test with Postman or cURL:
curl "http://yourapp.test/users?name=John&active=1"
Reusable Filter Logic: Extract common filter logic into base classes:
class BaseFilter extends QueryFilter
{
protected function filterByStatus($query)
{
if ($this->has('status')) {
$query->where('status', $this->get('status'));
}
return $query;
}
}
Document Filters: Add PHPDoc comments to generated filters for clarity:
/**
* Filters users by name, active status, and role.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function apply($query)
{
// ...
}
Default Values: Set defaults in the filter constructor:
public function __construct()
{
$this->defaults = [
'active' => true, // Default to active users
];
}
Soft Deletes:
Handle soft-deleted models with withTrashed():
$query = $filter->apply(User::withTrashed()->newQuery());
Ordering:
Combine with Laravel's orderBy for sorted results:
$query = $filter->apply(User::query())->orderBy('created_at', 'desc');
Pagination: Use Laravel's paginator for API responses:
return $filter->apply(User::query())->paginate(15);
Localization: For multilingual apps, translate filter labels:
$this->labels = [
'name' => __('Name'),
'active' => __('Active'),
];
Testing Filters:
Use fromRequest() to test filter logic:
public function test_filter_by_name()
{
$request = new Request(['name' => 'John']);
$filter = new UserFilter($request);
$query = $filter->apply(User::query());
$query->toSql(); // Check generated SQL
}
How can I help you explore Laravel packages today?