{"id":18323636,"url":"https://github.com/mortalflesh/php-query-builder-composer","last_synced_at":"2025-04-09T15:12:40.574Z","repository":{"id":57016915,"uuid":"98468242","full_name":"MortalFlesh/php-query-builder-composer","owner":"MortalFlesh","description":"QueryBuilderComposer for easier composing Doctrine\\\\ORM\\\\QueryBuilder parts","archived":false,"fork":false,"pushed_at":"2017-07-31T21:10:11.000Z","size":26,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-09T15:12:37.247Z","etag":null,"topics":["doctrine","doctrine-orm","doctrine-query-builders","query-builder"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MortalFlesh.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-07-26T21:44:13.000Z","updated_at":"2020-03-26T18:26:32.000Z","dependencies_parsed_at":"2022-08-22T09:40:41.097Z","dependency_job_id":null,"html_url":"https://github.com/MortalFlesh/php-query-builder-composer","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MortalFlesh%2Fphp-query-builder-composer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MortalFlesh%2Fphp-query-builder-composer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MortalFlesh%2Fphp-query-builder-composer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MortalFlesh%2Fphp-query-builder-composer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MortalFlesh","download_url":"https://codeload.github.com/MortalFlesh/php-query-builder-composer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248055276,"owners_count":21040157,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["doctrine","doctrine-orm","doctrine-query-builders","query-builder"],"created_at":"2024-11-05T18:28:57.147Z","updated_at":"2025-04-09T15:12:40.553Z","avatar_url":"https://github.com/MortalFlesh.png","language":"PHP","readme":"Query Builder Composer\n======================\n\n[![Latest Stable Version](https://img.shields.io/packagist/v/mf/query-builder-composer.svg)](https://packagist.org/packages/mf/query-builder-composer)\n[![Build Status](https://travis-ci.org/MortalFlesh/php-query-builder-composer.svg?branch=master)](https://travis-ci.org/MortalFlesh/php-query-builder-composer)\n[![Coverage Status](https://coveralls.io/repos/github/MortalFlesh/php-query-builder-composer/badge.svg?branch=master)](https://coveralls.io/github/MortalFlesh/php-query-builder-composer?branch=master)\n[![Total Downloads](https://img.shields.io/packagist/dt/mf/query-builder-composer.svg)](https://packagist.org/packages/mf/query-builder-composer)\n[![License](https://img.shields.io/packagist/l/mf/query-builder-composer.svg)](https://packagist.org/packages/mf/query-builder-composer)\n\n**QueryBuilderComposer** for easier composing `Doctrine\\\\ORM\\\\QueryBuilder` parts\n\n## Install\n```bash\n    composer require mf/query-builder-composer\n```\n\n## Compose parts for `QueryBuilder`\n`Parts` are array of:\n- modifiers\n- rules\n\n### Modifier:\n`Modifier` is **ANY** `callable` by this pattern: `(QueryBuilder -\u003e QueryBuilder)`\n\n#### example of `Modifiers`:\n\n    - (anonymus function): [ function(QueryBuilder $qb) { return $qb-\u003eselect('...'); }, ... ]\n    - (static function)  : [ [$this, 'modifyQueryBuilder'], ... ]\n    - (closure)          : [ $addSelectModifier, ... ]\n    - (Modifier)         : [ new Modifier('...'), ... ]\n    - ...\n\n\n### Rule:\n`Rule` represents any `QueryBuilder` method call\n- array of `strings`\n- array of **single** `string` (_separator is `space`_)\n- just a **single** `string` (_separator is `space`_)\n\nLet's say we have this `QueryBuilder` method call:\n```php\n// method\n$queryBuilder-\u003efrom('student', 's');\n    \n// Rule\n['from', 'student', 's']\nOR\n['from student s']\nOR\n'from student s'\n```\n\n#### example of `Rules`:\n`(QueryBuilder method call) : (rule representation)`\n    \n    - $qb-\u003eselect('t.column')    : ['select', 't.column']\n    - $qb-\u003ejoin('t.joined', 'j') : ['join', 't.joined', 'j']\n    - $qb-\u003efrom('table', 't')    : ['from', 'table', 't']\n    - $qb-\u003efrom('table', 't')    : ['from table t']\n    - $qb-\u003efrom('table', 't')    : 'from table t'\n    - ...\n\n\n## Usage \n\n### Why? What is a problem?\nIf you have complex methods for building `Query` via `QueryBuilder`, you might be in same situation as I am.\nI have many similar methods to build different `Queries` and I cant see a clear way how to reuse my `QueryBuilder` parts.\n\nSo I decided to create this `QueryBuilderComposer` to make this issue easier.\n\n### Example of complex methods with duplicated parts\n_Methods are simplified so they might not be 100% correct._\n\n```php\npublic function countFreeApproved()\n{\n    return $this-\u003ecreateQueryBuilder('c')\n        -\u003eselect('COUNT(c.id)')\n        -\u003ewhere('c.price = 0')\n        -\u003eandWhere('c.approved = TRUE')\n        -\u003egetQuery()\n        -\u003egetSingleScalarResult();\n}\n\npublic function findMostViewedFreeCourses()\n{\n    return $this-\u003ecreateQueryBuilder('c')\n        -\u003eselect('c, i, COUNT(views) AS HIDDEN views')\n        -\u003einnerJoin('c.image', 'i')\n        -\u003ewhere('c.approved = TRUE')\n        -\u003eandWhere('c.price = 0')\n        -\u003eorderBy('views', 'DESC')\n        -\u003eaddOrderBy('c.position', 'ASC')\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n\npublic function findFreeCourses()\n{\n    return $this-\u003ecreateQueryBuilder('c')\n        -\u003eselect('c, i')\n        -\u003einnerJoin('c.image', 'i')\n        -\u003ewhere('c.approved = TRUE')\n        -\u003eandWhere('c.price = 0')\n        -\u003eaddOrderBy('c.position', 'ASC')\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n```\n\nNow you can have some idea of those parts which are same for more cases and they can be composed and defined once!\n\n### Composition of parts\n\n#### Step 1 (rewrite to `QueryBuilderComposer`)\n```php\npublic function countFreeApproved()\n{\n    return $queryBuilderComposer\n        -\u003ecompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            [\n                ['select', 'COUNT(c.id)'],\n                ['where', 'c.price = 0'],\n                ['andWhere', 'c.approved = TRUE'],\n            ]\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n    \npublic function findMostViewedFreeCourses()\n{\n    return $queryBuilderComposer\n        -\u003ecompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            [\n                ['select', 'c, i, COUNT(views) AS HIDDEN views'],\n                ['innerJoin', 'c.image', 'i'],\n                ['where', 'c.approved = TRUE'],\n                ['andWhere', 'c.price = 0'],\n                ['orderBy', 'views', 'DESC'],\n                ['addOrderBy', 'c.position', 'ASC'],\n            ]\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n    \npublic function findFreeCourses()\n{\n    return $queryBuilderComposer\n        -\u003ecompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            [\n                ['select', 'c, i'],\n                ['innerJoin', 'c.image', 'i'],\n                ['where', 'c.approved = TRUE'],\n                ['andWhere', 'c.price = 0'],\n                ['addOrderBy', 'c.position', 'ASC'],\n            ]\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n```\n\n#### Step 2 (store common rules to class constants to allow easier reuse)\n```php\nconst SELECT_COURSE = ['select', 'c, i'];\nconst JOIN_IMAGE = ['innerJoin', 'c.image', 'i'];\nconst FREE_COURSES = ['andWhere', 'c.price = 0'];\nconst APPROVED_ONLY = ['andWhere', 'c.approved = TRUE'];\nconst DEFAULT_ORDER = ['addOrderBy', 'c.position', 'ASC'];\n    \npublic function countFreeApproved()\n{\n    return $this-\u003equeryBuilderComposer\n        -\u003ecompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            [\n                ['select', 'COUNT(c.id)'],\n                self::FREE_COURSES,\n                self::APPROVED_ONLY,\n            ]\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n    \npublic function findMostViewedFreeCourses()\n{\n    return $this-\u003equeryBuilderComposer\n        -\u003ecompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            [\n                self::SELECT_COURSE, \n                ['COUNT(views) AS HIDDEN views'],\n                self::JOIN_IMAGE,\n                self::FREE_COURSES,\n                self::APPROVED_ONLY,\n                ['orderBy', 'views', 'DESC'],\n                self::DEFAULT_ORDER,\n            ]\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n    \npublic function findFreeCourses()\n{\n    return $this-\u003equeryBuilderComposer\n        -\u003ecompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            [\n                self::SELECT_COURSE,\n                self::JOIN_IMAGE,\n                self::FREE_COURSES,\n                self::APPROVED_ONLY,\n                self::DEFAULT_ORDER,\n            ]\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n```\n\n#### Step 3 (compose parts)\n```php\nconst SELECT_COURSE = ['select', 'c, i'];\nconst JOIN_IMAGE = ['innerJoin', 'c.image', 'i'];\nconst FREE_COURSES = ['andWhere', 'c.price = 0'];\nconst APPROVED_ONLY = ['andWhere', 'c.approved = TRUE'];\nconst DEFAULT_ORDER = ['addOrderBy', 'c.position', 'ASC'];\n    \nconst SELECT_COURSE_W_IMAGE = [\n    self::SELECT_COURSE,\n    self::JOIN_IMAGE,\n];\n    \nconst FREE_APPROVED = [\n    self::FREE_COURSES,\n    self::APPROVED_ONLY,\n];\n    \npublic function countFreeApproved()\n{\n    return $this-\u003equeryBuilderComposer\n        -\u003ecompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            array_merge(\n                [['select', 'COUNT(c.id)']],\n                self::FREE_APPROVED\n            )\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n    \npublic function findMostViewedFreeCourses()\n{\n    return $this-\u003equeryBuilderComposer\n        -\u003ecompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            array_merge(\n                self::SELECT_COURSE_W_IMAGE,\n                [\n                    ['COUNT(views) AS HIDDEN views'],\n                    ['orderBy', 'views', 'DESC'],\n                    self::DEFAULT_ORDER,\n                ],\n                self::FREE_APPROVED\n            )\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n    \npublic function findFreeCourses()\n{\n    return $this-\u003equeryBuilderComposer\n        -\u003ecompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            array_merge(\n                self::SELECT_COURSE_W_IMAGE,\n                [self::DEFAULT_ORDER],\n                self::FREE_APPROVED\n            )\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n```\n\n#### Step 4 (use _syntax sugar_ over `array_merge`)\n```php\npublic function countFreeApproved()\n{\n    return $this-\u003equeryBuilderComposer\n        -\u003emergeCompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            [['select', 'COUNT(c.id)']],\n            self::FREE_APPROVED\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n    \npublic function findMostViewedFreeCourses()\n{\n    return $this-\u003equeryBuilderComposer\n        -\u003emergeCompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            self::SELECT_COURSE_W_IMAGE,\n            [\n                ['COUNT(views) AS HIDDEN views'],\n                ['orderBy', 'views', 'DESC'],\n                self::DEFAULT_ORDER,\n            ],\n            self::FREE_APPROVED\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n    \npublic function findFreeCourses()\n{\n    return $this-\u003equeryBuilderComposer\n        -\u003emergeCompose(\n            $this-\u003ecreateQueryBuilder('c'),\n            self::SELECT_COURSE_W_IMAGE,\n            [self::DEFAULT_ORDER],\n            self::FREE_APPROVED\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n```\n\n### Difference between `compose` vs `mergeCompose`\n```php\n$baseParts = [\n    'select s.id s.name s.age',\n    'from student s',\n];\n\n$approvedMature = [\n    ['andWhere', 's.approved = true'],\n    ['andWhere', 's.age \u003e= 18'],\n];\n\n// following calls are the same!\n$queryBuilder = $composer-\u003ecompose($this-\u003equeryBuilder, array_merge($baseParts, $approvedMature));\n$queryBuilder = $composer-\u003emergeCompose($this-\u003equeryBuilder, $baseParts, $approvedMature);\n```\n\n\n#### Conclusion\nYou can merge, compose and reuse your `QueryBuilder` parts easy.\nExample above is just quick solution. You can do much more patterns over this `composition`:\n- implement `Modifier` to do something with `QueryBuilder`\n- implement `Closure` to be reapplied again\n- ...\n\n\n## How to add complex rulex to `QueryBuilder`\n\n```php\npublic function complexResult()\n{\n    $queryBuilder = $this-\u003ecreateQueryBuilder('c');\n    \n    $queryBuilder-\u003e...  // do anything you want with QueryBuilder here\n    \n    return $this-\u003equeryBuilderComposer\n        -\u003ecompose(\n            $queryBuilder,\n            [\n                // add more parts here... ,\n                \n                function(QueryBuilder $queryBuilder) {\n                    return $queryBuilder-\u003e...   // do anything you want with QueryBuilder here either\n                },\n                \n                // add more parts here... ,\n            ]\n        )\n        -\u003egetQuery()\n        -\u003egetResult();\n}\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmortalflesh%2Fphp-query-builder-composer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmortalflesh%2Fphp-query-builder-composer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmortalflesh%2Fphp-query-builder-composer/lists"}