PHPackages                             hyvor/doctrine-filterq - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. hyvor/doctrine-filterq

ActiveLibrary[Database &amp; ORM](/categories/database)

hyvor/doctrine-filterq
======================

Advanced filtering for Doctrine ORM APIs

0.0.1(1mo ago)0120↑280%MITPHPPHP ^8.1CI passing

Since Jun 4Pushed 1mo agoCompare

[ Source](https://github.com/hyvor/doctrine-filterq)[ Packagist](https://packagist.org/packages/hyvor/doctrine-filterq)[ RSS](/packages/hyvor-doctrine-filterq/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (5)Versions (3)Used By (0)

FilterQ for Doctrine ORM
========================

[](#filterq-for-doctrine-orm)

FilterQ allows advanced filtering in Symfony APIs using Doctrine ORM. You can accept a single-line expression from your users like:

```
name=starter&(type=image|type=video)

```

And FilterQ will convert it to DQL WHERE conditions in your Doctrine QueryBuilder.

```
WHERE name = 'starter' AND (type = 'image' OR type = 'video')

```

---

FilterQ was built for [Hyvor Blogs](https://blogs.hyvor.com)' Data API. It was initially written for Laravel Eloquent and later ported to Doctrine ORM.

---

Features
--------

[](#features)

- Easy-to-write, single or multi-line expressions.
- Logical operators (`&` and `|`) and nesting/grouping (with `()`)
- Secure. FilterQ only gives access to the columns and operators you define.
- Supports joining related entities. Users can filter by joined entity fields.
- Supports "type hinting" for keys.
- Extensible. You can add your own operators easily (e.g., SQL `LIKE`).

FilterQ Expressions
===================

[](#filterq-expressions)

Example: `(published_at > 1639665890 & published_at < 1639695890) | is_featured=true`

A **FilterQ Expression** is a combination of **conditions**, connected and grouped using one or more of the following.

- `&` - AND
- `|` - OR
- `()` - to group logic

A condition has three parts:

- `key`
- `operator`
- `value`

### Key

[](#key)

Usually, a key maps to a DQL column reference (e.g., `p.id`, `a.name`). It should match `[a-zA-Z0-9_.]+`.

### Operators

[](#operators)

By default, the following operators are supported.

- `=` - equals
- `!=` - not equal
- `>` - greater than
- `=` - greater than or equals
- `createQueryBuilder()
    ->select('p')
    ->from(Post::class, 'p');

$qb = FilterQ::expression('id=100|slug=hello')
    ->queryBuilder($qb)
    ->keys(function ($keys) {
        $keys->add('id', 'p.id');
        $keys->add('slug', 'p.slug');
    })
    ->addWhere();

$posts = $qb->getQuery()->getResult();
```

The `FilterQ::expression()` static method is the entry point. The chain must end with `addWhere()`, which adds WHERE conditions to the QueryBuilder and returns it.

### 1. Setting the Expression and QueryBuilder

[](#1-setting-the-expression-and-querybuilder)

```
$qb = $em->createQueryBuilder()->select('p')->from(Post::class, 'p');

FilterQ::expression($request->query->get('filter'))
    ->queryBuilder($qb)
    // ...
```

`addWhere()` returns the Doctrine ORM `QueryBuilder` so you can continue chaining:

```
$posts = FilterQ::expression(...)
    ->queryBuilder($qb)
    ->keys(...)
    ->addWhere()
    ->setMaxResults(25)
    ->addOrderBy('p.id', 'DESC')
    ->getQuery()
    ->getResult();
```

### 2. Set Keys

[](#2-set-keys)

Define all keys the user is allowed to filter on. This prevents SQL injection — only the columns you explicitly allow can be used in expressions.

```
->keys(function ($keys) {
    $keys->add('id', 'p.id');
    $keys->add('slug', 'p.slug');
})
```

- `$keys->add($key, $column)` registers a key and returns a `Key` object for further configuration. `$column` is the DQL column reference — **always include the entity alias** (e.g., `p.id`, not just `id`).
- `Key::join()` sets a [join callback](#joins).
- `Key::operators()` sets [allowed operators](#key-operators).
- `Key::valueType()` defines [supported value types](#key-value-types).
- `Key::values()` defines [supported values](#key-values).

Joins
-----

[](#joins)

To filter on a related entity's field, use a join callback. The callback receives the QueryBuilder.

```
FilterQ::expression('author.name=hyvor')
    ->queryBuilder($qb)
    ->keys(function ($keys) {
        $keys->add('author.name', 'a.name')
            ->join(function ($qb) {
                $qb->leftJoin('p.author', 'a');
            });
    })
    ->addWhere();
```

> Even if the same key appears multiple times in the expression, the join callback is only called once.

Key Operators
-------------

[](#key-operators)

Restrict which operators are allowed for a key.

```
->keys(function ($keys) {
    // only allow these operators
    $keys->add('id', 'p.id')->operators('=,>,', true);
})
```

Key Value Types
---------------

[](#key-value-types)

Define the expected type for a key's value. Highly recommended for security and data integrity.

```
->keys(function ($keys) {
    $keys->add('id', 'p.id')->valueType('integer');
    $keys->add('name', 'p.name')->valueType('string');
    $keys->add('description', 'p.description')->valueType('string|null');
    $keys->add('created_at', 'p.created_at')->valueType('date');
})
```

Supported types:

Scalar: `int`, `float`, `string`, `null`, `bool`

Special:

- `numeric` — int, float, or numeric string
- `date` — a valid date/time string or Unix timestamp. Returns a `\DateTimeImmutable`. (Uses PHP's `strtotime()`, so relative dates like `"-7 days"` are supported.)

Multiple types can be combined with `|` or as an array:

```
$keys->add('created_at', 'p.created_at')->valueType('date|null');
// or
$keys->add('created_at', 'p.created_at')->valueType(['date', 'null']);
```

Key Values
----------

[](#key-values)

Restrict a key to a specific set of allowed values. Useful for enum columns.

```
->keys(function ($keys) {
    $keys->add('status', 'p.status')->values(['published', 'draft']);
    $keys->add('id', 'p.id')->values(200);
})
```

Custom Operators
----------------

[](#custom-operators)

Add custom DQL operators. The callback receives the QueryBuilder, an auto-generated parameter name, and the value. It must bind the parameter and return the DQL expression string.

```
FilterQ::expression("title~'Hello%'")
    ->queryBuilder($qb)
    ->keys(function ($keys) {
        $keys->add('title', 'p.title');
    })
    ->operators(function ($operators) {
        $operators->add('~', 'LIKE');
    })
    ->addWhere();
```

For more complex cases, use a callback:

```
->operators(function ($operators) {
    $operators->add('!', function ($qb, string $paramName, mixed $value): string {
        $qb->setParameter($paramName, $value);
        return 'MATCH (p.title) AGAINST (:' . $paramName . ')';
    });
})
```

The callback signature is `(QueryBuilder $qb, string $paramName, mixed $value): string`. It should:

1. Bind the value with `$qb->setParameter($paramName, $value)` if needed.
2. Return a DQL expression string.

Removing an Operator
--------------------

[](#removing-an-operator)

```
->operators(function ($operators) {
    $operators->remove('>');
})
```

Exception Handling
------------------

[](#exception-handling)

FilterQ throws three exceptions, all extending `FilterQException`:

- `Hyvor\FilterQ\Exceptions\FilterQException` — base exception
- `Hyvor\FilterQ\Exceptions\ParserException` — invalid expression syntax
- `Hyvor\FilterQ\Exceptions\InvalidValueException` — value fails key value/type validation

```
use Hyvor\FilterQ\Exceptions\FilterQException;

try {
    $qb = FilterQ::expression($filter)
        ->queryBuilder($qb)
        ->keys(...)
        ->addWhere();
} catch (FilterQException $e) {
    // $e->getMessage() is safe to display to API consumers
}
```

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance90

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity33

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

50d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/53796347?v=4)[HYVOR](/maintainers/HYVOR)[@hyvor](https://github.com/hyvor)

---

Top Contributors

[![supun-io](https://avatars.githubusercontent.com/u/44988673?v=4)](https://github.com/supun-io "supun-io (13 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/hyvor-doctrine-filterq/health.svg)

```
[![Health](https://phpackages.com/badges/hyvor-doctrine-filterq/health.svg)](https://phpackages.com/packages/hyvor-doctrine-filterq)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.9M400](/packages/easycorp-easyadmin-bundle)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1189.8k](/packages/rcsofttech-audit-trail-bundle)[kimai/kimai

Kimai - Time Tracking

4.8k9.0k1](/packages/kimai-kimai)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
