Installation:
composer require check24/apitk-url-bundle
Ensure byWulf/ApiTKUrlBundle is registered in config/bundle.php.
First Use Case: Annotate a controller action to enable filtering, sorting, and pagination:
use Shopping\ApiTKUrlBundle\Annotation as ApiTK;
/**
* @Rest\Get("/v1/products")
* @ApiTK\Filter(name="name")
* @ApiTK\Sort(name="price", allowedDirections={"asc", "desc"})
* @ApiTK\Pagination(maxPerPage=50)
*/
public function getProducts() { ... }
Trigger Processing:
Add the ApiTKUrlListener to your services.yaml:
services:
Shopping\ApiTKUrlBundle\EventListener\ApiTKUrlListener:
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
Test the Endpoint:
Call GET /v1/products?filter[name]=laptop&sort[price]=asc&page=1 to see filters/sorting/pagination in action.
Basic Filtering:
@ApiTK\Filter(name="status") // Allows `filter[status][eq]=active`
eq, ne, gt, lt, in, nin comparisons by default.Complex Filtering:
@ApiTK\Filter(name="created", allowedComparisons={"gt", "lt"})
@ApiTK\Filter(name="category", enum={"electronics", "clothing"})
allowedComparisons to restrict operators.enum for predefined values (validates client input).Joined Table Fields:
@ApiTK\Filter(name="user.country", queryBuilderName="u.country")
Route Parameter Filters:
Define filters matching route placeholders (e.g., /users/{id} → @ApiTK\Filter(name="id")).
@ApiTK\Sort(name="name") // Allows `sort[name]=asc` or `sort[name]=desc`
?sort[name]=asc&sort[price]=desc.@ApiTK\Sort(name="created", allowedDirections={"asc"})
@ApiTK\Pagination(maxPerPage=20) // Limits `page[size]` to 20
maxPerPage to enforce a max (e.g., maxPerPage=100).apitk_url.default_page_size = 10).Doctrine Example:
$qb = $this->createQueryBuilder('p');
$applier = new \Shopping\ApiTKUrlBundle\QueryBuilder\FilterApplier($qb);
$applier->applyFilters($this->get('apitk_url.filter_manager')->getFilters());
$applier->applySorting($this->get('apitk_url.sort_manager')->getSorting());
$applier->applyPagination($this->get('apitk_url.pagination_manager')->getPagination());
Custom Query Builders:
Extend FilterApplier or implement FilterApplierInterface for non-Doctrine ORMs.
@ApiTK\Filter/@ApiTK\Sort annotations to auto-generate docs with supported parameters.
Example:
parameters:
- in: query
name: filter[name][eq]
schema:
type: string
description: Filter by name (exact match)
Annotation Loading:
api_platform.meta.factory.doctrine.orm.proxy is not used if annotations aren’t loading.api_platform.meta.factory.doctrine.orm or manually register annotations.Case Sensitivity:
snake_case or camelCase consistently.Query Builder Conflicts:
queryBuilderName clashes with existing query aliases, rename the alias in your DQL.u_country instead of country).Pagination Edge Cases:
page[size]=0 explicitly to avoid "page not found" errors.maxPerPage; validate server-side:
$size = min($request->query->getInt('page[size]', 10), $maxPerPage);
Route Overrides:
/users/{id}) take precedence over query filters with the same name.@ApiTK\Filter(name="user_id")).Enable Verbose Logging:
Add to config/packages/dev/apitk_url.yaml:
apitk_url:
debug: true
Logs filter/sort/pagination parsing to var/log/dev.log.
Validate Filters:
filter[nonexistent]=value) to trigger 400 responses.ApiTKUrlListener for parsed data:
$this->get('apitk_url.filter_manager')->getFilters();
Query Builder Debugging:
getSQL() to inspect generated queries:
$qb->getQuery()->getSQL();
Custom Filter Operators:
Extend FilterApplier to add operators like contains:
public function applyFilter(Filter $filter, QueryBuilder $qb) {
if ($filter->getComparison() === 'contains') {
$qb->andWhere("LOWER($filter->getField()) LIKE LOWER(:val)");
$qb->setParameter('val', "%{$filter->getValue()}%");
}
}
Dynamic Filter Sources: Fetch filter options from a database:
@ApiTK\Filter(name="category", dynamicSource="app.get_categories")
Implement DynamicFilterSourceInterface to resolve app.get_categories.
Override Default Behavior:
FilterManager, SortManager, or PaginationManager via DI:
services:
App\Custom\FilterManager:
decorates: 'apitk_url.filter_manager'
Non-Doctrine ORMs:
Create a custom FilterApplier for Eloquent, MongoDB, etc., by implementing FilterApplierInterface.
Cache Filter Enums:
If enum values are static, cache them to avoid repeated DB lookups.
Batch Filter Validation:
Validate all filters at once in ApiTKUrlListener to reduce overhead:
$this->get('apitk_url.filter_manager')->validateAll();
Limit Sorting Fields:
Restrict sorting to indexed columns to avoid ORDER BY performance hits.
Pagination Offsets:
For large datasets, use keyset pagination (e.g., ?after=last_id) instead of OFFSET.
How can I help you explore Laravel packages today?