├── .coveralls.yml ├── CHANGELOG.md ├── CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json ├── composer.lock ├── config ├── module.config.php └── zf-doctrine-querybuilder.global.php.dist └── src ├── Filter ├── FilterInterface.php ├── ODM │ ├── AbstractFilter.php │ ├── Between.php │ ├── Equals.php │ ├── GreaterThan.php │ ├── GreaterThanOrEquals.php │ ├── In.php │ ├── IsNotNull.php │ ├── IsNull.php │ ├── LessThan.php │ ├── LessThanOrEquals.php │ ├── Like.php │ ├── NotEquals.php │ ├── NotIn.php │ └── Regex.php ├── ORM │ ├── AbstractFilter.php │ ├── AndX.php │ ├── Between.php │ ├── Equals.php │ ├── GreaterThan.php │ ├── GreaterThanOrEquals.php │ ├── In.php │ ├── InnerJoin.php │ ├── IsMemberOf.php │ ├── IsNotNull.php │ ├── IsNull.php │ ├── LeftJoin.php │ ├── LessThan.php │ ├── LessThanOrEquals.php │ ├── Like.php │ ├── NotEquals.php │ ├── NotIn.php │ ├── NotLike.php │ └── OrX.php └── Service │ ├── ODMFilterManager.php │ ├── ODMFilterManagerFactory.php │ ├── ORMFilterManager.php │ └── ORMFilterManagerFactory.php ├── Hydrator └── Strategy │ ├── CollectionLinkHydratorV2.php │ └── CollectionLinkHydratorV3.php ├── Module.php ├── OrderBy ├── ODM │ ├── AbstractOrderBy.php │ └── Field.php ├── ORM │ ├── AbstractOrderBy.php │ └── Field.php ├── OrderByInterface.php └── Service │ ├── ODMOrderByManager.php │ ├── ODMOrderByManagerFactory.php │ ├── ORMOrderByManager.php │ └── ORMOrderByManagerFactory.php ├── Query └── Provider │ ├── DefaultOdm.php │ ├── DefaultOdmFactory.php │ ├── DefaultOrm.php │ └── DefaultOrmFactory.php └── _autoload.php /.coveralls.yml: -------------------------------------------------------------------------------- 1 | coverage_clover: clover.xml 2 | json_path: coveralls-upload.json 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 1.8.1 - TBD 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 1.8.0 - 2019-02-02 28 | 29 | ### Added 30 | 31 | - [#52](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/52) adds support for PHP 7.3. 32 | 33 | - [#46](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/46) adds support for DoctrineModule 2.1 34 | and DoctrineORMModule 2.1. 35 | 36 | ### Changed 37 | 38 | - Nothing. 39 | 40 | ### Deprecated 41 | 42 | - Nothing. 43 | 44 | ### Removed 45 | 46 | - Nothing. 47 | 48 | ### Fixed 49 | 50 | - Nothing. 51 | 52 | ## 1.7.0 - 2018-12-11 53 | 54 | ### Added 55 | 56 | - [#50](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/50) adds support for zend-hydrator version 3 releases, while retaining 57 | compatibility with previous versions. 58 | 59 | ### Changed 60 | 61 | - Nothing. 62 | 63 | ### Deprecated 64 | 65 | - Nothing. 66 | 67 | ### Removed 68 | 69 | - Nothing. 70 | 71 | ### Fixed 72 | 73 | - Nothing. 74 | 75 | ## 1.6.0 - 2018-01-17 76 | 77 | ### Added 78 | 79 | - [#42](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/42) adds 80 | `leftjoin` ORM query type. 81 | 82 | - [#44](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/44) adds 83 | support for PHP 7.2. 84 | 85 | ### Changed 86 | 87 | - [#38](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/38) changes 88 | methods visibility in Query Providers to `protected`: 89 | - `getConfig()` 90 | - `getFilterManager()` 91 | - `getOrderByManager()` 92 | 93 | ### Deprecated 94 | 95 | - Nothing. 96 | 97 | ### Removed 98 | 99 | - [#44](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/44) removes 100 | support for HHVM. 101 | 102 | ### Fixed 103 | 104 | - [#40](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/40) fixes 105 | ODM `isnull` and `isnotnull` query filters to give correct result with 106 | nullable fields. 107 | 108 | ## 1.5.1 - 2016-11-14 109 | 110 | ### Added 111 | 112 | - Nothing. 113 | 114 | ### Deprecated 115 | 116 | - Nothing. 117 | 118 | ### Removed 119 | 120 | - Nothing. 121 | 122 | ### Fixed 123 | 124 | - [#35](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/35) fixes 125 | an issue with `DefaultOdmFactory` and `DefaultOrmFactory` when used with 126 | ZF2 with context of `AbstractPluginManager`. 127 | 128 | ## 1.5.0 - 2016-11-10 129 | 130 | ### Added 131 | 132 | - [#32](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/32) adds 133 | support for PHP 7. 134 | - [#32](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/32) adds 135 | support for v3 releases of Zend Framework components, while retaining 136 | compatibility for v2 releases. 137 | - [#32](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/32) exposes 138 | the module to [zendframework/zend-component-installer](https://github.com/zendframework/zend-component-installer). 139 | 140 | ### Deprecated 141 | 142 | - Nothing. 143 | 144 | ### Removed 145 | 146 | - [#32](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/32) removes 147 | support for PHP 5.4 and PHP 5.5. 148 | 149 | ### Fixed 150 | 151 | - [#32](https://github.com/zfcampus/zf-doctrine-querybuilder/pull/32) adds a 152 | ton of tests to the module. 153 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | The Zend Framework project adheres to [The Code Manifesto](http://codemanifesto.com) 4 | as its guidelines for contributor interactions. 5 | 6 | ## The Code Manifesto 7 | 8 | We want to work in an ecosystem that empowers developers to reach their 9 | potential — one that encourages growth and effective collaboration. A space that 10 | is safe for all. 11 | 12 | A space such as this benefits everyone that participates in it. It encourages 13 | new developers to enter our field. It is through discussion and collaboration 14 | that we grow, and through growth that we improve. 15 | 16 | In the effort to create such a place, we hold to these values: 17 | 18 | 1. **Discrimination limits us.** This includes discrimination on the basis of 19 | race, gender, sexual orientation, gender identity, age, nationality, technology 20 | and any other arbitrary exclusion of a group of people. 21 | 2. **Boundaries honor us.** Your comfort levels are not everyone’s comfort 22 | levels. Remember that, and if brought to your attention, heed it. 23 | 3. **We are our biggest assets.** None of us were born masters of our trade. 24 | Each of us has been helped along the way. Return that favor, when and where 25 | you can. 26 | 4. **We are resources for the future.** As an extension of #3, share what you 27 | know. Make yourself a resource to help those that come after you. 28 | 5. **Respect defines us.** Treat others as you wish to be treated. Make your 29 | discussions, criticisms and debates from a position of respectfulness. Ask 30 | yourself, is it true? Is it necessary? Is it constructive? Anything less is 31 | unacceptable. 32 | 6. **Reactions require grace.** Angry responses are valid, but abusive language 33 | and vindictive actions are toxic. When something happens that offends you, 34 | handle it assertively, but be respectful. Escalate reasonably, and try to 35 | allow the offender an opportunity to explain themselves, and possibly correct 36 | the issue. 37 | 7. **Opinions are just that: opinions.** Each and every one of us, due to our 38 | background and upbringing, have varying opinions. The fact of the matter, is 39 | that is perfectly acceptable. Remember this: if you respect your own 40 | opinions, you should respect the opinions of others. 41 | 8. **To err is human.** You might not intend it, but mistakes do happen and 42 | contribute to build experience. Tolerate honest mistakes, and don't hesitate 43 | to apologize if you make one yourself. 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | Apigility and related modules (of which this is one) are open source and licensed 4 | as [BSD-3-Clause](http://opensource.org/licenses/BSD-3-Clause). Contributions 5 | are welcome in the form of issue reports and pull requests. 6 | 7 | All pull requests should include unit tests when applicable, and should follow 8 | our coding standards (more on these below); failure to do so may result in 9 | rejection of the pull request. If you need help writing tests, please ask on the 10 | developer mailing list and/or in IRC. 11 | 12 | ## RESOURCES 13 | 14 | If you wish to contribute to Apigility modules, please be sure to 15 | read/subscribe to the following resources: 16 | 17 | - [Coding Standards](https://github.com/zendframework/zf2/wiki/Coding-Standards) 18 | - [ZF Git Guide](https://github.com/zendframework/zf2/blob/master/README-GIT.md) 19 | - [Apigility developer mailing list](http://bit.ly/apigility-dev) 20 | - Apigility developer IRC channel: #apigility-dev on Freenode.net 21 | 22 | If you are working on new features, refactoring an existing module, or proposing 23 | a new module, please send an email to the developer mailing list. 24 | 25 | ## REPORTING POTENTIAL SECURITY ISSUES 26 | 27 | If you have encountered a potential security vulnerability in any Apigility 28 | module, please report it to us at [zf-security@zend.com](mailto:zf-security@zend.com). 29 | We will work with you to verify the vulnerability and patch it. 30 | 31 | When reporting issues, please provide the following information: 32 | 33 | - Module(s) affected 34 | - A description indicating how to reproduce the issue 35 | - A summary of the security vulnerability and impact 36 | 37 | We request that you contact us via the email address above and give the project 38 | contributors a chance to resolve the vulnerability and issue a new release prior 39 | to any public exposure; this helps protect Apigility users, and provides them 40 | with a chance to upgrade and/or update in order to protect their applications. 41 | 42 | For sensitive email communications, please use 43 | [our PGP key](http://framework.zend.com/zf-security-pgp-key.asc). 44 | 45 | ## RUNNING TESTS 46 | 47 | First, use [Composer](https://getcomposer.org) to install all dependencies: 48 | 49 | ```console 50 | $ composer install 51 | ``` 52 | 53 | To run tests: 54 | 55 | ```console 56 | $ composer test 57 | ``` 58 | 59 | ## CODING STANDARDS 60 | 61 | While Apigility uses Zend Framework 2 coding standards, in practice, we check 62 | standards against PSR-1/2. To check for CS issues: 63 | 64 | ```console 65 | $ composer cs-check 66 | ``` 67 | 68 | This will report CS issues. You can also attempt to fix many reported errors 69 | automatically: 70 | 71 | ```console 72 | $ composer cs-fix 73 | ``` 74 | 75 | If you use `cs-fix` to fix issues, make certain you add and commit any files 76 | changed! 77 | 78 | ## Conduct 79 | 80 | Please see our [CONDUCT.md](CONDUCT.md) to understand expected behavior when interacting with others in the project. 81 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2019, Zend Technologies USA, Inc. 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | - Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | - Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ZF Doctrine QueryBuilder 2 | ======================== 3 | 4 | > ## Repository abandoned 2019-12-31 5 | > 6 | > This repository has moved to [laminas-api-tools/api-tools-doctrine-querybuilder](https://github.com/laminas-api-tools/api-tools-doctrine-querybuilder). 7 | 8 | [![Build Status](https://travis-ci.org/zfcampus/zf-doctrine-querybuilder.svg?branch=master)](https://travis-ci.org/zfcampus/zf-doctrine-querybuilder) 9 | [![Coverage Status](https://coveralls.io/repos/github/zfcampus/zf-doctrine-querybuilder/badge.svg?branch=master)](https://coveralls.io/github/zfcampus/zf-doctrine-querybuilder?branch=master) 10 | [![Total Downloads](https://poser.pugx.org/zfcampus/zf-doctrine-querybuilder/downloads)](https://packagist.org/packages/zfcampus/zf-doctrine-querybuilder) 11 | 12 | This library provides query builder directives from array parameters. This library was designed 13 | to apply filters from an HTTP request to give an API fluent filter and order-by dialects. 14 | 15 | 16 | Philosophy 17 | ---------- 18 | 19 | Given developers identified A and B: A == B with respect to ability and desire to filter and sort the entity data. 20 | 21 | The Doctrine entity to share contains 22 | ``` 23 | id integer, 24 | name string, 25 | startAt datetime, 26 | endAt datetime, 27 | ``` 28 | 29 | Developer A or B writes the API. The resource is a single Doctrine Entity and the data 30 | is queried using a Doctrine QueryBuilder `$objectManager->createQueryBuilder()`. 31 | This module gives the other developer the same filtering and sorting ability to the 32 | Doctrine query builder, but accessed through request parameters, as the API author. 33 | For instance, `startAt between('2015-01-09', '2015-01-11');` and `name like ('%arlie')` 34 | are not common API filters for hand rolled APIs and perhaps without this module the API 35 | author would choose not to implement it for their reason(s). With the help of this 36 | module the API developer can implement complex queryability to resources without 37 | complicated effort thereby maintaining A == B. 38 | 39 | 40 | Installation 41 | ------------ 42 | 43 | Installation of this module uses composer. For composer documentation, please refer to 44 | [getcomposer.org](http://getcomposer.org/). 45 | 46 | ```bash 47 | $ composer require zfcampus/zf-doctrine-querybuilder 48 | ``` 49 | 50 | Once installed, add `ZF\Doctrine\QueryBuilder` to your list of modules inside 51 | `config/application.config.php`. 52 | 53 | > ### zf-component-installer 54 | > 55 | > If you use [zf-component-installer](https://github.com/zendframework/zf-component-installer), 56 | > that plugin will install zf-doctrine-querybuilder as a module for you. 57 | 58 | 59 | Configuring the Module 60 | ---------------------- 61 | 62 | Copy `config/zf-doctrine-querybuilder.global.php.dist` to `config/autoload/zf-doctrine-querybuilder.global.php` 63 | and edit the list of aliases for orm and odm to those you want enabled by default. 64 | 65 | 66 | Use With Apigility Doctrine 67 | --------------------------- 68 | 69 | To enable all filters you may override the default query providers in `zf-apigility-doctrine`. 70 | Add this to your `zf-doctrine-querybuilder.global.php` config file and filters and order-by will be applied 71 | if they are in `$_GET['filter']` or `$_GET['order-by']` request. These `$_GET` keys are customizable 72 | through `zf-doctrine-querybuilder-options`: 73 | 74 | ```php 75 | 'zf-apigility-doctrine-query-provider' => [ 76 | 'aliases' => [ 77 | 'default_orm' => \ZF\Doctrine\QueryBuilder\Query\Provider\DefaultOrm::class, 78 | 'default_odm' => \ZF\Doctrine\QueryBuilder\Query\Provider\DefaultOdm::class, 79 | ], 80 | 'factories' => [ 81 | \ZF\Doctrine\QueryBuilder\Query\Provider\DefaultOrm::class => \ZF\Doctrine\QueryBuilder\Query\Provider\DefaultOrmFactory::class, 82 | \ZF\Doctrine\QueryBuilder\Query\Provider\DefaultOdm::class => \ZF\Doctrine\QueryBuilder\Query\Provider\DefaultOdmFactory::class, 83 | ], 84 | ], 85 | ``` 86 | 87 | 88 | Use 89 | --- 90 | 91 | Configuration example 92 | ```php 93 | 'zf-doctrine-querybuilder-orderby-orm' => [ 94 | 'aliases' => [ 95 | 'field' => \ZF\Doctrine\QueryBuilder\OrderBy\ORM\Field::class, 96 | ], 97 | 'factories' => [ 98 | \ZF\Doctrine\QueryBuilder\OrderBy\ORM\Field::class => \Zend\ServiceManager\Factory\InvokableFactory::class, 99 | ], 100 | ], 101 | 'zf-doctrine-querybuilder-filter-orm' => [ 102 | 'aliases' => [ 103 | 'eq' => \ZF\Doctrine\QueryBuilder\Filter\ORM\Equals::class, 104 | ], 105 | 'factories' => [ 106 | \ZF\Doctrine\QueryBuilder\Filter\ORM\Equals::class => \Zend\ServiceManager\Factory\InvokableFactory::class, 107 | ], 108 | ], 109 | ``` 110 | 111 | Request example 112 | ```php 113 | $_GET = [ 114 | 'filter' => [ 115 | [ 116 | 'type' => 'eq', 117 | 'field' => 'name', 118 | 'value' => 'Tom', 119 | ], 120 | ], 121 | 'order-by' => [ 122 | [ 123 | 'type' => 'field', 124 | 'field' => 'startAt', 125 | 'direction' => 'desc', 126 | ], 127 | ], 128 | ]; 129 | ``` 130 | 131 | Resource example 132 | ```php 133 | $serviceLocator = $this->getApplication()->getServiceLocator(); 134 | $objectManager = $serviceLocator->get('doctrine.entitymanager.orm_default'); 135 | 136 | $filterManager = $serviceLocator->get('ZfDoctrineQueryBuilderFilterManagerOrm'); 137 | $orderByManager = $serviceLocator->get('ZfDoctrineQueryBuilderOrderByManagerOrm'); 138 | 139 | $queryBuilder = $objectManager->createQueryBuilder(); 140 | $queryBuilder->select('row') 141 | ->from($entity, 'row') 142 | ; 143 | 144 | $metadata = $objectManager->getMetadataFactory()->getMetadataFor(ENTITY_NAME); // $e->getEntity() using doctrine resource event 145 | $filterManager->filter($queryBuilder, $metadata, $_GET['filter']); 146 | $orderByManager->orderBy($queryBuilder, $metadata, $_GET['order-by']); 147 | 148 | $result = $queryBuilder->getQuery()->getResult(); 149 | ``` 150 | 151 | 152 | Filters 153 | ------- 154 | 155 | Filters are not simple key/value pairs. Filters are a key-less array of filter definitions. 156 | Each filter definition is an array and the array values vary for each filter type. 157 | 158 | Each filter definition requires at a minimum a 'type'. 159 | A type references the configuration key such as 'eq', 'neq', 'between'. 160 | 161 | Each filter definition requires at a minimum a 'field'. This is the name of a field on the target entity. 162 | 163 | Each filter definition may specify 'where' with values of either 'and', 'or'. 164 | 165 | Embedded logic such as and(x or y) is supported through AndX and OrX filter types. 166 | 167 | ### Building HTTP GET query: 168 | 169 | Javascript Example: 170 | 171 | ```javascript 172 | $(function () { 173 | $.ajax({ 174 | url: "http://localhost:8081/api/db/entity/user_data", 175 | type: "GET", 176 | data: { 177 | 'filter': [ 178 | { 179 | 'field': 'cycle', 180 | 'where': 'or', 181 | 'type': 'between', 182 | 'from': '1', 183 | 'to': '100' 184 | }, 185 | { 186 | 'field': 'cycle', 187 | 'where': 'or', 188 | 'type': 'gte', 189 | 'value': '1000' 190 | } 191 | ] 192 | }, 193 | dataType: "json" 194 | }); 195 | }); 196 | ``` 197 | 198 | 199 | Querying Relations 200 | ------------------ 201 | 202 | ### Single valued 203 | It is possible to query collections by relations - just supply the relation name as `fieldName` and 204 | identifier as `value`. 205 | 206 | Assuming we have defined 2 entities, `User` and `UserGroup`... 207 | 208 | ```php 209 | /** 210 | * @Entity 211 | */ 212 | class User { 213 | /** 214 | * @ManyToOne(targetEntity="UserGroup") 215 | * @var UserGroup 216 | */ 217 | protected $group; 218 | } 219 | ``` 220 | 221 | ```php 222 | /** 223 | * @Entity 224 | */ 225 | class UserGroup {} 226 | ``` 227 | 228 | find all users that belong to UserGroup id #1 by querying the user resource with the following filter: 229 | 230 | ```php 231 | ['type' => 'eq', 'field' => 'group', 'value' => '1'] 232 | ``` 233 | 234 | ### Collection valued 235 | To match entities A that have entity B in a collection use `ismemberof`. 236 | Assuming `User` has a ManyToMany (or OneToMany) association with `UserGroup`... 237 | 238 | ```php 239 | /** 240 | * @Entity 241 | */ 242 | class User { 243 | /** 244 | * @ManyToMany(targetEntity="UserGroup") 245 | * @var UserGroup[]|ArrayCollection 246 | */ 247 | protected $groups; 248 | } 249 | ``` 250 | find all users that belong to UserGroup id #1 by querying the user resource with the following filter: 251 | 252 | ```php 253 | ['type' => 'ismemberof', 'field' => 'groups', 'value' => '1'] 254 | ``` 255 | 256 | Format of Date Fields 257 | --------------------- 258 | 259 | When a date field is involved in a filter you may specify the format of the date using PHP date 260 | formatting options. The default date format is `Y-m-d H:i:s` If you have a date field which is 261 | just `Y-m-d`, then add the format to the filter. For complete date format options see 262 | [DateTime::createFromFormat](http://php.net/manual/en/datetime.createfromformat.php) 263 | 264 | ```php 265 | [ 266 | 'format' => 'Y-m-d', 267 | 'value' => '2014-02-04', 268 | ] 269 | ``` 270 | 271 | 272 | Joining Entities and Aliasing Queries 273 | ------------------------------------- 274 | 275 | There is an included ORM Query Type for Inner Join so for every filter type there is an optional `alias`. 276 | The default alias is 'row' and refers to the entity at the heart of the REST resource. 277 | There is not a filter to add other entities to the return data. That is, only the original target resource, 278 | by default 'row', will be returned regardless of what filters or order by are applied through this module. 279 | 280 | Inner Join is not included by default in the `zf-doctrine-querybuilder.global.php.dist`. 281 | 282 | This example joins the report field through the inner join already defined on the row entity then filters 283 | for `r.id = 2`: 284 | 285 | ```php 286 | ['type' => 'innerjoin', 'field' => 'report', 'alias' => 'r'], 287 | ['type' => 'eq', 'alias' => 'r', 'field' => 'id', 'value' => '2'] 288 | ``` 289 | 290 | You can inner join tables from an inner join using `parentAlias`: 291 | 292 | ```php 293 | ['type' => 'innerjoin', 'parentAlias' => 'r', 'field' => 'owner', 'alias' => 'o'], 294 | ``` 295 | 296 | Inner Join is commented by default in the `zf-doctrine-querybuilder.global.php.dist`. 297 | 298 | 299 | 300 | There is also an ORM Query Type for LeftJoin. This join type is commonly used to fetch an empty right side of a relationship. 301 | 302 | Left Join is commented by default in the `zf-doctrine-querybuilder.global.php.dist`. 303 | 304 | ```php 305 | ['type' => 'leftjoin', 'field' => 'report', 'alias' => 'r'], 306 | ['type' => 'isnull', 'alias' => 'r', 'field' => 'id'] 307 | ``` 308 | 309 | 310 | Included Filter Types 311 | --------------------- 312 | 313 | ### ORM and ODM 314 | 315 | Equals: 316 | 317 | ```php 318 | ['type' => 'eq', 'field' => 'fieldName', 'value' => 'matchValue'] 319 | ``` 320 | 321 | Not Equals: 322 | 323 | ```php 324 | ['type' => 'neq', 'field' => 'fieldName', 'value' => 'matchValue'] 325 | ``` 326 | 327 | Less Than: 328 | 329 | ```php 330 | ['type' => 'lt', 'field' => 'fieldName', 'value' => 'matchValue'] 331 | ``` 332 | 333 | Less Than or Equals: 334 | 335 | ```php 336 | ['type' => 'lte', 'field' => 'fieldName', 'value' => 'matchValue'] 337 | ``` 338 | 339 | Greater Than: 340 | 341 | ```php 342 | ['type' => 'gt', 'field' => 'fieldName', 'value' => 'matchValue'] 343 | ``` 344 | 345 | Greater Than or Equals: 346 | 347 | ```php 348 | ['type' => 'gte', 'field' => 'fieldName', 'value' => 'matchValue'] 349 | ``` 350 | 351 | Is Null: 352 | 353 | ```php 354 | ['type' => 'isnull', 'field' => 'fieldName'] 355 | ``` 356 | 357 | Is Not Null: 358 | 359 | ```php 360 | ['type' => 'isnotnull', 'field' => 'fieldName'] 361 | ``` 362 | 363 | Note: Dates in the In and NotIn filters are not handled as dates. 364 | It is recommended you use multiple Equals statements instead of these filters for date datatypes. 365 | 366 | In: 367 | 368 | ```php 369 | ['type' => 'in', 'field' => 'fieldName', 'values' => [1, 2, 3]] 370 | ``` 371 | 372 | NotIn: 373 | 374 | ```php 375 | ['type' => 'notin', 'field' => 'fieldName', 'values' => [1, 2, 3]] 376 | ``` 377 | 378 | Between: 379 | 380 | ```php 381 | ['type' => 'between', 'field' => 'fieldName', 'from' => 'startValue', 'to' => 'endValue'] 382 | ``` 383 | 384 | Like (`%` is used as a wildcard): 385 | 386 | ```php 387 | ['type' => 'like', 'field' => 'fieldName', 'value' => 'like%search'] 388 | ``` 389 | 390 | ### ORM Only 391 | 392 | Is Member Of: 393 | 394 | ```php 395 | ['type' => 'ismemberof', 'field' => 'fieldName', 'value' => 1] 396 | ``` 397 | 398 | AndX: 399 | 400 | In AndX queries, the `conditions` is an array of filter types for any of those described 401 | here. The join will always be `and` so the `where` parameter inside of conditions is 402 | ignored. The `where` parameter on the AndX filter type is not ignored. 403 | 404 | ```php 405 | [ 406 | 'type' => 'andx', 407 | 'conditions' => [ 408 | ['field' =>'name', 'type'=>'eq', 'value' => 'ArtistOne'], 409 | ['field' =>'name', 'type'=>'eq', 'value' => 'ArtistTwo'], 410 | ], 411 | 'where' => 'and', 412 | ] 413 | ``` 414 | 415 | OrX: 416 | 417 | In OrX queries, the `conditions` is an array of filter types for any of those described 418 | here. The join will always be `or` so the `where` parameter inside of conditions is 419 | ignored. The `where` parameter on the OrX filter type is not ignored. 420 | 421 | ```php 422 | [ 423 | 'type' => 'orx', 424 | 'conditions' => [ 425 | ['field' =>'name', 'type'=>'eq', 'value' => 'ArtistOne'], 426 | ['field' =>'name', 'type'=>'eq', 'value' => 'ArtistTwo'], 427 | ], 428 | 'where' => 'and', 429 | ] 430 | ``` 431 | 432 | ### ODM Only 433 | 434 | Regex: 435 | 436 | ```php 437 | ['type' => 'regex', 'field' => 'fieldName', 'value' => '/.*search.*/i'] 438 | ``` 439 | 440 | 441 | Included Order By Type 442 | ---------------------- 443 | 444 | Field: 445 | 446 | ```php 447 | ['type' => 'field', 'field' => 'fieldName', 'direction' => 'desc'] 448 | ``` 449 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zfcampus/zf-doctrine-querybuilder", 3 | "description": "Apigility Doctrine QueryBuilder module", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "api", 7 | "apigility", 8 | "doctrine", 9 | "filter", 10 | "zendframework", 11 | "zf" 12 | ], 13 | "homepage": "https://apigility.org", 14 | "support": { 15 | "issues": "https://github.com/zfcampus/zf-doctrine-querybuilder/issues", 16 | "source": "https://github.com/zfcampus/zf-doctrine-querybuilder", 17 | "rss": "https://github.com/zfcampus/zf-doctrine-querybuilder/releases.atom", 18 | "slack": "https://zendframework-slack.herokuapp.com", 19 | "forum": "https://discourse.zendframework.com/c/questions/apigility" 20 | }, 21 | "config": { 22 | "sort-packages": true 23 | }, 24 | "extra": { 25 | "branch-alias": { 26 | "dev-master": "1.8.x-dev", 27 | "dev-develop": "1.9.x-dev" 28 | }, 29 | "zf": { 30 | "module": [ 31 | "ZF\\Doctrine\\QueryBuilder" 32 | ] 33 | } 34 | }, 35 | "require": { 36 | "php": "^5.6 || ^7.0", 37 | "doctrine/doctrine-module": "^1.2 || ^2.1.8", 38 | "zendframework/zend-hydrator": "^1.1 || ^2.2.1 || ^3.0", 39 | "zendframework/zend-modulemanager": "^2.7.2", 40 | "zendframework/zend-mvc": "^2.7.13 || ^3.0.2", 41 | "zendframework/zend-servicemanager": "^2.7.6 || ^3.1.1", 42 | "zfcampus/zf-api-problem": "^1.2.2", 43 | "zfcampus/zf-apigility-doctrine": "^2.1", 44 | "zfcampus/zf-hal": "^1.4.2" 45 | }, 46 | "require-dev": { 47 | "doctrine/doctrine-mongo-odm-module": "^0.11 || ^1.0", 48 | "doctrine/doctrine-orm-module": "^1.1 || ^2.1.3", 49 | "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.3", 50 | "symfony/yaml": "^2.3 || ^3.0 || ^4.0", 51 | "zendframework/zend-coding-standard": "~1.0.0", 52 | "zendframework/zend-i18n": "^2.7.3", 53 | "zendframework/zend-log": "^2.9.1", 54 | "zendframework/zend-serializer": "^2.8", 55 | "zendframework/zend-test": "^2.6.1 || ^3.0.1", 56 | "zfcampus/zf-apigility-provider": "^1.2" 57 | }, 58 | "suggest": { 59 | "ext/mongo": "Mongo extension, if using ODM", 60 | "doctrine/doctrine-mongo-odm-module": "^1.0, if you wish to use the Doctrine ODM", 61 | "doctrine/doctrine-orm-module": "^1.1 || ^2.1.3, if you wish to use the Doctrine ORM" 62 | }, 63 | "autoload": { 64 | "files": [ 65 | "src/_autoload.php" 66 | ], 67 | "psr-4": { 68 | "ZF\\Doctrine\\QueryBuilder\\": "src/" 69 | } 70 | }, 71 | "autoload-dev": { 72 | "psr-4": { 73 | "ZFTest\\Doctrine\\QueryBuilder\\": "test/" 74 | } 75 | }, 76 | "scripts": { 77 | "check": [ 78 | "@cs-check", 79 | "@test" 80 | ], 81 | "cs-check": "phpcs", 82 | "cs-fix": "phpcbf", 83 | "test": "phpunit --colors=always", 84 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'aliases' => [ 12 | 'ZfDoctrineQueryBuilderFilterManagerOrm' => Filter\Service\ORMFilterManager::class, 13 | 'ZfDoctrineQueryBuilderFilterManagerOdm' => Filter\Service\ODMFilterManager::class, 14 | 'ZfDoctrineQueryBuilderOrderByManagerOrm' => OrderBy\Service\ORMOrderByManager::class, 15 | 'ZfDoctrineQueryBuilderOrderByManagerOdm' => OrderBy\Service\ODMOrderByManager::class, 16 | ], 17 | 'factories' => [ 18 | Filter\Service\ORMFilterManager::class => Filter\Service\ORMFilterManagerFactory::class, 19 | Filter\Service\ODMFilterManager::class => Filter\Service\ODMFilterManagerFactory::class, 20 | OrderBy\Service\ORMOrderByManager::class => OrderBy\Service\ORMOrderByManagerFactory::class, 21 | OrderBy\Service\ODMOrderByManager::class => OrderBy\Service\ODMOrderByManagerFactory::class, 22 | ], 23 | ], 24 | ]; 25 | -------------------------------------------------------------------------------- /config/zf-doctrine-querybuilder.global.php.dist: -------------------------------------------------------------------------------- 1 | [ 13 | // 'aliases' => [ 14 | // 'default_orm' => Query\Provider\DefaultOrm::class, 15 | // 'default_odm' => Query\Provider\DefaultOdm::class, 16 | // ], 17 | // 'factories' => [ 18 | // Query\Provider\DefaultOrm::class => Query\Provider\DefaultOrmFactory::class, 19 | // Query\Provider\DefaultOdm::class => Query\Provider\DefaultOdmFactory::class, 20 | // ], 21 | // ], 22 | 23 | 'zf-doctrine-querybuilder-options' => [ 24 | 'filter_key' => 'filter', 25 | 'order_by_key' => 'order-by', 26 | ], 27 | 'zf-doctrine-querybuilder-orderby-orm' => [ 28 | 'aliases' => [ 29 | 'field' => OrderBy\ORM\Field::class, 30 | ], 31 | 'factories' => [ 32 | OrderBy\ORM\Field::class => InvokableFactory::class, 33 | ], 34 | ], 35 | 'zf-doctrine-querybuilder-orderby-odm' => [ 36 | 'aliases' => [ 37 | 'field' => OrderBy\ODM\Field::class, 38 | ], 39 | 'factories' => [ 40 | OrderBy\ODM\Field::class => InvokableFactory::class, 41 | ], 42 | ], 43 | 'zf-doctrine-querybuilder-filter-orm' => [ 44 | 'aliases' => [ 45 | 'eq' => Filter\ORM\Equals::class, 46 | 'neq' => Filter\ORM\NotEquals::class, 47 | 'lt' => Filter\ORM\LessThan::class, 48 | 'lte' => Filter\ORM\LessThanOrEquals::class, 49 | 'gt' => Filter\ORM\GreaterThan::class, 50 | 'gte' => Filter\ORM\GreaterThanOrEquals::class, 51 | 'isnull' => Filter\ORM\IsNull::class, 52 | 'isnotnull' => Filter\ORM\IsNotNull::class, 53 | 'in' => Filter\ORM\In::class, 54 | 'notin' => Filter\ORM\NotIn::class, 55 | 'between' => Filter\ORM\Between::class, 56 | 'like' => Filter\ORM\Like::class, 57 | 'notlike' => Filter\ORM\NotLike::class, 58 | 'ismemberof' => Filter\ORM\IsMemberOf::class, 59 | 'orx' => Filter\ORM\OrX::class, 60 | 'andx' => Filter\ORM\AndX::class, 61 | // 'innerjoin' => Filter\ORM\InnerJoin::class, 62 | // 'leftjoin' => Filter\ORM\LeftJoin::class, 63 | ], 64 | 'factories' => [ 65 | Filter\ORM\Equals::class => InvokableFactory::class, 66 | Filter\ORM\NotEquals::class => InvokableFactory::class, 67 | Filter\ORM\LessThan::class => InvokableFactory::class, 68 | Filter\ORM\LessThanOrEquals::class => InvokableFactory::class, 69 | Filter\ORM\GreaterThan::class => InvokableFactory::class, 70 | Filter\ORM\GreaterThanOrEquals::class => InvokableFactory::class, 71 | Filter\ORM\IsNull::class => InvokableFactory::class, 72 | Filter\ORM\IsNotNull::class => InvokableFactory::class, 73 | Filter\ORM\In::class => InvokableFactory::class, 74 | Filter\ORM\NotIn::class => InvokableFactory::class, 75 | Filter\ORM\Between::class => InvokableFactory::class, 76 | Filter\ORM\Like::class => InvokableFactory::class, 77 | Filter\ORM\NotLike::class => InvokableFactory::class, 78 | Filter\ORM\IsMemberOf::class => InvokableFactory::class, 79 | Filter\ORM\OrX::class => InvokableFactory::class, 80 | Filter\ORM\AndX::class => InvokableFactory::class, 81 | // Filter\ORM\InnerJoin::class => InvokableFactory::class, 82 | // Filter\ORM\LeftJoin::class => InvokableFactory::class, 83 | ], 84 | ], 85 | 'zf-doctrine-querybuilder-filter-odm' => [ 86 | 'aliases' => [ 87 | 'eq' => Filter\ODM\Equals::class, 88 | 'neq' => Filter\ODM\NotEquals::class, 89 | 'lt' => Filter\ODM\LessThan::class, 90 | 'lte' => Filter\ODM\LessThanOrEquals::class, 91 | 'gt' => Filter\ODM\GreaterThan::class, 92 | 'gte' => Filter\ODM\GreaterThanOrEquals::class, 93 | 'isnull' => Filter\ODM\IsNull::class, 94 | 'isnotnull' => Filter\ODM\IsNotNull::class, 95 | 'in' => Filter\ODM\In::class, 96 | 'notin' => Filter\ODM\NotIn::class, 97 | 'between' => Filter\ODM\Between::class, 98 | 'like' => Filter\ODM\Like::class, 99 | 'regex' => Filter\ODM\Regex::class, 100 | ], 101 | 'factories' => [ 102 | Filter\ODM\Equals::class => InvokableFactory::class, 103 | Filter\ODM\NotEquals::class => InvokableFactory::class, 104 | Filter\ODM\LessThan::class => InvokableFactory::class, 105 | Filter\ODM\LessThanOrEquals::class => InvokableFactory::class, 106 | Filter\ODM\GreaterThan::class => InvokableFactory::class, 107 | Filter\ODM\GreaterThanOrEquals::class => InvokableFactory::class, 108 | Filter\ODM\IsNull::class => InvokableFactory::class, 109 | Filter\ODM\IsNotNull::class => InvokableFactory::class, 110 | Filter\ODM\In::class => InvokableFactory::class, 111 | Filter\ODM\NotIn::class => InvokableFactory::class, 112 | Filter\ODM\Between::class => InvokableFactory::class, 113 | Filter\ODM\Like::class => InvokableFactory::class, 114 | Filter\ODM\Regex::class => InvokableFactory::class, 115 | ], 116 | ], 117 | ]; 118 | -------------------------------------------------------------------------------- /src/Filter/FilterInterface.php: -------------------------------------------------------------------------------- 1 | fieldMappings[$field])) { 19 | return $value; 20 | } 21 | 22 | switch ($metadata->fieldMappings[$field]['type']) { 23 | case 'int': 24 | settype($value, 'integer'); 25 | break; 26 | case 'boolean': 27 | settype($value, 'boolean'); 28 | break; 29 | case 'float': 30 | settype($value, 'float'); 31 | break; 32 | case 'string': 33 | settype($value, 'string'); 34 | break; 35 | case 'bin_data_custom': 36 | break; 37 | case 'bin_data_func': 38 | break; 39 | case 'bin_data_md5': 40 | break; 41 | case 'bin_data': 42 | break; 43 | case 'bin_data_uuid': 44 | break; 45 | case 'collection': 46 | break; 47 | case 'custom_id': 48 | break; 49 | case 'date': 50 | if ($value && ! $doNotTypecastDatetime) { 51 | if (! $format) { 52 | $format = 'Y-m-d H:i:s'; 53 | } 54 | $value = DateTime::createFromFormat($format, $value); 55 | } 56 | break; 57 | case 'file': 58 | break; 59 | case 'hash': 60 | break; 61 | case 'id': 62 | break; 63 | case 'increment': 64 | break; 65 | case 'key': 66 | break; 67 | case 'object_id': 68 | break; 69 | case 'raw_type': 70 | break; 71 | case 'timestamp': 72 | break; 73 | default: 74 | break; 75 | } 76 | 77 | return $value; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Filter/ODM/Between.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['from'], $format); 26 | $to = $this->typeCastField($metadata, $option['field'], $option['to'], $format); 27 | 28 | $queryBuilder->$queryType($queryBuilder->expr()->field($option['field'])->range($from, $to)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Filter/ODM/Equals.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 26 | 27 | $queryBuilder->$queryType($queryBuilder->expr()->field($option['field'])->equals($value)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Filter/ODM/GreaterThan.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 26 | 27 | $queryBuilder->$queryType($queryBuilder->expr()->field($option['field'])->gt($value)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Filter/ODM/GreaterThanOrEquals.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 26 | 27 | $queryBuilder->$queryType($queryBuilder->expr()->field($option['field'])->gte($value)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Filter/ODM/In.php: -------------------------------------------------------------------------------- 1 | typeCastField( 27 | $metadata, 28 | $option['field'], 29 | $value, 30 | $format, 31 | $doNotTypecastDatetime = true 32 | ); 33 | } 34 | 35 | $queryBuilder->$queryType($queryBuilder->expr()->field($option['field'])->in($queryValues)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Filter/ODM/IsNotNull.php: -------------------------------------------------------------------------------- 1 | $queryType($queryBuilder->expr()->field($option['field'])->notEqual(null)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Filter/ODM/IsNull.php: -------------------------------------------------------------------------------- 1 | $queryType($queryBuilder->expr()->field($option['field'])->equals(null)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Filter/ODM/LessThan.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 25 | 26 | $queryBuilder->$queryType($queryBuilder->expr()->field($option['field'])->lt($value)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Filter/ODM/LessThanOrEquals.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 25 | 26 | $queryBuilder->$queryType($queryBuilder->expr()->field($option['field'])->lte($value)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Filter/ODM/Like.php: -------------------------------------------------------------------------------- 1 | $queryType( 27 | $queryBuilder 28 | ->expr() 29 | ->field($option['field']) 30 | ->equals(new MongoRegex($regex)) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Filter/ODM/NotEquals.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 25 | 26 | $queryBuilder->$queryType($queryBuilder->expr()->field($option['field'])->notEqual($value)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Filter/ODM/NotIn.php: -------------------------------------------------------------------------------- 1 | typeCastField( 27 | $metadata, 28 | $option['field'], 29 | $value, 30 | $format, 31 | $doNotTypecastDatetime = true 32 | ); 33 | } 34 | 35 | $queryBuilder->$queryType($queryBuilder->expr()->field($option['field'])->notIn($queryValues)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Filter/ODM/Regex.php: -------------------------------------------------------------------------------- 1 | $queryType( 25 | $queryBuilder 26 | ->expr() 27 | ->field($option['field']) 28 | ->equals(new MongoRegex($option['value'])) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Filter/ORM/AbstractFilter.php: -------------------------------------------------------------------------------- 1 | setFilterManager($params[0]); 22 | } 23 | 24 | public function setFilterManager(ORMFilterManager $filterManager) 25 | { 26 | $this->filterManager = $filterManager; 27 | return $this; 28 | } 29 | 30 | public function getFilterManager() 31 | { 32 | return $this->filterManager; 33 | } 34 | 35 | protected function typeCastField($metadata, $field, $value, $format, $doNotTypecastDatetime = false) 36 | { 37 | if (! isset($metadata->fieldMappings[$field])) { 38 | return $value; 39 | } 40 | 41 | switch ($metadata->fieldMappings[$field]['type']) { 42 | case 'string': 43 | settype($value, 'string'); 44 | break; 45 | case 'integer': 46 | case 'smallint': 47 | #case 'bigint': // Don't try to manipulate bigints? 48 | settype($value, 'integer'); 49 | break; 50 | case 'boolean': 51 | settype($value, 'boolean'); 52 | break; 53 | case 'decimal': 54 | case 'float': 55 | settype($value, 'float'); 56 | break; 57 | case 'date': 58 | // For dates set time to midnight 59 | if ($value && ! $doNotTypecastDatetime) { 60 | if (! $format) { 61 | $format = 'Y-m-d'; 62 | } 63 | $value = DateTime::createFromFormat($format, $value); 64 | $value = DateTime::createFromFormat('Y-m-d H:i:s', $value->format('Y-m-d') . ' 00:00:00'); 65 | } 66 | break; 67 | case 'time': 68 | if ($value && ! $doNotTypecastDatetime) { 69 | if (! $format) { 70 | $format = 'H:i:s'; 71 | } 72 | $value = DateTime::createFromFormat($format, $value); 73 | } 74 | break; 75 | case 'datetime': 76 | if ($value && ! $doNotTypecastDatetime) { 77 | if (! $format) { 78 | $format = 'Y-m-d H:i:s'; 79 | } 80 | $value = DateTime::createFromFormat($format, $value); 81 | } 82 | break; 83 | default: 84 | break; 85 | } 86 | 87 | return $value; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Filter/ORM/AndX.php: -------------------------------------------------------------------------------- 1 | expr()->andX(); 28 | $em = $queryBuilder->getEntityManager(); 29 | $qb = $em->createQueryBuilder(); 30 | 31 | foreach ($option['conditions'] as $condition) { 32 | $filter = $this->getFilterManager()->get( 33 | strtolower($condition['type']), 34 | [$this->getFilterManager()] 35 | ); 36 | $filter->filter($qb, $metadata, $condition); 37 | } 38 | 39 | $dqlParts = $qb->getDqlParts(); 40 | $andX->addMultiple($dqlParts['where']->getParts()); 41 | $queryBuilder->setParameters( 42 | new ArrayCollection(array_merge_recursive( 43 | $queryBuilder->getParameters()->toArray(), 44 | $qb->getParameters()->toArray() 45 | )) 46 | ); 47 | 48 | $queryBuilder->$queryType($andX); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Filter/ORM/Between.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['from'], $format); 32 | $to = $this->typeCastField($metadata, $option['field'], $option['to'], $format); 33 | 34 | $fromParameter = uniqid('a1'); 35 | $toParameter = uniqid('a2'); 36 | 37 | $queryBuilder->$queryType( 38 | $queryBuilder 39 | ->expr() 40 | ->between( 41 | sprintf('%s.%s', $option['alias'], $option['field']), 42 | sprintf(':%s', $fromParameter), 43 | sprintf(':%s', $toParameter) 44 | ) 45 | ); 46 | $queryBuilder->setParameter($fromParameter, $from); 47 | $queryBuilder->setParameter($toParameter, $to); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Filter/ORM/Equals.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 35 | 36 | $parameter = uniqid('a'); 37 | $queryBuilder->$queryType( 38 | $queryBuilder 39 | ->expr() 40 | ->eq($option['alias'] . '.' . $option['field'], ':' . $parameter) 41 | ); 42 | $queryBuilder->setParameter($parameter, $value); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Filter/ORM/GreaterThan.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 32 | 33 | $parameter = uniqid('a'); 34 | $queryBuilder->$queryType( 35 | $queryBuilder 36 | ->expr() 37 | ->gt($option['alias'] . '.' . $option['field'], ':' . $parameter) 38 | ); 39 | $queryBuilder->setParameter($parameter, $value); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Filter/ORM/GreaterThanOrEquals.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 32 | 33 | $parameter = uniqid('a'); 34 | $queryBuilder->$queryType( 35 | $queryBuilder 36 | ->expr() 37 | ->gte($option['alias'] . '.' . $option['field'], ':' . $parameter) 38 | ); 39 | $queryBuilder->setParameter($parameter, $value); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Filter/ORM/In.php: -------------------------------------------------------------------------------- 1 | typeCastField( 34 | $metadata, 35 | $option['field'], 36 | $value, 37 | $format, 38 | $doNotTypecastDatetime = true 39 | ); 40 | } 41 | 42 | $parameter = uniqid('a'); 43 | $queryBuilder->$queryType( 44 | $queryBuilder 45 | ->expr() 46 | ->in($option['alias'] . '.' . $option['field'], ':' . $parameter) 47 | ); 48 | $queryBuilder->setParameter($parameter, $queryValues); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Filter/ORM/InnerJoin.php: -------------------------------------------------------------------------------- 1 | innerJoin( 48 | $option['parentAlias'] . '.' . $option['field'], 49 | $option['alias'], 50 | $option['conditionType'], 51 | $option['condition'], 52 | $option['indexBy'] 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Filter/ORM/IsMemberOf.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 40 | 41 | $parameter = uniqid('a'); 42 | $queryBuilder->$queryType( 43 | $queryBuilder 44 | ->expr() 45 | ->isMemberOf(':' . $parameter, $option['alias'] . '.' . $option['field']) 46 | ); 47 | $queryBuilder->setParameter($parameter, $value); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Filter/ORM/IsNotNull.php: -------------------------------------------------------------------------------- 1 | $queryType( 30 | $queryBuilder 31 | ->expr() 32 | ->isNotNull($option['alias'] . '.' . $option['field']) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Filter/ORM/IsNull.php: -------------------------------------------------------------------------------- 1 | $queryType( 30 | $queryBuilder 31 | ->expr() 32 | ->isNull($option['alias'] . '.' . $option['field']) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Filter/ORM/LeftJoin.php: -------------------------------------------------------------------------------- 1 | leftJoin( 48 | $option['parentAlias'] . '.' . $option['field'], 49 | $option['alias'], 50 | $option['conditionType'], 51 | $option['condition'], 52 | $option['indexBy'] 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Filter/ORM/LessThan.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 35 | 36 | $parameter = uniqid('a'); 37 | $queryBuilder->$queryType( 38 | $queryBuilder 39 | ->expr() 40 | ->lt($option['alias'] . '.' . $option['field'], ":$parameter") 41 | ); 42 | $queryBuilder->setParameter($parameter, $value); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Filter/ORM/LessThanOrEquals.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 32 | 33 | $parameter = uniqid('a'); 34 | $queryBuilder->$queryType( 35 | $queryBuilder 36 | ->expr() 37 | ->lte($option['alias'] . '.' . $option['field'], ':' . $parameter) 38 | ); 39 | $queryBuilder->setParameter($parameter, $value); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Filter/ORM/Like.php: -------------------------------------------------------------------------------- 1 | $queryType( 30 | $queryBuilder 31 | ->expr() 32 | ->like( 33 | $option['alias'] . '.' . $option['field'], 34 | $queryBuilder 35 | ->expr() 36 | ->literal($option['value']) 37 | ) 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Filter/ORM/NotEquals.php: -------------------------------------------------------------------------------- 1 | typeCastField($metadata, $option['field'], $option['value'], $format); 32 | 33 | $parameter = uniqid('a'); 34 | $queryBuilder->$queryType( 35 | $queryBuilder 36 | ->expr() 37 | ->neq($option['alias'] . '.' . $option['field'], ':' . $parameter) 38 | ); 39 | $queryBuilder->setParameter($parameter, $value); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Filter/ORM/NotIn.php: -------------------------------------------------------------------------------- 1 | typeCastField( 34 | $metadata, 35 | $option['field'], 36 | $value, 37 | $format, 38 | $doNotTypecastDatetime = true 39 | ); 40 | } 41 | 42 | $parameter = uniqid('a'); 43 | $queryBuilder->$queryType( 44 | $queryBuilder 45 | ->expr() 46 | ->notIn($option['alias'] . '.' . $option['field'], ':' . $parameter) 47 | ); 48 | $queryBuilder->setParameter($parameter, $queryValues); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Filter/ORM/NotLike.php: -------------------------------------------------------------------------------- 1 | $queryType( 30 | $queryBuilder 31 | ->expr() 32 | ->notlike( 33 | $option['alias'] . '.' . $option['field'], 34 | $queryBuilder->expr()->literal($option['value']) 35 | ) 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Filter/ORM/OrX.php: -------------------------------------------------------------------------------- 1 | expr()->orX(); 28 | $em = $queryBuilder->getEntityManager(); 29 | $qb = $em->createQueryBuilder(); 30 | 31 | foreach ($option['conditions'] as $condition) { 32 | $filter = $this->getFilterManager() 33 | ->get( 34 | strtolower($condition['type']), 35 | [$this->getFilterManager()] 36 | ); 37 | $filter->filter($qb, $metadata, $condition); 38 | } 39 | 40 | $dqlParts = $qb->getDqlParts(); 41 | $orX->addMultiple($dqlParts['where']->getParts()); 42 | $queryBuilder->setParameters( 43 | new ArrayCollection(array_merge_recursive( 44 | $queryBuilder->getParameters()->toArray(), 45 | $qb->getParameters()->toArray() 46 | )) 47 | ); 48 | 49 | $queryBuilder->$queryType($orX); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Filter/Service/ODMFilterManager.php: -------------------------------------------------------------------------------- 1 | get(strtolower($option['type']), [$this]); 31 | $filter->filter($queryBuilder, $metadata, $option); 32 | } 33 | } 34 | 35 | /** 36 | * Validate the plugin is of the expected type (v3). 37 | * 38 | * Validates against `$instanceOf`. 39 | * 40 | * @param mixed $instance 41 | * @return void 42 | * @throws Exception\InvalidServiceException 43 | */ 44 | public function validate($instance) 45 | { 46 | if (! $instance instanceof $this->instanceOf) { 47 | throw new Exception\InvalidServiceException(sprintf( 48 | '%s can only create instances of %s; %s is invalid', 49 | get_class($this), 50 | $this->instanceOf, 51 | is_object($instance) ? get_class($instance) : gettype($instance) 52 | )); 53 | } 54 | } 55 | 56 | /** 57 | * Validate the plugin is of the expected type (v2). 58 | * 59 | * Proxies to `validate()`. 60 | * 61 | * @param mixed $instance 62 | * @return void 63 | * @throws Exception\InvalidArgumentException 64 | */ 65 | public function validatePlugin($instance) 66 | { 67 | try { 68 | $this->validate($instance); 69 | } catch (Exception\InvalidServiceException $e) { 70 | throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Filter/Service/ODMFilterManagerFactory.php: -------------------------------------------------------------------------------- 1 | get(strtolower($option['type']), [$this]); 30 | $filter->filter($queryBuilder, $metadata, $option); 31 | } 32 | } 33 | 34 | /** 35 | * Validate the plugin is of the expected type (v3). 36 | * 37 | * Validates against `$instanceOf`. 38 | * 39 | * @param mixed $instance 40 | * @return void 41 | * @throws Exception\InvalidServiceException 42 | */ 43 | public function validate($instance) 44 | { 45 | if (! $instance instanceof $this->instanceOf) { 46 | throw new Exception\InvalidServiceException(sprintf( 47 | '%s can only create instances of %s; %s is invalid', 48 | get_class($this), 49 | $this->instanceOf, 50 | is_object($instance) ? get_class($instance) : gettype($instance) 51 | )); 52 | } 53 | } 54 | 55 | /** 56 | * Validate the plugin is of the expected type (v2). 57 | * 58 | * Proxies to `validate()`. 59 | * 60 | * @param mixed $instance 61 | * @return void 62 | * @throws Exception\InvalidArgumentException 63 | */ 64 | public function validatePlugin($instance) 65 | { 66 | try { 67 | $this->validate($instance); 68 | } catch (Exception\InvalidServiceException $e) { 69 | throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Filter/Service/ORMFilterManagerFactory.php: -------------------------------------------------------------------------------- 1 | serviceManager = $serviceManager; 30 | 31 | return $this; 32 | } 33 | 34 | public function getServiceManager() 35 | { 36 | return $this->serviceManager; 37 | } 38 | 39 | public function extract($value) 40 | { 41 | $config = $this->getServiceManager()->get('config'); 42 | if (! method_exists($value, 'getTypeClass') 43 | || ! isset($config['zf-hal']['metadata_map'][$value->getTypeClass()->name]) 44 | ) { 45 | return; 46 | } 47 | 48 | $config = $config['zf-hal']['metadata_map'][$value->getTypeClass()->name]; 49 | $mapping = $value->getMapping(); 50 | 51 | $filter = new FilterChain(); 52 | $filter->attachByName('WordCamelCaseToUnderscore') 53 | ->attachByName('StringToLower'); 54 | 55 | $link = new Link($filter($mapping['fieldName'])); 56 | $link->setRoute($config['route_name']); 57 | $link->setRouteParams(['id' => null]); 58 | 59 | if (isset($config['zf-doctrine-querybuilder-options']['filter_key'])) { 60 | $filterKey = $config['zf-doctrine-querybuilder-options']['filter_key']; 61 | } else { 62 | $filterKey = 'filter'; 63 | } 64 | 65 | $filterValue = [ 66 | 'field' => $mapping['mappedBy'] ? : $mapping['inversedBy'], 67 | 'type' => isset($mapping['joinTable']) ? 'ismemberof' : 'eq', 68 | 'value' => $value->getOwner()->getId(), 69 | ]; 70 | 71 | $link->setRouteOptions([ 72 | 'query' => [ 73 | $filterKey => [ 74 | $filterValue, 75 | ], 76 | ], 77 | ]); 78 | 79 | return $link; 80 | } 81 | 82 | public function hydrate($value) 83 | { 84 | // Hydration is not supported for collections. 85 | // A call to PATCH will use hydration to extract then hydrate 86 | // an entity. In this process a collection will be included 87 | // so no error is thrown here. 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Hydrator/Strategy/CollectionLinkHydratorV3.php: -------------------------------------------------------------------------------- 1 | serviceManager = $serviceManager; 28 | 29 | return $this; 30 | } 31 | 32 | public function getServiceManager() 33 | { 34 | return $this->serviceManager; 35 | } 36 | 37 | public function extract($value, ?object $object = null) 38 | { 39 | $config = $this->getServiceManager()->get('config'); 40 | if (! method_exists($value, 'getTypeClass') 41 | || ! isset($config['zf-hal']['metadata_map'][$value->getTypeClass()->name]) 42 | ) { 43 | return; 44 | } 45 | 46 | $config = $config['zf-hal']['metadata_map'][$value->getTypeClass()->name]; 47 | $mapping = $value->getMapping(); 48 | 49 | $filter = new FilterChain(); 50 | $filter->attachByName('WordCamelCaseToUnderscore') 51 | ->attachByName('StringToLower'); 52 | 53 | $link = new Link($filter($mapping['fieldName'])); 54 | $link->setRoute($config['route_name']); 55 | $link->setRouteParams(['id' => null]); 56 | 57 | if (isset($config['zf-doctrine-querybuilder-options']['filter_key'])) { 58 | $filterKey = $config['zf-doctrine-querybuilder-options']['filter_key']; 59 | } else { 60 | $filterKey = 'filter'; 61 | } 62 | 63 | $filterValue = [ 64 | 'field' => $mapping['mappedBy'] ? : $mapping['inversedBy'], 65 | 'type' => isset($mapping['joinTable']) ? 'ismemberof' : 'eq', 66 | 'value' => $value->getOwner()->getId(), 67 | ]; 68 | 69 | $link->setRouteOptions([ 70 | 'query' => [ 71 | $filterKey => [ 72 | $filterValue, 73 | ], 74 | ], 75 | ]); 76 | 77 | return $link; 78 | } 79 | 80 | public function hydrate($value, ?array $data = null) 81 | { 82 | // Hydration is not supported for collections. 83 | // A call to PATCH will use hydration to extract then hydrate 84 | // an entity. In this process a collection will be included 85 | // so no error is thrown here. 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Module.php: -------------------------------------------------------------------------------- 1 | getEvent()->getParam('ServiceManager'); 23 | /** @var ServiceListener $serviceListener */ 24 | $serviceListener = $serviceManager->get('ServiceListener'); 25 | 26 | $serviceListener->addServiceManager( 27 | 'ZfDoctrineQueryBuilderFilterManagerOrm', 28 | 'zf-doctrine-querybuilder-filter-orm', 29 | Filter\FilterInterface::class, 30 | 'getDoctrineQueryBuilderFilterOrmConfig' 31 | ); 32 | 33 | $serviceListener->addServiceManager( 34 | 'ZfDoctrineQueryBuilderFilterManagerOdm', 35 | 'zf-doctrine-querybuilder-filter-odm', 36 | Filter\FilterInterface::class, 37 | 'getDoctrineQueryBuilderFilterOdmConfig' 38 | ); 39 | 40 | $serviceListener->addServiceManager( 41 | 'ZfDoctrineQueryBuilderOrderByManagerOrm', 42 | 'zf-doctrine-querybuilder-orderby-orm', 43 | OrderBy\OrderByInterface::class, 44 | 'getDoctrineQueryBuilderOrderByOrmConfig' 45 | ); 46 | $serviceListener->addServiceManager( 47 | 'ZfDoctrineQueryBuilderOrderByManagerOdm', 48 | 'zf-doctrine-querybuilder-orderby-odm', 49 | OrderBy\OrderByInterface::class, 50 | 'getDoctrineQueryBuilderOrderByOdmConfig' 51 | ); 52 | } 53 | 54 | /** 55 | * Expected to return an array of modules on which the current one depends on 56 | * 57 | * @return array 58 | */ 59 | public function getModuleDependencies() 60 | { 61 | return ['DoctrineModule']; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/OrderBy/ODM/AbstractOrderBy.php: -------------------------------------------------------------------------------- 1 | setOrderByManager($params[0]); 21 | } 22 | 23 | public function setOrderByManager(ODMOrderByManager $orderByManager) 24 | { 25 | $this->orderByManager = $orderByManager; 26 | 27 | return $this; 28 | } 29 | 30 | public function getOrderByManager() 31 | { 32 | return $this->orderByManager; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/OrderBy/ODM/Field.php: -------------------------------------------------------------------------------- 1 | sort($option['field'], $option['direction']); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/OrderBy/ORM/AbstractOrderBy.php: -------------------------------------------------------------------------------- 1 | setOrderByManager($params[0]); 21 | } 22 | 23 | public function setOrderByManager(ORMOrderByManager $orderByManager) 24 | { 25 | $this->orderByManager = $orderByManager; 26 | 27 | return $this; 28 | } 29 | 30 | public function getOrderByManager() 31 | { 32 | return $this->orderByManager; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/OrderBy/ORM/Field.php: -------------------------------------------------------------------------------- 1 | addOrderBy($option['alias'] . '.' . $option['field'], $option['direction']); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/OrderBy/OrderByInterface.php: -------------------------------------------------------------------------------- 1 | get(strtolower($option['type']), [$this]); 30 | $orderByHandler->orderBy($queryBuilder, $metadata, $option); 31 | } 32 | } 33 | 34 | /** 35 | * Validate the plugin is of the expected type (v3). 36 | * 37 | * Validates against `$instanceOf`. 38 | * 39 | * @param mixed $instance 40 | * @return void 41 | * @throws Exception\InvalidServiceException 42 | */ 43 | public function validate($instance) 44 | { 45 | if (! $instance instanceof $this->instanceOf) { 46 | throw new Exception\InvalidServiceException(sprintf( 47 | '%s can only create instances of %s; %s is invalid', 48 | get_class($this), 49 | $this->instanceOf, 50 | is_object($instance) ? get_class($instance) : gettype($instance) 51 | )); 52 | } 53 | } 54 | 55 | /** 56 | * Validate the plugin is of the expected type (v2). 57 | * 58 | * Proxies to `validate()`. 59 | * 60 | * @param mixed $instance 61 | * @return void 62 | * @throws Exception\InvalidArgumentException 63 | */ 64 | public function validatePlugin($instance) 65 | { 66 | try { 67 | $this->validate($instance); 68 | } catch (Exception\InvalidServiceException $e) { 69 | throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/OrderBy/Service/ODMOrderByManagerFactory.php: -------------------------------------------------------------------------------- 1 | get(strtolower($option['type']), [$this]); 30 | $orderByHandler->orderBy($queryBuilder, $metadata, $option); 31 | } 32 | } 33 | 34 | /** 35 | * Validate the plugin is of the expected type (v3). 36 | * 37 | * Validates against `$instanceOf`. 38 | * 39 | * @param mixed $instance 40 | * @return void 41 | * @throws Exception\InvalidServiceException 42 | */ 43 | public function validate($instance) 44 | { 45 | if (! $instance instanceof $this->instanceOf) { 46 | throw new Exception\InvalidServiceException(sprintf( 47 | '%s can only create instances of %s; %s is invalid', 48 | get_class($this), 49 | $this->instanceOf, 50 | is_object($instance) ? get_class($instance) : gettype($instance) 51 | )); 52 | } 53 | } 54 | 55 | /** 56 | * Validate the plugin is of the expected type (v2). 57 | * 58 | * Proxies to `validate()`. 59 | * 60 | * @param mixed $instance 61 | * @return void 62 | * @throws Exception\InvalidArgumentException 63 | */ 64 | public function validatePlugin($instance) 65 | { 66 | try { 67 | $this->validate($instance); 68 | } catch (Exception\InvalidServiceException $e) { 69 | throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/OrderBy/Service/ORMOrderByManagerFactory.php: -------------------------------------------------------------------------------- 1 | serviceLocator = $serviceLocator; 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * Get service locator 39 | * 40 | * @return ServiceLocatorInterface 41 | */ 42 | public function getServiceLocator() 43 | { 44 | return $this->serviceLocator; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | public function createQuery(ResourceEvent $event, $entityClass, $parameters) 51 | { 52 | $request = $event->getRequest()->getQuery()->toArray(); 53 | 54 | $queryBuilder = $this->getObjectManager()->createQueryBuilder(); 55 | $queryBuilder->find($entityClass); 56 | 57 | if (isset($request[$this->getFilterKey()])) { 58 | $metadata = $this->getObjectManager()->getMetadataFactory()->getMetadataFor($entityClass); 59 | $this->getFilterManager()->filter( 60 | $queryBuilder, 61 | $metadata, 62 | $request[$this->getFilterKey()] 63 | ); 64 | } 65 | 66 | if (isset($request[$this->getOrderByKey()])) { 67 | $metadata = $this->getObjectManager()->getMetadataFactory()->getMetadataFor($entityClass); 68 | $this->getOrderByManager()->orderBy( 69 | $queryBuilder, 70 | $metadata, 71 | $request[$this->getOrderByKey()] 72 | ); 73 | } 74 | 75 | return $queryBuilder; 76 | } 77 | 78 | /** 79 | * @param $queryBuilder 80 | * @return DoctrineOdmAdapter 81 | */ 82 | public function getPaginatedQuery($queryBuilder) 83 | { 84 | $adapter = new DoctrineOdmAdapter($queryBuilder); 85 | 86 | return $adapter; 87 | } 88 | 89 | /** 90 | * @param $entityClass 91 | * @return int 92 | */ 93 | public function getCollectionTotal($entityClass) 94 | { 95 | $queryBuilder = $this->getObjectManager()->createQueryBuilder(); 96 | $queryBuilder->find($entityClass); 97 | $count = $queryBuilder->getQuery()->execute()->count(); 98 | 99 | return $count; 100 | } 101 | 102 | /** 103 | * @return string 104 | */ 105 | protected function getFilterKey() 106 | { 107 | $config = $this->getConfig(); 108 | if (isset($config['filter_key'])) { 109 | return $config['filter_key']; 110 | } 111 | 112 | return 'filter'; 113 | } 114 | 115 | /** 116 | * @return string 117 | */ 118 | protected function getOrderByKey() 119 | { 120 | $config = $this->getConfig(); 121 | if (isset($config['order_by_key'])) { 122 | return $config['order_by_key']; 123 | } 124 | 125 | return 'order-by'; 126 | } 127 | 128 | /** 129 | * @return array 130 | */ 131 | protected function getConfig() 132 | { 133 | $config = $this->getServiceLocator()->get('config'); 134 | if (isset($config['zf-doctrine-querybuilder-options'])) { 135 | return $config['zf-doctrine-querybuilder-options']; 136 | } 137 | 138 | return []; 139 | } 140 | 141 | /** 142 | * @return ODMFilterManager 143 | */ 144 | protected function getFilterManager() 145 | { 146 | return $this->getServiceLocator()->get(ODMFilterManager::class); 147 | } 148 | 149 | /** 150 | * @return ODMOrderByManager 151 | */ 152 | protected function getOrderByManager() 153 | { 154 | return $this->getServiceLocator()->get(ODMOrderByManager::class); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Query/Provider/DefaultOdmFactory.php: -------------------------------------------------------------------------------- 1 | getServiceLocator() ?: $container; 24 | } 25 | 26 | $provider = new DefaultOdm(); 27 | $provider->setServiceLocator($container); 28 | 29 | return $provider; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Query/Provider/DefaultOrm.php: -------------------------------------------------------------------------------- 1 | serviceLocator = $serviceLocator; 32 | 33 | return $this; 34 | } 35 | 36 | /** 37 | * Get service locator 38 | * 39 | * @return ServiceLocatorInterface 40 | */ 41 | public function getServiceLocator() 42 | { 43 | return $this->serviceLocator; 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | public function createQuery(ResourceEvent $event, $entityClass, $parameters) 50 | { 51 | $request = $event->getRequest()->getQuery()->toArray(); 52 | $queryBuilder = $this->getObjectManager()->createQueryBuilder(); 53 | $queryBuilder->select('row') 54 | ->from($entityClass, 'row'); 55 | 56 | if (isset($request[$this->getFilterKey()])) { 57 | $metadata = $this->getObjectManager()->getClassMetadata($entityClass); 58 | $this->getFilterManager()->filter( 59 | $queryBuilder, 60 | $metadata, 61 | $request[$this->getFilterKey()] 62 | ); 63 | } 64 | 65 | if (isset($request[$this->getOrderByKey()])) { 66 | $metadata = $this->getObjectManager()->getClassMetadata($entityClass); 67 | $this->getOrderByManager()->orderBy( 68 | $queryBuilder, 69 | $metadata, 70 | $request[$this->getOrderByKey()] 71 | ); 72 | } 73 | 74 | return $queryBuilder; 75 | } 76 | 77 | /** 78 | * @return string 79 | */ 80 | protected function getFilterKey() 81 | { 82 | $config = $this->getConfig(); 83 | if (isset($config['filter_key'])) { 84 | return $config['filter_key']; 85 | } 86 | 87 | return 'filter'; 88 | } 89 | 90 | /** 91 | * @return string 92 | */ 93 | protected function getOrderByKey() 94 | { 95 | $config = $this->getConfig(); 96 | if (isset($config['order_by_key'])) { 97 | return $config['order_by_key']; 98 | } 99 | 100 | return 'order-by'; 101 | } 102 | 103 | /** 104 | * @return array 105 | */ 106 | protected function getConfig() 107 | { 108 | $config = $this->getServiceLocator()->get('config'); 109 | if (isset($config['zf-doctrine-querybuilder-options'])) { 110 | return $config['zf-doctrine-querybuilder-options']; 111 | } 112 | 113 | return []; 114 | } 115 | 116 | /** 117 | * @return ORMFilterManager 118 | */ 119 | protected function getFilterManager() 120 | { 121 | return $this->getServiceLocator()->get(ORMFilterManager::class); 122 | } 123 | 124 | /** 125 | * @return ORMOrderByManager 126 | */ 127 | protected function getOrderByManager() 128 | { 129 | return $this->getServiceLocator()->get(ORMOrderByManager::class); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Query/Provider/DefaultOrmFactory.php: -------------------------------------------------------------------------------- 1 | getServiceLocator() ?: $container; 24 | } 25 | 26 | $provider = new DefaultOrm(); 27 | $provider->setServiceLocator($container); 28 | 29 | return $provider; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/_autoload.php: -------------------------------------------------------------------------------- 1 |