PHPackages                             exegeseit/doctrinequerysearch-helper - 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. exegeseit/doctrinequerysearch-helper

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

exegeseit/doctrinequerysearch-helper
====================================

This package aims to facilitate the creation of dynamic WHERE clauses when using Doctrine\\ORM\\Querybuilder or Doctrine\\DBAL\\Querybuilder

v5.0.0(1mo ago)1130MITPHPPHP &gt;=8.2

Since Feb 17Pushed 1mo agoCompare

[ Source](https://github.com/exeGeseIT/DoctrineQuerySearchHelper)[ Packagist](https://packagist.org/packages/exegeseit/doctrinequerysearch-helper)[ RSS](/packages/exegeseit-doctrinequerysearch-helper/feed)WikiDiscussions main Synced yesterday

READMEChangelogDependencies (25)Versions (42)Used By (0)

DoctrineQuerySearchHelper
=========================

[](#doctrinequerysearchhelper)

[![PHP Version](https://camo.githubusercontent.com/4f0ff8d47b7c73441eb92a1f49af61c2d6521b14113c8fd85fac4416c863e7cc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e322d626c7565)](https://camo.githubusercontent.com/4f0ff8d47b7c73441eb92a1f49af61c2d6521b14113c8fd85fac4416c863e7cc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e322d626c7565)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)

DoctrineQuerySearchHelper is a lightweight helper package for building dynamic `WHERE` clauses with Doctrine ORM or Doctrine DBAL query builders.

It helps you convert a structured `$search` array into safe, reusable and centralized query conditions, while keeping your repositories easy to read and maintain.

Table of contents
-----------------

[](#table-of-contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Quick start](#quick-start)
- [How it works](#how-it-works)
    - [QueryClauseBuilder](#queryclausebuilder)
    - [SearchFilter](#searchfilter)
- [Defining searchable fields](#defining-searchable-fields)
- [Basic usage with Doctrine ORM](#basic-usage-with-doctrine-orm)
- [Using SearchFilter helpers](#using-searchfilter-helpers)
    - [Equality filters](#equality-filters)
    - [LIKE filters](#like-filters)
    - [NULL filters](#null-filters)
    - [Comparison filters](#comparison-filters)
    - [Composition helpers](#composition-helpers)
- [About tokenized search keys](#about-tokenized-search-keys)
- [Security](#security)
- [License](#license)

Requirements
------------

[](#requirements)

- PHP 8.2 or higher
- Doctrine ORM or Doctrine DBAL

Installation
------------

[](#installation)

Run the following command to install the package in your application:

```
$ composer require exegeseit/doctrinequerysearch-helper
```

Quick start
-----------

[](#quick-start)

The following example shows a minimal usage of the package with a Doctrine ORM `QueryBuilder`.

```
use ExeGeseIT\DoctrineQuerySearchHelper\QueryClauseBuilder;
use ExeGeseIT\DoctrineQuerySearchHelper\SearchFilter;
use App\Entity\User;
use App\Repository\UserRepository;

$qb = $entityManager->getRepository(User::class)->createQueryBuilder('u');

$clauseBuilder = QueryClauseBuilder::getInstance(qb);
$clauseBuilder->setSearchFields[
    'id' => 'u.id',
    'email' => 'u.email',
    'createdAt' => 'u.createdAt',
];

$search = [
    SearchFilter::equal('email') => 'john@example.com',
];

$users = $clauseBuilder->getQueryBuilder($search)->getQuery()->getResult();
```

This produces a condition equivalent to:

```
WHERE u.email = :email
```

How it works
------------

[](#how-it-works)

The package is based on two main components:

- `QueryClauseBuilder`
- `SearchFilter`

A typical use case is to create a `fetchQb()` method in your repository.

This method receives a `$search` array as parameter and returns a fully configured Doctrine `QueryBuilder` instance, including both the `SELECT` statement and the dynamic `WHERE` clause.

The `$search` parameter is an associative array where each entry defines one condition of the final `WHERE` clause:

```
$search = [
    SearchFilter::equal('email') => 'john@example.com',
];
```

QueryClauseBuilder
------------------

[](#queryclausebuilder)

`QueryClauseBuilder` receives a Doctrine `QueryBuilder` instance and applies the dynamic conditions defined in the `$search` array.

It also defines the list of allowed search keys and maps them to actual Doctrine fields or expressions.

For example:

```
$clauseBuilder->setSearchFields[
    'id' => 'u.id',
    'email' => 'u.email',
    'createdAt' => 'u.createdAt',
];
```

With this configuration, the `$search` array can use `id`, `email` and `createdAt` as search keys.

SearchFilter
------------

[](#searchfilter)

`SearchFilter` provides static helper methods used to generate the keys of the `$search` array.

For example:

```
$search = [
    SearchFilter::like('email') => 'john',
];
```

Each helper defines the type of condition to apply:

- equality
- inequality
- `LIKE`
- `NULL`
- comparison
- grouped `AND` / `OR` conditions

Defining searchable fields
--------------------------

[](#defining-searchable-fields)

Search keys must be explicitly declared before they can be used.

This is done with `setSearchFields()`:

```
$clauseBuilder->setSearchFields[
    'id' => 'u.id',
    'email' => 'u.email',
    'createdAt' => 'u.createdAt',
];
```

You can also define default `LIKE` fields with `setDefaultLikeFields()`:

```
$clauseBuilder->setDefaultLikeFields([
    'firstName' => 'u.firstName',
    'lastName' => 'u.lastName',
]);
```

Default `LIKE` fields allow these two definitions to be equivalent:

```
$search = [
    SearchFilter::filter('firstName') => 'john',
];
```

```
$search = [
    SearchFilter::like('firstName') => 'john',
];
```

Basic usage with Doctrine ORM
-----------------------------

[](#basic-usage-with-doctrine-orm)

> The examples below use Doctrine ORM, but the same approach can also be applied with Doctrine DBAL query builders.

The following example shows a repository method that creates a `QueryBuilder` to fetch `Market` objects.

It defines:

- the base `SELECT` statement;
- the allowed search keys;
- default `LIKE` fields;
- a default `ORDER BY` clause;
- the dynamic `WHERE` clause.

```
// src/Repository/MarketRepository.php
use App\Entity\Market;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use ExeGeseIT\DoctrineQuerySearchHelper\QueryClauseBuilder;

class UserRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Market::class);
    }

    public function fetchMarketQb(array $search = [], string $paginatorSort = ''): QueryBuilder
    {

        /**
         * Get a QueryBuilder instance and define its SELECT statement
         */
        $qb = $this->createQueryBuilder('m')
                ->innerJoin('m.organization', 'o')
                    ->addSelect('o')
                ->leftJoin('m.userofmarkets', 'uof', Join::WITH, 'uof.isaccountable = 1')
                    ->addSelect('uof')
                ->leftJoin('uof.user', 'u')
                    ->addSelect('u');

        $qb->addOrderBy('m.name');

        /*
         * Create a QueryClauseBuilder instance from the QueryBuilder.
         */
        $clauseBuilder = QueryClauseBuilder::getInstance($qb);

        $clauseBuilder
            /*
             * Define valid search keys and their entity field mapping.
             */
            ->setSearchFields([
                'idmarket' => 'm.id',
                'keymarket' => 'm.key',
                'idorganization' => 'o.id',
                'keyorganization' => 'o.key',
                'idmanager' => 'u.id',
                'isprivate' => 'm.isprivate',
                'amount' => 'm.amount',
                'createdAt' => 'm.createdAt',
                'deletedAt' => 'm.deletedAt',
            ])
            /*
             * Define default LIKE fields.
             *
             * If one of these search keys appears in the $search array through
             * SearchFilter::filter(), a LIKE filter is implicitly applied.
             */
            ->setDefaultLikeFields([
                'organization' => 'o.name',
                'market' => 'm.name',
                'name' => 'm.name',
                'manager' => "CONCAT(u.firstname, ' ', u.lastname)",
            ])
            ;

        /*
         * Apply the WHERE clause and return the configured QueryBuilder.
         */
        return $clauseBuilder->getQueryBuilder($search, $paginatorSort);
    }
}
```

You can then use this repository method from a controller or an application service:

```
// src/Controller/SomeController.php
use App\Entity\Market;
use Doctrine\ORM\EntityManagerInterface;
use ExeGeseIT\DoctrineQuerySearchHelper\SearchFilter;

class SomeController
{
    public function index(EntityManagerInterface $em) : void
    {
        $search = [
            SearchFilter::filter('idorganization') => $idorganization,
            SearchFilter::equal('manager') => $manager,
            SearchFilter::equal('isprivate') => false,
            SearchFilter::or() => [
                SearchFilter::equal('isprivate') => true,
                SearchFilter::greaterOrEqual('amount') => 5000,
            ],
        ];

        $markets = $em
            ->getRepository(Market::class)
            ->fetchMarketQb($search)
            ->getQuery()
            ->useQueryCache(true)
            ->getResult();

        // ...
    }
}
```

Using SearchFilter helpers
--------------------------

[](#using-searchfilter-helpers)

The following table summarizes the available filter helpers.

MethodSQL/DQL condition`SearchFilter::filter()`For not `falsy` value, applies a *default filter* depending on the configured field`SearchFilter::equal()``field = value``SearchFilter::notEqual()``field  value``SearchFilter::like()``field LIKE value``SearchFilter::notLike()``field NOT LIKE value``SearchFilter::likeStrict()``field LIKE value` without automatic wildcard handling`SearchFilter::notLikeStrict()``field NOT LIKE value` without automatic wildcard handling`SearchFilter::null()``field IS NULL``SearchFilter::notNull()``field IS NOT NULL``SearchFilter::greater()``field > value``SearchFilter::greaterOrEqual()``field >= value``SearchFilter::lower()``field < value``SearchFilter::lowerOrEqual()``field  $status,
];
```

Equivalent condition when `$status` is not `falsy`:

```
WHERE m.status LIKE :status -- if status is defined in the default LIKE fields
WHERE m.status = :status    -- otherwise
```

#### `SearchFilter::equal()`

[](#searchfilterequal)

```
SearchFilter::equal(string $searchKey, bool $tokenize = true): string
```

Applies an equality condition.

```
$search = [
    SearchFilter::equal('status') => $status,
];
```

Equivalent condition:

```
WHERE m.status = :status
```

#### `SearchFilter::notEqual()`

[](#searchfilternotequal)

```
SearchFilter::notEqual(string $searchKey, bool $tokenize = true): string
```

Applies an inequality condition.

```
$search = [
    SearchFilter::notEqual('status') => $status,
];
```

Equivalent condition:

```
WHERE m.status  :status
```

### LIKE filters

[](#like-filters)

#### `SearchFilter::like()`

[](#searchfilterlike)

```
SearchFilter::like(string $searchKey, bool $tokenize = true): string
```

Applies a `LIKE` condition.

```
$search = [
    SearchFilter::like('name') => $name,
];
```

Equivalent condition:

```
WHERE m.name LIKE :name
```

#### `SearchFilter::notLike()`

[](#searchfilternotlike)

```
SearchFilter::notLike(string $searchKey, bool $tokenize = true): string
```

Applies a `NOT LIKE` condition.

```
$search = [
    SearchFilter::notLike('name') => $name,
];
```

Equivalent condition:

```
WHERE m.name NOT LIKE :name
```

#### `SearchFilter::likeStrict()`

[](#searchfilterlikestrict)

```
SearchFilter::likeStrict(string $searchKey, bool $tokenize = true): string
```

Applies a strict `LIKE` condition.

Unlike `SearchFilter::like()`, the provided value is used as-is. Characters such as `%` and `_` are neither appended nor escaped. This is useful for exact matches.

```
$search = [
    SearchFilter::likeStrict('name') => $name,
];
```

Equivalent condition:

```
WHERE m.name LIKE :name
```

#### `SearchFilter::notLikeStrict()`

[](#searchfilternotlikestrict)

```
SearchFilter::notLikeStrict(string $searchKey, bool $tokenize = true): string
```

Applies a strict `NOT LIKE` condition.

Unlike `SearchFilter::notLike()`, the provided value is used as-is. Characters such as `%` and `_` are neither appended nor escaped. This is useful for exact matches.

```
$search = [
    SearchFilter::notLikeStrict('name') => $name,
];
```

Equivalent condition:

```
WHERE m.name NOT LIKE :name
```

### NULL filters

[](#null-filters)

#### `SearchFilter::null()`

[](#searchfilternull)

```
SearchFilter::null(string $searchKey, bool $tokenize = true): string
```

Applies an `IS NULL` condition.

```
$search = [
    SearchFilter::isNull('deletedAt') => true,
];
```

Equivalent condition:

```
WHERE m.deletedAt IS NULL
```

#### `SearchFilter::notNull()`

[](#searchfilternotnull)

```
SearchFilter::notNull(string $searchKey, bool $tokenize = true): string
```

Applies an `IS NOT NULL` condition.

```
$search = [
    SearchFilter::isNotNull('deletedAt') => true,
];
```

Equivalent condition:

```
WHERE m.deletedAt IS NOT NULL
```

### Comparison filters

[](#comparison-filters)

#### `SearchFilter::greater()`

[](#searchfiltergreater)

```
SearchFilter::greater(string $searchKey, bool $tokenize = true): string
```

Applies a greater-than condition.

```
$search = [
    SearchFilter::greater('amount') => $amount,
];
```

Equivalent condition:

```
WHERE m.amount > :amount
```

#### `SearchFilter::greaterOrEqual()`

[](#searchfiltergreaterorequal)

```
SearchFilter::greaterOrEqual(string $searchKey, bool $tokenize = true): string
```

Applies a greater-than-or-equal condition.

```
$search = [
    SearchFilter::greaterOrEqual('amount') => $amount,
];
```

Equivalent condition:

```
WHERE m.amount >= :amount
```

#### `SearchFilter::lower()`

[](#searchfilterlower)

```
SearchFilter::lower(string $searchKey, bool $tokenize = true): string
```

Applies a lower-than condition.

```
$search = [
    SearchFilter::lower('amount') => $amount,
];
```

Equivalent condition:

```
WHERE m.amount  $amount,
];
```

Equivalent condition:

```
WHERE m.amount  $startDate,
    SearchFilter::lowerOrEqual('createdAt') => $endDate,
 ];
```

Equivalent condition:

```
WHERE m.createdAt >= :startDate AND m.createdAt  $minAmount,
    SearchFilter::lower('amount') => $maxAmount,
 ];
```

Equivalent condition:

```
WHERE m.amount >= :minAmount AND m.amount  [
        SearchFilter::equal('status') => $statusDraf,
        SearchFilter::equal('status') => $statusPending,
];
```

Equivalent condition:

```
WHERE 1 = 1 AND (status = :statusDraft OR status = :statusPending)
```

### `SearchFilter::and()`

[](#searchfilterand)

```
SearchFilter::and(): string
```

Applies a grouped `AND` condition joined to the current query with `AND`.

```
$search = [
    SearchFilter::and() => [
         SearchFilter::greaterOrEqual('amount') => $amountMin,
         SearchFilter::lowerOrEqual('amount') => $amountMax,   ,
];
```

Equivalent condition:

```
WHERE 1 = 1 AND (amount >= :amountMin AND amount  [
         SearchFilter::equal('isprivate') => true,
         SearchFilter::greaterOrEqual('amount') => $amount,   ,
];
```

Equivalent condition:

```
sql WHERE 1 = 1 OR (isprivate = :isprivate AND amount >= :amount)
```

About tokenized search keys
---------------------------

[](#about-tokenized-search-keys)

Most `SearchFilter` methods accept a `$tokenize` argument.

```
SearchFilter::equal(string $searchKey, bool $tokenize = true): string
```

When `$tokenize` is enabled, a random suffix is added to the generated search key in order to avoid collisions when the same field is used multiple times in the same `$search` array.

This is useful when applying several conditions to the same field. For example:

```
$search = [
    SearchFilter::andOr() => [
        SearchFilter::equal('status') => $statusDraf,
        SearchFilter::equal('status') => $statusPending,
];
```

Without tokenization, both conditions would use the same *array key* and one condition could overwrite the other.

Tokenization makes each generated key unique.

Security
--------

[](#security)

Search values should be bound as query parameters and must not be interpolated directly into DQL or SQL strings.

This package is designed around declared search keys:

- allowed fields are explicitly configured with `setSearchFields()`;
- default `LIKE` fields are explicitly configured with `setDefaultLikeFields()`;
- user-provided search keys should not be passed directly without being mapped first.

This approach helps centralize the filtering logic and reduces the risk of exposing arbitrary fields or expressions.

License
-------

[](#license)

This package is released under the MIT License.

###  Health Score

48

—

FairBetter than 93% of packages

Maintenance88

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity74

Established project with proven stability

 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

Every ~38 days

Recently: every ~21 days

Total

41

Last Release

59d ago

Major Versions

v1.1.5 → v2.0.02022-09-06

v2.0.2 → v3.0.02023-05-09

v3.1.2 → v4.0.02025-03-08

v4.0.14 → v5.0.02026-05-07

PHP version history (5 changes)v1.0.0PHP ^7.4 || ^8.0.2

v1.0.4PHP ^7.4 | ^8.0.2

v2.0.0PHP ^8.0.2

v3.0.0PHP &gt;=8.1

v5.0.0PHP &gt;=8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/aa8bfad6584e52aa8fd8ec6aaa2f09beb053085423f711f051b0eb0d78600659?d=identicon)[jcglombard](/maintainers/jcglombard)

---

Top Contributors

[![jcglombard](https://avatars.githubusercontent.com/u/46714959?v=4)](https://github.com/jcglombard "jcglombard (80 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StyleECS

Type Coverage Yes

### Embed Badge

![Health badge](/badges/exegeseit-doctrinequerysearch-helper/health.svg)

```
[![Health](https://phpackages.com/badges/exegeseit-doctrinequerysearch-helper/health.svg)](https://phpackages.com/packages/exegeseit-doctrinequerysearch-helper)
```

###  Alternatives

[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)[rector/rector-src

Instant Upgrade and Automated Refactoring of any PHP code

136406.3k14](/packages/rector-rector-src)

PHPackages © 2026

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