Query Bundle
This bundle integrates derafu/query with Symfony by registering its operator catalogue, parsers and Doctrine ORM condition applier. It supports custom controllers, repositories and commands, as well as API Platform endpoints when that optional package is installed.
Requirements
- PHP 8.5 or higher
- Symfony 8.1 components compatible with the Composer constraints
- Doctrine ORM
^3.7, installed as a runtime dependency
The bundle registers the ORM bridge but does not configure the application’s database or EntityManager. Keep the application’s existing Doctrine configuration; applications using DoctrineBundle should retain its bundle registration and database settings.
API Platform is optional. SmartFilter is registered only when ApiPlatform\Doctrine\Orm\Filter\FilterInterface is available. Without API Platform, the operators, parsers and ORM bridge remain available through their interfaces. The integration tests exercise both installations, including API Platform 4.4 and real SQLite queries.
Installation
composer require derafu/symfony-query-bundle
The package uses Derafu dependencies from dev-main. If needed, allow development packages while preferring stable releases:
{
"minimum-stability": "dev",
"prefer-stable": true
}
Register the bundle in config/bundles.php, preserving the application’s other registrations:
return [
// Keep FrameworkBundle, DoctrineBundle and other application bundles here.
Derafu\QueryBundle\QueryBundle::class => ['all' => true],
];
Services are private and intended for autowiring. QueryBundle extends Symfony’s AbstractBundle and imports the library’s query-bridge-doctrine-orm-services.yaml from its Composer installation path. That configuration supplies the core parsers, operator catalogue, translation provider and ORM bridge, registered under their interfaces. The bundle loads resources/config/symfony-query-bundle-services.yaml for the optional API Platform filter when its interface is present.
After adding or removing API Platform, rebuild the application’s container with php bin/console cache:clear so service registration reflects the installed packages.
Configuration
The public configuration alias is derafu_query. By default, the bundle resolves the operators file from the actual Composer installation path of derafu/query, including custom vendor directories.
Both an omitted path and an explicit null select the default catalogue:
# config/packages/derafu_query.yaml
derafu_query:
operators_path: null
To select a custom catalogue:
derafu_query:
operators_path: '%kernel.project_dir%/config/query/operators.yaml'
The bundle maps operators_path to the shared derafu.query.operators.path parameter after importing Query’s defaults. Omitting the option or setting it to null preserves the default supplied by Query.
The selected file defines the entire operator catalogue; it is not automatically merged with the default file. Supply the types and operators sections expected by the operator loader. For example:
{% verbatim %}
# config/query/operators.yaml
types:
standard:
name: Standard
description: Standard comparison
operators:
'eq:':
type: standard
name: Equality
description: Application equality operator
sql: '{{column}} = {{value}}'
{% endverbatim %}
This catalogue supports expressions such as name?eq:Notebook. Wrong path types are rejected during configuration loading. A missing file raises the original package exception when the operator catalogue is loaded.
Available Services
Operators
| Interface | Purpose |
|---|---|
Derafu\Query\Operator\Contract\OperatorLoaderInterface |
Loads operator definitions from YAML or arrays. |
Derafu\Query\Operator\Contract\OperatorManagerFactoryInterface |
Builds a manager from an operators file. |
Derafu\Query\Operator\Contract\OperatorManagerInterface |
Provides the configured operators and symbol lookup. |
Parsers
| Interface | Input |
|---|---|
Derafu\Query\Filter\Contract\PathParserInterface |
A path such as customer__name, with supported segment options. |
Derafu\Query\Filter\Contract\FilterParserInterface |
An operator and operand, such as >=100 or =active. |
Derafu\Query\Filter\Contract\ExpressionParserInterface |
A complete condition, such as price?>100. |
Derafu\Query\Filter\Contract\CompositeExpressionParserInterface |
Conditions combined with &&, ` |
Doctrine ORM
Derafu\Query\Bridge\Contract\QueryBuilderConditionApplierInterface resolves to Derafu\Query\Bridge\DoctrineORMQueryBuilderConditionApplier. It adds parsed conditions to an existing Doctrine ORM QueryBuilder and binds their values as parameters.
Inject the interfaces listed above; the bundle does not add concrete-class aliases. SmartFilter receives the same condition-applier interface service as application consumers. Any replacement must support Doctrine ORM query builders. The bundle does not register PDO/DBAL engines or SqlQueryBuilder; those are separate integration paths of the underlying library.
Use Without API Platform
A custom AJAX endpoint can receive filters from a table, pass the expression to an application service and return the resulting rows. API Platform is not involved:
namespace App\Service;
use App\Entity\Product;
use Derafu\Query\Bridge\Contract\QueryBuilderConditionApplierInterface;
use Derafu\Query\Filter\Contract\CompositeExpressionParserInterface;
use Doctrine\ORM\EntityManagerInterface;
final class ProductSearch
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly CompositeExpressionParserInterface $parser,
private readonly QueryBuilderConditionApplierInterface $applier,
) {
}
/** @return list<array<string, mixed>> */
public function search(string $expression): array
{
$query = $this->entityManager->createQueryBuilder()
->select('p')
->from(Product::class, 'p');
$this->applier->apply($query, $this->parser->parse($expression));
return $query->getQuery()->getArrayResult();
}
}
For example, price?>100 && status?=active selects active products whose price is greater than 100. The same service can be called from a controller, export or Symfony command. The application decides which fields users may filter and applies its authorization restrictions; existing query restrictions are retained when the applier adds conditions.
API Platform Integration
With API Platform installed and configured in the application, the bundle registers Derafu\Query\Bridge\ApiPlatform\SmartFilter with the api_platform.filter tag. API Platform’s native filter locator consumes that tag.
Add parameters to an existing API resource, for example:
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\QueryParameter;
use Derafu\Query\Bridge\ApiPlatform\SmartFilter;
#[ApiResource(parameters: [
'price' => new QueryParameter(
property: 'price',
schema: ['type' => 'string'],
filter: SmartFilter::class,
),
'status' => new QueryParameter(
property: 'status',
schema: ['type' => 'string'],
filter: SmartFilter::class,
),
'filter' => new QueryParameter(
property: '__derafu_smart_filter',
schema: ['type' => 'string'],
filter: SmartFilter::class,
),
])]
class Product
{
// Keep the entity's existing mapping and properties.
}
The query parameters are strings containing filter expressions, including when the underlying property is numeric. For the price parameter, a value such as >100 becomes price?>100. The special property __derafu_smart_filter accepts a full expression, such as status?=active&&(price?>100||price?<30).
Encode these values as URL query parameters using the HTTP client, especially expressions containing &. SmartFilter retains the library’s behavior of ignoring malformed expressions and operators unsupported by the DQL bridge.
Translations
The imported library configuration registers Query’s tagged translation provider. To load its ICU exception catalogues into Symfony’s native translator, install and register Translation Bundle in the consuming application and configure the desired locale and fallbacks through FrameworkBundle.
Translation Bundle is optional for using the query services; Query Bundle does not replace the application’s translator or its contract alias.
Verification
ServicesTest boots real FrameworkBundle and DoctrineBundle applications, resolves the registered services and aliases, executes filters on SQLite, checks custom and default operators, and translates a real package exception. When API Platform is installed, it also boots its bundle and checks actual collection HTTP requests and filter-locator consumption.
composer update
composer validate --strict
composer tests
composer phpstan
composer phpcs
PHPStan runs at level 9. CI runs the complete suite and a separate installation without API Platform; only the API-specific HTTP tests are skipped in the latter.
See the Query documentation for expression syntax, operators, path navigation and security considerations.