Installation:
composer require austral/filter-bundle
Ensure austral/tools-bundle and austral/form-bundle are also installed (auto-included as dependencies).
Register the Bundle:
Add to config/app.php under providers:
Austral\FilterBundle\FilterBundle::class,
First Use Case:
Filter a collection of Austral entities (e.g., Post, User).
Example in a controller:
use Austral\FilterBundle\Filter\FilterManager;
public function index(FilterManager $filterManager)
{
$posts = $filterManager->apply(
Post::query(),
request()->query->all() // or custom filter rules
);
return view('posts.index', compact('posts'));
}
Key Files:
config/filter.php: Default configuration (rules, mappings).src/FilterBundle/Resources/config/services.yaml: Service definitions.tests/: Reference implementations for edge cases.Request-Based Filtering:
Bind query parameters to filter rules in config/filter.php:
# config/filter.php
filters:
posts:
- { name: 'title', operator: 'like', field: 'title' }
- { name: 'published', operator: 'eq', field: 'is_published', type: 'bool' }
Use in controller:
$filtered = $filterManager->apply(Post::query(), request()->query);
Custom Filter Rules:
Extend Austral\FilterBundle\Filter\AbstractFilter:
namespace App\Filters;
use Austral\FilterBundle\Filter\AbstractFilter;
class CustomDateFilter extends AbstractFilter
{
protected function applyFilter($query, $value)
{
return $query->whereDate('created_at', $value);
}
}
Register in config/filter.php:
filters:
posts:
- { name: 'date', class: App\Filters\CustomDateFilter }
Form Integration:
Use Austral\FormBundle to render filter inputs:
use Austral\FormBundle\Form\FilterForm;
$form = $this->createForm(FilterForm::class, null, [
'filters' => $filterManager->getFilters('posts')
]);
API Filtering: Validate and apply filters via DTOs (e.g., Symfony Serializer):
$dto = $serializer->deserialize(request()->json->all(), FilterDto::class, 'json');
$filtered = $filterManager->apply(Post::query(), $dto->getFilters());
scout:filter for search-as-you-type:
$scoutResults = Post::search(request('q'))->filter($filterManager);
simple-pagination or laravel-pagination after filtering:
$posts = $filterManager->apply(Post::query(), request()->query)
->paginate(10);
Cache::remember:
$posts = Cache::remember("posts_{$cacheKey}", now()->addHour(), function() use ($filterManager) {
return $filterManager->apply(Post::query(), request()->query)->get();
});
Operator Mismatches:
like vs contains: Ensure config/filter.php operators align with your DB dialect (e.g., LIKE vs ILIKE in PostgreSQL).AbstractFilter::getOperator() for custom SQL.Case Sensitivity:
eq on strings may fail if DB collation differs (e.g., utf8mb4_bin vs utf8mb4_unicode_ci).lower() in applyFilter:
return $query->whereRaw('LOWER(field) = LOWER(:value)', ['value' => $value]);
Nested Relationships:
user.email requires eager loading:
$posts = $filterManager->apply(
Post::with('user'),
request()->query
);
with() or loadMissing() to avoid N+1 queries.Boolean Fields:
eq:1 may not work for boolean fields (stored as 0/1 or true/false).applyFilter:
return $query->where('is_active', (bool)$value);
Mass Assignment:
deleted_at).config/filter.php:
filters:
posts:
allowed_fields: ['title', 'published']
Log Raw Queries:
Enable Laravel’s query logging in AppServiceProvider:
public function boot()
{
if (app()->environment('local')) {
DB::enableQueryLog();
}
}
Dump logs after filtering:
dd(DB::getQueryLog());
Validate Inputs:
Use Symfony’s Validator to sanitize filter inputs:
use Symfony\Component\Validator\Validator\ValidatorInterface;
$validator = app(ValidatorInterface::class);
$errors = $validator->validate($filterDto);
if ($errors->count()) { /* Handle */ }
Custom Filter Types:
Add support for array or json fields:
class JsonFilter extends AbstractFilter
{
protected function applyFilter($query, $value)
{
return $query->whereJsonContains('metadata', $value);
}
}
Dynamic Filter Sources: Load filters from a database table:
$dynamicFilters = FilterRule::where('model', 'Post')->get();
$filtered = $filterManager->apply(Post::query(), request()->query, $dynamicFilters);
Event Listeners:
Hook into FilterApplied events to log or modify queries:
use Austral\FilterBundle\Event\FilterAppliedEvent;
public function onFilterApplied(FilterAppliedEvent $event)
{
if ($event->getModel() === Post::class) {
$event->getQuery()->orderBy('title');
}
}
Register in EventServiceProvider:
protected $listen = [
FilterAppliedEvent::class => [
FilterListener::class,
],
];
Testing:
Mock FilterManager in PHPUnit:
$filterManager = $this->createMock(FilterManager::class);
$filterManager->method('apply')
->willReturn(Post::factory()->count(3));
$this->app->instance(FilterManager::class, $filterManager);
Default Values:
Override defaults in config/filter.php:
default_operator: 'contains' # Overrides global 'eq'
strict_mode: true # Throws exceptions on invalid filters
Performance:
Disable strict_mode in production to avoid exceptions on malformed requests:
strict_mode: false
Localization:
Translate operator labels (e.g., eq → "equals") in resources/lang/en/filter.php:
return [
'operators' => [
'eq' => 'is equal to',
'like' => 'contains',
],
];
Use in Blade:
{{ __('filter.operators.'.$filter->getOperator()) }}
How can I help you explore Laravel packages today?