PHPackages                             mf/query-builder-composer - 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. mf/query-builder-composer

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

mf/query-builder-composer
=========================

QueryBuilderComposer for easier composing Doctrine\\ORM\\QueryBuilder parts

1.0.0(8y ago)123MITPHP

Since Jul 30Pushed 8y ago1 watchersCompare

[ Source](https://github.com/MortalFlesh/php-query-builder-composer)[ Packagist](https://packagist.org/packages/mf/query-builder-composer)[ RSS](/packages/mf-query-builder-composer/feed)WikiDiscussions master Synced 3w ago

READMEChangelogDependencies (8)Versions (3)Used By (0)

Query Builder Composer
======================

[](#query-builder-composer)

[![Latest Stable Version](https://camo.githubusercontent.com/3916c0e0df4a5330e6469174c63bc1425f243aa664c4fe0a27f77b69ad85443f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d662f71756572792d6275696c6465722d636f6d706f7365722e737667)](https://packagist.org/packages/mf/query-builder-composer)[![Build Status](https://camo.githubusercontent.com/4ab2d2c0e16cac3e084fc2bd5cf2d2ce92cc16f461e3b9ccfb7ef5646647cbf2/68747470733a2f2f7472617669732d63692e6f72672f4d6f7274616c466c6573682f7068702d71756572792d6275696c6465722d636f6d706f7365722e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/MortalFlesh/php-query-builder-composer)[![Coverage Status](https://camo.githubusercontent.com/2e2d4c0a0555a22083857fc90aa17d5fd53edab23a4acfd860b4aa62d5d106eb/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f4d6f7274616c466c6573682f7068702d71756572792d6275696c6465722d636f6d706f7365722f62616467652e7376673f6272616e63683d6d6173746572)](https://coveralls.io/github/MortalFlesh/php-query-builder-composer?branch=master)[![Total Downloads](https://camo.githubusercontent.com/82efe3bc34f74b25054ccd02caeb98c8bf5719477c81d058ab97ab5c5235447f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d662f71756572792d6275696c6465722d636f6d706f7365722e737667)](https://packagist.org/packages/mf/query-builder-composer)[![License](https://camo.githubusercontent.com/76132b514f00b1f26fb83d3b2d97203c2711bc982e91c2b5839e7e014863a74a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6d662f71756572792d6275696c6465722d636f6d706f7365722e737667)](https://packagist.org/packages/mf/query-builder-composer)

**QueryBuilderComposer** for easier composing `Doctrine\\ORM\\QueryBuilder` parts

Install
-------

[](#install)

```
    composer require mf/query-builder-composer
```

Compose parts for `QueryBuilder`
--------------------------------

[](#compose-parts-for-querybuilder)

`Parts` are array of:

- modifiers
- rules

### Modifier:

[](#modifier)

`Modifier` is **ANY** `callable` by this pattern: `(QueryBuilder -> QueryBuilder)`

#### example of `Modifiers`:

[](#example-of-modifiers)

```
- (anonymus function): [ function(QueryBuilder $qb) { return $qb->select('...'); }, ... ]
- (static function)  : [ [$this, 'modifyQueryBuilder'], ... ]
- (closure)          : [ $addSelectModifier, ... ]
- (Modifier)         : [ new Modifier('...'), ... ]
- ...

```

### Rule:

[](#rule)

`Rule` represents any `QueryBuilder` method call

- array of `strings`
- array of **single** `string` (*separator is `space`*)
- just a **single** `string` (*separator is `space`*)

Let's say we have this `QueryBuilder` method call:

```
// method
$queryBuilder->from('student', 's');

// Rule
['from', 'student', 's']
OR
['from student s']
OR
'from student s'
```

#### example of `Rules`:

[](#example-of-rules)

`(QueryBuilder method call) : (rule representation)`

```
- $qb->select('t.column')    : ['select', 't.column']
- $qb->join('t.joined', 'j') : ['join', 't.joined', 'j']
- $qb->from('table', 't')    : ['from', 'table', 't']
- $qb->from('table', 't')    : ['from table t']
- $qb->from('table', 't')    : 'from table t'
- ...

```

Usage
-----

[](#usage)

### Why? What is a problem?

[](#why-what-is-a-problem)

If you have complex methods for building `Query` via `QueryBuilder`, you might be in same situation as I am. I have many similar methods to build different `Queries` and I cant see a clear way how to reuse my `QueryBuilder` parts.

So I decided to create this `QueryBuilderComposer` to make this issue easier.

### Example of complex methods with duplicated parts

[](#example-of-complex-methods-with-duplicated-parts)

*Methods are simplified so they might not be 100% correct.*

```
public function countFreeApproved()
{
    return $this->createQueryBuilder('c')
        ->select('COUNT(c.id)')
        ->where('c.price = 0')
        ->andWhere('c.approved = TRUE')
        ->getQuery()
        ->getSingleScalarResult();
}

public function findMostViewedFreeCourses()
{
    return $this->createQueryBuilder('c')
        ->select('c, i, COUNT(views) AS HIDDEN views')
        ->innerJoin('c.image', 'i')
        ->where('c.approved = TRUE')
        ->andWhere('c.price = 0')
        ->orderBy('views', 'DESC')
        ->addOrderBy('c.position', 'ASC')
        ->getQuery()
        ->getResult();
}

public function findFreeCourses()
{
    return $this->createQueryBuilder('c')
        ->select('c, i')
        ->innerJoin('c.image', 'i')
        ->where('c.approved = TRUE')
        ->andWhere('c.price = 0')
        ->addOrderBy('c.position', 'ASC')
        ->getQuery()
        ->getResult();
}
```

Now you can have some idea of those parts which are same for more cases and they can be composed and defined once!

### Composition of parts

[](#composition-of-parts)

#### Step 1 (rewrite to `QueryBuilderComposer`)

[](#step-1-rewrite-to-querybuildercomposer)

```
public function countFreeApproved()
{
    return $queryBuilderComposer
        ->compose(
            $this->createQueryBuilder('c'),
            [
                ['select', 'COUNT(c.id)'],
                ['where', 'c.price = 0'],
                ['andWhere', 'c.approved = TRUE'],
            ]
        )
        ->getQuery()
        ->getResult();
}

public function findMostViewedFreeCourses()
{
    return $queryBuilderComposer
        ->compose(
            $this->createQueryBuilder('c'),
            [
                ['select', 'c, i, COUNT(views) AS HIDDEN views'],
                ['innerJoin', 'c.image', 'i'],
                ['where', 'c.approved = TRUE'],
                ['andWhere', 'c.price = 0'],
                ['orderBy', 'views', 'DESC'],
                ['addOrderBy', 'c.position', 'ASC'],
            ]
        )
        ->getQuery()
        ->getResult();
}

public function findFreeCourses()
{
    return $queryBuilderComposer
        ->compose(
            $this->createQueryBuilder('c'),
            [
                ['select', 'c, i'],
                ['innerJoin', 'c.image', 'i'],
                ['where', 'c.approved = TRUE'],
                ['andWhere', 'c.price = 0'],
                ['addOrderBy', 'c.position', 'ASC'],
            ]
        )
        ->getQuery()
        ->getResult();
}
```

#### Step 2 (store common rules to class constants to allow easier reuse)

[](#step-2-store-common-rules-to-class-constants-to-allow-easier-reuse)

```
const SELECT_COURSE = ['select', 'c, i'];
const JOIN_IMAGE = ['innerJoin', 'c.image', 'i'];
const FREE_COURSES = ['andWhere', 'c.price = 0'];
const APPROVED_ONLY = ['andWhere', 'c.approved = TRUE'];
const DEFAULT_ORDER = ['addOrderBy', 'c.position', 'ASC'];

public function countFreeApproved()
{
    return $this->queryBuilderComposer
        ->compose(
            $this->createQueryBuilder('c'),
            [
                ['select', 'COUNT(c.id)'],
                self::FREE_COURSES,
                self::APPROVED_ONLY,
            ]
        )
        ->getQuery()
        ->getResult();
}

public function findMostViewedFreeCourses()
{
    return $this->queryBuilderComposer
        ->compose(
            $this->createQueryBuilder('c'),
            [
                self::SELECT_COURSE,
                ['COUNT(views) AS HIDDEN views'],
                self::JOIN_IMAGE,
                self::FREE_COURSES,
                self::APPROVED_ONLY,
                ['orderBy', 'views', 'DESC'],
                self::DEFAULT_ORDER,
            ]
        )
        ->getQuery()
        ->getResult();
}

public function findFreeCourses()
{
    return $this->queryBuilderComposer
        ->compose(
            $this->createQueryBuilder('c'),
            [
                self::SELECT_COURSE,
                self::JOIN_IMAGE,
                self::FREE_COURSES,
                self::APPROVED_ONLY,
                self::DEFAULT_ORDER,
            ]
        )
        ->getQuery()
        ->getResult();
}
```

#### Step 3 (compose parts)

[](#step-3-compose-parts)

```
const SELECT_COURSE = ['select', 'c, i'];
const JOIN_IMAGE = ['innerJoin', 'c.image', 'i'];
const FREE_COURSES = ['andWhere', 'c.price = 0'];
const APPROVED_ONLY = ['andWhere', 'c.approved = TRUE'];
const DEFAULT_ORDER = ['addOrderBy', 'c.position', 'ASC'];

const SELECT_COURSE_W_IMAGE = [
    self::SELECT_COURSE,
    self::JOIN_IMAGE,
];

const FREE_APPROVED = [
    self::FREE_COURSES,
    self::APPROVED_ONLY,
];

public function countFreeApproved()
{
    return $this->queryBuilderComposer
        ->compose(
            $this->createQueryBuilder('c'),
            array_merge(
                [['select', 'COUNT(c.id)']],
                self::FREE_APPROVED
            )
        )
        ->getQuery()
        ->getResult();
}

public function findMostViewedFreeCourses()
{
    return $this->queryBuilderComposer
        ->compose(
            $this->createQueryBuilder('c'),
            array_merge(
                self::SELECT_COURSE_W_IMAGE,
                [
                    ['COUNT(views) AS HIDDEN views'],
                    ['orderBy', 'views', 'DESC'],
                    self::DEFAULT_ORDER,
                ],
                self::FREE_APPROVED
            )
        )
        ->getQuery()
        ->getResult();
}

public function findFreeCourses()
{
    return $this->queryBuilderComposer
        ->compose(
            $this->createQueryBuilder('c'),
            array_merge(
                self::SELECT_COURSE_W_IMAGE,
                [self::DEFAULT_ORDER],
                self::FREE_APPROVED
            )
        )
        ->getQuery()
        ->getResult();
}
```

#### Step 4 (use *syntax sugar* over `array_merge`)

[](#step-4-use-syntax-sugar-over-array_merge)

```
public function countFreeApproved()
{
    return $this->queryBuilderComposer
        ->mergeCompose(
            $this->createQueryBuilder('c'),
            [['select', 'COUNT(c.id)']],
            self::FREE_APPROVED
        )
        ->getQuery()
        ->getResult();
}

public function findMostViewedFreeCourses()
{
    return $this->queryBuilderComposer
        ->mergeCompose(
            $this->createQueryBuilder('c'),
            self::SELECT_COURSE_W_IMAGE,
            [
                ['COUNT(views) AS HIDDEN views'],
                ['orderBy', 'views', 'DESC'],
                self::DEFAULT_ORDER,
            ],
            self::FREE_APPROVED
        )
        ->getQuery()
        ->getResult();
}

public function findFreeCourses()
{
    return $this->queryBuilderComposer
        ->mergeCompose(
            $this->createQueryBuilder('c'),
            self::SELECT_COURSE_W_IMAGE,
            [self::DEFAULT_ORDER],
            self::FREE_APPROVED
        )
        ->getQuery()
        ->getResult();
}
```

### Difference between `compose` vs `mergeCompose`

[](#difference-between-compose-vs-mergecompose)

```
$baseParts = [
    'select s.id s.name s.age',
    'from student s',
];

$approvedMature = [
    ['andWhere', 's.approved = true'],
    ['andWhere', 's.age >= 18'],
];

// following calls are the same!
$queryBuilder = $composer->compose($this->queryBuilder, array_merge($baseParts, $approvedMature));
$queryBuilder = $composer->mergeCompose($this->queryBuilder, $baseParts, $approvedMature);
```

#### Conclusion

[](#conclusion)

You can merge, compose and reuse your `QueryBuilder` parts easy. Example above is just quick solution. You can do much more patterns over this `composition`:

- implement `Modifier` to do something with `QueryBuilder`
- implement `Closure` to be reapplied again
- ...

How to add complex rulex to `QueryBuilder`
------------------------------------------

[](#how-to-add-complex-rulex-to-querybuilder)

```
public function complexResult()
{
    $queryBuilder = $this->createQueryBuilder('c');

    $queryBuilder->...  // do anything you want with QueryBuilder here

    return $this->queryBuilderComposer
        ->compose(
            $queryBuilder,
            [
                // add more parts here... ,

                function(QueryBuilder $queryBuilder) {
                    return $queryBuilder->...   // do anything you want with QueryBuilder here either
                },

                // add more parts here... ,
            ]
        )
        ->getQuery()
        ->getResult();
}
```

###  Health Score

28

—

LowBetter than 52% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity8

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity64

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

Unknown

Total

1

Last Release

3250d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2bdf0b9957c08a48e70a52fce74fc4f1add30b12d442450d5e2b48854fc98b21?d=identicon)[MortalFlesh](/maintainers/MortalFlesh)

---

Top Contributors

[![MortalFlesh](https://avatars.githubusercontent.com/u/6317184?v=4)](https://github.com/MortalFlesh "MortalFlesh (13 commits)")

---

Tags

doctrinedoctrine-ormdoctrine-query-buildersquery-builderormdoctrinequerybuilder

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/mf-query-builder-composer/health.svg)

```
[![Health](https://phpackages.com/badges/mf-query-builder-composer/health.svg)](https://phpackages.com/packages/mf-query-builder-composer)
```

###  Alternatives

[laravel-doctrine/orm

An integration library for Laravel and Doctrine ORM

8385.5M96](/packages/laravel-doctrine-orm)[scienta/doctrine-json-functions

A set of extensions to Doctrine that add support for json query functions.

58725.2M48](/packages/scienta-doctrine-json-functions)[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.

1155.2k](/packages/rcsofttech-audit-trail-bundle)[api-platform/doctrine-orm

Doctrine ORM bridge

263.9M78](/packages/api-platform-doctrine-orm)[fourlabs/qbjs-parser

Parse JSON coming from jQuery QueryBuilder, into database queries.

2536.0k2](/packages/fourlabs-qbjs-parser)[fourlabs/qbjs-parser-bundle

This bundle is a Symfony wrapper for fourlabs/qbjs-parser.

1514.8k1](/packages/fourlabs-qbjs-parser-bundle)

PHPackages © 2026

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