Installation:
composer require alhames/filter-bundle
Ensure your project uses Symfony 6.x/7.x and PHP 8.0+.
Bundle Registration:
Add to config/bundles.php:
return [
// ...
Alhames\FilterBundle\FilterBundle::class => ['all' => true],
];
First Use Case:
Define a filter in a YAML config (e.g., config/filters.yaml):
filters:
user:
name: string
email: email
is_active: boolean
rating: float
ip_address: ip
Then inject the filter service in a controller/service:
use Alhames\FilterBundle\Filter\FilterManager;
public function __construct(private FilterManager $filterManager) {}
public function applyFilters(Request $request) {
$filters = $this->filterManager->getFilters('user');
// Process filters...
}
Filter Definition:
Define filters in YAML/array format with field types (string, boolean, float, ip, etc.).
Example:
filters:
product:
price: float[min: 0, max: 1000, format: decimal]
in_stock: boolean[required: true]
ip: ip[default: 0]
Validation & Sanitization:
Use the FilterManager to validate/sanitize input:
$validated = $filterManager->validate($inputData, 'product');
Database Field Generation: Dynamically generate SQL field definitions:
$dbField = $filterManager->getFilter('price')->getDbField();
// Output: `FLOAT UNSIGNED NOT NULL DEFAULT '0.00'`
Integration with Forms: Bind filters to Symfony forms for type-safe handling:
$form = $this->createFormBuilder()
->add('price', FloatType::class, [
'filter_config' => ['min' => 0, 'max' => 1000]
])
->getForm();
Custom Filter Types:
Extend AbstractFilter to add new types (e.g., DateFilter):
class DateFilter extends AbstractFilter {
public function getDbField(array $config = []): string {
return 'DATE' . ($config['required'] ? ' NOT NULL' : '');
}
}
Register in services.yaml:
services:
App\Filter\DateFilter:
tags: { name: alhames.filter.filter, type: date }
Dynamic Filter Loading:
Load filters from multiple sources (e.g., database) via FilterLoaderInterface:
$loader = new YamlFileLoader($this->container->getParameter('kernel.project_dir').'/config');
$filterManager->setLoader($loader);
Translation Support: Localize filter labels/errors:
# config/filters.yaml
filters:
user:
name: { label: 'user.name', type: string }
$translator = $this->container->get('translator');
$label = $translator->trans('user.name');
Type Mismatches:
float fields with format: integer will generate raw INT without precision.format: decimal for floats requiring precision.IP Address Handling:
ip filter stores IPs as unsigned integers (e.g., 192.168.1.1 → 3232235777).FilterManager::convertIpToInt()/convertIntToIp() for conversion.Default Values:
0/1 (not false/true) in SQL.default: 0 for false in config.YAML Parsing:
config: [key: value]) may break.filters:
product:
tags: { type: string, config: { min_length: 3, max_length: 255 } }
Validate Config:
Use FilterManager::validateConfig() to check for invalid types/configs:
try {
$filterManager->validateConfig($config);
} catch (InvalidFilterConfigException $e) {
// Handle error
}
SQL Field Debugging: Log generated fields for inspection:
$filters = $filterManager->getFilters('user');
foreach ($filters as $name => $filter) {
$this->logger->debug("Field [$name]: {$filter->getDbField()}");
}
Custom Validators:
Override validate() in a custom filter to add logic:
public function validate($value, array $config = []): mixed {
if ($value < 0 && !$config['allow_negative']) {
throw new \InvalidArgumentException('Negative values not allowed.');
}
return parent::validate($value, $config);
}
Database-Specific Adjustments:
Extend getDbField() for platform-specific SQL (e.g., PostgreSQL):
public function getDbField(array $config = []): string {
$field = parent::getDbField($config);
return str_replace('FLOAT', 'REAL', $field); // PostgreSQL uses REAL
}
Performance:
FilterManager instances if filters are static.FilterManager::getFilters() with a cache key:
$filters = $filterManager->getFilters('user', 'cache_key');
Testing:
FilterManager in tests:
$filterManager = $this->createMock(FilterManager::class);
$filterManager->method('validate')->willReturn($expectedData);
getDbField() with edge cases (e.g., max values exceeding DB_MAX_SIGNED_FLOAT).
How can I help you explore Laravel packages today?