Installation:
composer require andrii-mz/doctrine-qb-filter
Ensure your project uses Doctrine ORM (^2.7) and Symfony components (^4.1 or ^5.1).
Basic Usage:
Define a FilterRequest object (or extend it) to structure your filters:
use AndriiMz\DoctrineQbFilter\FilterRequest;
$filter = new FilterRequest();
$filter->filter['name'] = 'John'; // Exact match
$filter->filter['age']['gt'] = 25; // Greater than
First Query:
Inject the QueryFilter service (via DI container) and execute:
$queryFilter = $container->get('andrii_mz.doctrine_qb_filter.query_filter');
$result = $queryFilter->getResults(User::class, $filter);
$users = $result->items; // Filtered results
Key Files:
FilterRequest.php: Core filter structure.QueryFilter.php: Main service class.FilterBuilder.php: Handles filter logic (extend for custom rules).Dynamic Filtering:
Use FilterRequest to build reusable filter logic:
$filter = new FilterRequest();
$filter->filter['status'] = ['active', 'pending']; // IN clause
$filter->filter['created_at']['between'] = ['2023-01-01', '2023-12-31'];
Integration with Controllers:
Bind filter input to FilterRequest in a controller:
public function index(Request $request, QueryFilter $queryFilter) {
$filter = (new FilterRequest())->hydrate($request->query->all());
return $queryFilter->getResults(User::class, $filter);
}
Nested Relationships:
Filter on related entities (e.g., User with Task):
$filter->filter['task']['priority'] = 'high'; // Joins + WHERE
$filter->filter['task']['id']['is_not_null'] = true;
Pagination: Combine with Doctrine Paginator:
$result = $queryFilter->getResults(User::class, $filter, 10, 1); // 10 items/page, page 1
Custom Filter Rules:
Extend FilterBuilder to add domain-specific logic:
class CustomFilterBuilder extends FilterBuilder {
protected function buildCustomRule($field, $operator, $value) {
// Add logic for 'custom_operator'
}
}
FilterRequest to form components for UI-driven filtering.FilterRequest with API platforms (e.g., Symfony API Platform) for query params.FilterRequest inputs early (e.g., with Symfony Validator).Operator Limitations:
=, !=, >, <, IN, IS NULL, etc.).FilterBuilder for custom operators (e.g., LIKE, REGEXP).Performance:
fetch="LAZY" for large datasets or add DISTINCT manually.Case Sensitivity:
LOWER() in custom filter rules:
$qb->expr()->eq('LOWER(u.name)', ':name')->setParameter('name', strtolower($value));
Doctrine Events:
@PrePersist). Avoid filtering on transient entities.Proprietary License:
LICENSE file).$entityManager->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
if (!$this->supportsOperator($operator)) {
throw new \InvalidArgumentException("Operator '$operator' not supported for field '$field'");
}
Custom Filter Classes:
Create filter-specific classes (e.g., DateFilterRequest) to enforce types:
class DateFilterRequest extends FilterRequest {
public function __construct() {
$this->filter['created_at']['between'] = null; // Predefined field
}
}
Override FilterBuilder:
Subclass FilterBuilder to modify default behavior:
services:
andrii_mz.doctrine_qb_filter.filter_builder:
class: App\Filter\CustomFilterBuilder
public: true
Add Metadata: Use Symfony PropertyInfo to auto-detect filterable fields:
$propertyInfo = new PropertyInfo();
$filterableFields = $propertyInfo->getTypes(User::class);
Testing:
Mock QueryFilter in tests:
$queryFilter = $this->createMock(QueryFilter::class);
$queryFilter->method('getResults')->willReturn(new ResultSet([$user], 1));
How can I help you explore Laravel packages today?