Query Bundle

GitHub GitHub last commit CI Workflow GitHub code size in bytes GitHub Issues Total Downloads Monthly Downloads

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.

On this page

#php, #symfony
Last updated on 30/10/2026 by Anonymous