├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .scrutinizer.yml ├── CHANGELOG-2.0.md ├── CHANGELOG-2.1.md ├── CHANGELOG-2.2.md ├── CHANGELOG-2.3.md ├── CHANGELOG-2.4.md ├── CHANGELOG-2.5.md ├── LICENSE ├── Makefile ├── README.md ├── UPGRADE-2.1.md ├── UPGRADE-2.2.md ├── UPGRADE-2.3.md ├── UPGRADE-2.4.md ├── agile.sh ├── composer.json ├── composer.lock ├── coverage.sh ├── doc └── additional-filters.md ├── phpunit.xml ├── runtests.sh ├── src └── Mado │ └── QueryBundle │ ├── Component │ └── Meta │ │ ├── DataMapper.php │ │ ├── Dijkstra.php │ │ ├── DijkstraWalker.php │ │ ├── Exceptions │ │ └── UnInitializedQueryBuilderException.php │ │ ├── GraphWalker.php │ │ └── MapBuilder.php │ ├── DependencyInjection │ ├── Configuration.php │ └── MadoQueryExtension.php │ ├── Dictionary.php │ ├── Exceptions │ ├── ForbiddenContentException.php │ ├── InvalidFiltersException.php │ ├── MissingFieldsException.php │ └── MissingFiltersException.php │ ├── Interfaces │ ├── AdditionalFilterable.php │ └── CustomFilter.php │ ├── MadoQueryBundle.php │ ├── Objects │ ├── Filter.php │ ├── HalResponse.php │ ├── MetaDataAdapter.php │ └── PagerfantaBuilder.php │ ├── Queries │ ├── AbstractQuery.php │ ├── AndFilter.php │ ├── Join.php │ ├── Objects │ │ ├── FilterObject.php │ │ └── Value.php │ ├── Options │ │ └── QueryOptionsBuilder.php │ ├── OrFilter.php │ ├── QueryBuilderFactory.php │ └── QueryBuilderOptions.php │ ├── Repositories │ └── BaseRepository.php │ ├── Resources │ └── config │ │ └── services.yml │ └── Services │ ├── AdditionalFilterExtractor.php │ ├── Pager.php │ ├── Router.php │ └── StringParser.php └── tests └── Mado └── QueryBundle ├── Component └── Meta │ ├── DijkstraTest.php │ ├── DijkstraWalkerTest.php │ └── MapBuilderTest.php ├── Objects ├── FilterTest.php ├── HalResponseTest.php ├── MetaDataAdapterTest.php ├── PagerfantaBuilderTest.php ├── QueryBuilderFactoryTest.php ├── QueryBuilderOptionsTest.php └── ValueTest.php ├── Queries ├── Objects │ ├── FilterObjectTest.php │ └── QueryOptionsBuilderTest.php └── QueryBuilderOptionsTest.php └── Services ├── AdditionalFilterExtractorTest.php ├── PagerTest.php ├── RouterTest.php └── StringParserTest.php /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Changelog 5 | --------- 6 | 7 | * every time new feature is added, new line on CHANGELOG file must be present 8 | in the form 9 | 10 | > feature - feature description 11 | 12 | Branch names 13 | ------------ 14 | 15 | The following is just a suggestion and it is not mandatory to follow as naming 16 | convention. According to what the branch represents it can start with the 17 | following prefix: 18 | 19 | - feature/ 20 | - refactoring/ 21 | - enhancement/ 22 | - fix/ 23 | 24 | Choose the right branch 25 | ----------------------- 26 | 27 | Before open your pull request, you must determine on which branch you need to 28 | work. 29 | 30 | * if it contains a fix, refactoring or simply some code improvements must be 31 | opened against latest minor release branch. If latest stable version is 32 | `v2.2.3`, pull request must be opened starting from branch 2.2; 33 | 34 | * once new branch is merged to version 2.2 a new tag can should be created 35 | in this branch. 36 | 37 | * every time new version is released, that version must be merged to the 38 | upper minor branch (if exists) until master branch. This allow to keep all 39 | version fixed and also the next one; 40 | 41 | * if it contains new features must be opened against master branch; 42 | 43 | 44 | Coding Standards 45 | ---------------- 46 | 47 | * every time new class or method is added, `@since` annotation should be 48 | present. Just mark the minor version. `/** @since version x.y */`; 49 | 50 | * always use `yoda` condition; 51 | 52 | * respect `PSR-2` coding standards; 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | | Q | A 2 | | -------------------- | ----- 3 | | Bug report? | yes/no 4 | | Feature request? | yes/no 5 | | QueryBundle version | x.y 6 | 7 | --- 8 | 9 | 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | | Q | A 2 | | ------------- | --- 3 | | Branch? | master 4 | | Minor version? | (x.y) 5 | | Hotfix? | yes 6 | | Hotfix? | no 7 | | Refactoring? | yes 8 | | New feature? | yes 9 | | New feature? | no 10 | | BC breaks? | yes/no 11 | | Deprecations? | yes 12 | | Deprecations? | no 13 | | Tests pass? | yes/no 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /html/ 3 | /bin/ 4 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | environment: 3 | php: 4 | version: 7.0.8 5 | tests: 6 | override: 7 | - 8 | command: 'bin/phpunit --coverage-clover=some-file' 9 | coverage: 10 | file: 'some-file' 11 | format: 'clover' 12 | filter: 13 | excluded_paths: 14 | - "tests/" 15 | 16 | -------------------------------------------------------------------------------- /CHANGELOG-2.0.md: -------------------------------------------------------------------------------- 1 | CHANGELOG for 2.0 2 | ================= 3 | 4 | This changelog references the relevant changes (bug and security fixes) done 5 | in 2.0 minor versions. 6 | 7 | - feature #55 - add missing phpunit configuration for coverage 8 | - feature #66 - add missing MIT license file 9 | - fix #50 - add pagerfanta in composer 10 | - fix #64 - add installation guidelines 11 | -------------------------------------------------------------------------------- /CHANGELOG-2.1.md: -------------------------------------------------------------------------------- 1 | CHANGELOG for 2.1 2 | ================= 3 | 4 | This changelog references the relevant changes (bug and security fixes) done 5 | in 2.1 minor versions. 6 | 7 | - feature #67 - move all github files inside `.github` folder 8 | - feature #66 - add missing licence 9 | - feature #62 - add additional filter component 10 | - feature #71 - add new `Objects\Value` component to wrap additional filter's value 11 | - fix #68 - add `GraphWalker` to create mocks for testing purpose 12 | -------------------------------------------------------------------------------- /CHANGELOG-2.2.md: -------------------------------------------------------------------------------- 1 | CHANGELOG for 2.2 2 | ================= 3 | 4 | This changelog references the relevant changes (bug and security fixes) done 5 | in 2.2 minor versions. 6 | 7 | - add new [FilterObject] component 8 | - undefined index list 9 | - anonymous method `setFilters` to more explicit `setAndFilters` 10 | - remove `Mado\QueryBundle\Queries\Objects\Operator` statement 11 | - remove variable assignment 'cause its unused 12 | - convert negative limit to PHP_INT_MAX 13 | - add [Services\FilterExtractor] extract additional filters from AdditionalFilterable 14 | - add [Objects\Filter] to read filter value 15 | - add [AdditionalFilterable] interface 16 | - add the possibility to just return the count of query results 17 | -------------------------------------------------------------------------------- /CHANGELOG-2.3.md: -------------------------------------------------------------------------------- 1 | CHANGELOG for 2.3 2 | ================= 3 | 4 | This changelog references the relevant changes (bug and security fixes) done 5 | in 2.3 minor versions. 6 | 7 | - fix - remove deprecated [Queries\Object\Operator] class 8 | - feature - [QueryBuilderFactory] add relation to query builder 9 | - feature - [BaseRepository] get route params directly from request -------------------------------------------------------------------------------- /CHANGELOG-2.4.md: -------------------------------------------------------------------------------- 1 | CHANGELOG for 2.4 2 | ================= 3 | 4 | This changelog references the relevant changes (bug and security fixes) done 5 | in 2.4 minor versions. 6 | 7 | - fix - or filter generate left join instead of inner join 8 | - feature - added operator isnull and isnotnull 9 | - fix - don't adding parameters in filter or if operator is null type 10 | - feature - added operator listcontains 11 | - feature - [BaseRepository] added find all no paginated method 12 | - feature - added possibility to have more or conditions -------------------------------------------------------------------------------- /CHANGELOG-2.5.md: -------------------------------------------------------------------------------- 1 | CHANGELOG for 2.5 2 | ================= 3 | 4 | This changelog references the relevant changes (bug and security fixes) done 5 | in 2.5 minor versions. 6 | 7 | - feature - add new component HalResponse to wrap raw json responses 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Studio Mado 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .QUERYBUNDLE: runtests 2 | runtests: install 3 | php bin/phpunit -c phpunit.xml 4 | 5 | .QUERYBUNDLE: install 6 | install: 7 | composer install 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QueryBundle 2 | 3 | latest stable version [![Latest Stable Version](https://poser.pugx.org/studiomado/query-bundle/version)](https://packagist.org/packages/studiomado/query-bundle) 4 | 5 | 6 | | 2.4 (master) | 2.3 | 2.2 | 7 | |----------------|----------|---| 8 | | [![Build Status](https://scrutinizer-ci.com/g/studiomado/query-bundle/badges/build.png?b=master)](https://scrutinizer-ci.com/g/studiomado/query-bundle/build-status/master) | [![Build Status](https://scrutinizer-ci.com/g/studiomado/query-bundle/badges/build.png?b=2.3)](https://scrutinizer-ci.com/g/studiomado/query-bundle/build-status/2.3) | [![Build Status](https://scrutinizer-ci.com/g/studiomado/query-bundle/badges/build.png?b=2.2)](https://scrutinizer-ci.com/g/studiomado/query-bundle/build-status/2.2) | 9 | | [![Code Coverage](https://scrutinizer-ci.com/g/studiomado/query-bundle/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/studiomado/query-bundle/?branch=master) | [![Code Coverage](https://scrutinizer-ci.com/g/studiomado/query-bundle/badges/coverage.png?b=2.3)](https://scrutinizer-ci.com/g/studiomado/query-bundle/?branch=2.3) | [![Code Coverage](https://scrutinizer-ci.com/g/studiomado/query-bundle/badges/coverage.png?b=2.2)](https://scrutinizer-ci.com/g/studiomado/query-bundle/?branch=2.2) | 10 | | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/studiomado/query-bundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/studiomado/query-bundle/?branch=master) | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/studiomado/query-bundle/badges/quality-score.png?b=2.3)](https://scrutinizer-ci.com/g/studiomado/query-bundle/?branch=2.3) | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/studiomado/query-bundle/badges/quality-score.png?b=2.2)](https://scrutinizer-ci.com/g/studiomado/query-bundle/?branch=2.2) | 11 | 12 | 13 | ## Run tests 14 | 15 | - `./runtests.sh` run all unit tests 16 | - `./agile.sh` generate testdox documentation 17 | - `./coverage.sh` generate and open html coverage 18 | 19 | # Plain symfony project for query-bundle 20 | 21 | The purpose of this project is to see how studiomado/query-bundle works and can be installed in a plain symfony project. 22 | 23 | - database configuration 24 | - install query-bundle 25 | - create at least one entity 26 | 27 | # Database configuration 28 | 29 | Remember to update parameter.yml file, parameter.yml.dist and config.yml file. In config.yml file also remember that the drive MUST be changed in pdo_sqlite to enable doctrine to work with this database. 30 | 31 | This is just an example: for this example we use sqlite but in production you can use mysql or postgres or any other database supported by doctrine. 32 | 33 | prompt> ./bin/console doctrine:database:create 34 | Created database /path/to/project/var/data/data.sqlite for connection named default 35 | 36 | # Install query-bundle 37 | 38 | prompt> composer require studiomado/query-bundle 39 | 40 | # Create at least one entity 41 | 42 | Create at least one entity ... 43 | 44 | prompt> ./bin/console doctrine:generate:entity 45 | 46 | In this example I created an entity Task following command steps. 47 | 48 | created ./src/AppBundle/Entity/ 49 | created ./src/AppBundle/Entity/Task.php 50 | > Generating entity class src/AppBundle/Entity/Task.php: OK! 51 | > > Generating repository class src/AppBundle/Repository/TaskRepository.php: OK! 52 | 53 | ... and update the schema ... 54 | 55 | prompt> ./bin/console doctrine:schema:update 56 | ATTENTION: This operation should not be executed in a production environment. 57 | Use the incremental update to detect changes during development and use 58 | the SQL DDL provided to manually update your database in production. 59 | 60 | The Schema-Tool would execute "1" queries to update the database. 61 | Please run the operation by passing one - or both - of the following options: 62 | doctrine:schema:update --force to execute the command 63 | doctrine:schema:update --dump-sql to dump the SQL statements to the screen 64 | 65 | The schema update works only with force option 66 | 67 | prompt> ./bin/console doctrine:schema:update --force 68 | Updating database schema... 69 | Database schema updated successfully! "1" query was executed 70 | 71 | Just take a look of the database content (that now is simply empty). 72 | 73 | prompt> ./bin/console doctrine:query:dql "select t from AppBundle:Task t" 74 | 75 | The query will return an empty array of result 76 | 77 | array (size=0) 78 | empty 79 | 80 | Just add first task ... 81 | 82 | prompt> ./bin/console doctrine:query:sql "insert into task values (null, 'complete this guide', 'todo') " 83 | 84 | and take a look of the content 85 | 86 | prompt> ./bin/console doctrine:query:dql "select t from AppBundle:Task t" 87 | 88 | array (size=1) 89 | 0 => 90 | object(stdClass)[507] 91 | public '__CLASS__' => string 'AppBundle\Entity\Task' (length=21) 92 | public 'id' => int 1 93 | public 'description' => string 'complete this guide' (length=19) 94 | public 'status' => string 'todo' (length=4) 95 | 96 | 97 | # Complete installation 98 | 99 | First of all install vendors 100 | 101 | prompt> composer require jms/serializer-bundle 102 | prompt> composer require willdurand/hateoas-bundle 103 | prompt> composer require white-october/pagerfanta-bundle 104 | prompt> composer require friendsofsymfony/rest-bundle 105 | 106 | and then, … add vendors in your app/AppKernel 107 | 108 | new FOS\RestBundle\FOSRestBundle(), 109 | new JMS\SerializerBundle\JMSSerializerBundle(), 110 | new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(), 111 | 112 | # Complete configuration and use the bundle 113 | 114 | Once everything is done, you can add new endpoints using the query-bundle to query the database. 115 | 116 | ``` 117 | namespace AppBundle\Controller; 118 | 119 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 120 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 121 | use Symfony\Component\HttpFoundation\Request; 122 | use Symfony\Component\HttpFoundation\Response; 123 | 124 | class DefaultController extends Controller 125 | { 126 | /** @Route("/", name="homepage") */ 127 | public function indexAction( 128 | Request $request, 129 | \Doctrine\ORM\EntityManager $em, 130 | \JMS\Serializer\Serializer $serializer 131 | ) { 132 | $data = $em->getRepository('AppBundle:Task') 133 | ->setRequest($request) 134 | ->findAllPaginated(); 135 | 136 | $content = $serializer->serialize($data, 'json'); 137 | 138 | return new Response($content, 200); 139 | } 140 | } 141 | ``` 142 | 143 | # Configure your entity repository 144 | 145 | Now be sure that your repository extends the right BaseRepository. 146 | 147 | ``` 148 | namespace AppBundle\Repository; 149 | 150 | class TaskRepository extends \Mado\QueryBundle\Repositories\BaseRepository 151 | { 152 | // to do … 153 | } 154 | ``` 155 | 156 | ``` 157 | namespace AppBundle\Entity; 158 | 159 | /** @ORM\Entity(repositoryClass="AppBundle\Repository\TaskRepository") */ 160 | class Task 161 | { 162 | // to do … 163 | } 164 | ``` 165 | 166 | # Customize entity serialization 167 | 168 | Now if you want to customize responses add 169 | 170 | use JMS\Serializer\Annotation as JMS; 171 | 172 | On top of your entities and complete your JMS configurations. See JMS documentation to get all the complete documentation. 173 | 174 | Here some examples: 175 | 176 | - http://127.0.0.1:8000/?filtering[status]=todo 177 | - http://127.0.0.1:8000/?filtering[status|contains]=od 178 | - http://127.0.0.1:8000/?filtering[status|endswith]=gress 179 | 180 | # Find All No Paginated 181 | 182 | Added a new method in **BaseRepository** 183 | When you need results applying filter and sort without pagination 184 | ``` 185 | public function findAllNoPaginated(); 186 | ``` 187 | This feature was needed to create an Excel Report, injecting results into the Excel Report 188 | 189 | Example without pagination 190 | -------------------------- 191 | In Controller: 192 | ``` 193 | public function getTasksExcelReportAction(Request $request) 194 | { 195 | $tasks = $this->getDoctrine() 196 | ->getRepository('AppBundle:Task') 197 | ->findAllNoPaginated(); 198 | 199 | $reportExcel = new TasksReport($tasks); 200 | $reportExcel->createReport(); 201 | 202 | $excelContent = $reportExcel->printReport(); 203 | 204 | return new Response( 205 | $excelContent, 206 | 200, [ 207 | 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 208 | ] 209 | ); 210 | } 211 | ``` 212 | 213 | Example with pagination 214 | ----------------------- 215 | In Controller: 216 | ``` 217 | public function getTasksAction(Request $request) 218 | { 219 | return $this->getDoctrine() 220 | ->getRepository('AppBundle:Task') 221 | ->setRequest($request) 222 | ->findAllPaginated(); 223 | } 224 | ``` 225 | 226 | # Queries 227 | 228 | ## AND Conditions 229 | 230 | If you want to create an AND condition with this library you can create it from the client for example with a simple GET request like this: 231 | 232 | ``` 233 | /api/foo?filtering[name|eq]=bar&filtering[surname|eq]=bar 234 | ``` 235 | 236 | This request will produce a query like this: 237 | 238 | ``` 239 | SELECT f0_.id AS id_0, f0_.name AS name_1, f0_.surname AS surname_2" . 240 | FROM foo f0_" . 241 | WHERE f0_.name = "bar" AND f0_.surname = "bar" 242 | ``` 243 | 244 | ## OR Conditions 245 | 246 | If you want to create an OR condition with this library you can create it from the client for example with a simple GET request like this: 247 | 248 | ``` 249 | /api/foo?filtering_or[name|eq]=bar&filtering_or[surname|eq]=bar 250 | ``` 251 | 252 | This request will produce a query like this: 253 | 254 | ``` 255 | SELECT f0_.id AS id_0, f0_.name AS name_1, f0_.surname AS surname_2" . 256 | FROM foo f0_" . 257 | WHERE ((f0_.name = "bar" OR f0_.surname = "bar")) 258 | ``` 259 | 260 | Instead, if you want to have more OR conditions separated you can do something like this: 261 | 262 | ``` 263 | /api/foo?filtering_or[name|eq|1]=bar&filtering_or[surname|eq|1]=bar&filtering_or[group|contains|2]=baz&filtering_or[role|contains|2]=baz 264 | ``` 265 | 266 | This request will produce a query like this: 267 | 268 | ``` 269 | SELECT f0_.id AS id_0, f0_.name AS name_1, f0_.surname AS surname_2, f0_.group AS group_3, f0_.role AS role_4" . 270 | FROM foo f0_" . 271 | WHERE (f0_.name = "bar" OR f0_.surname = "bar") AND (f0_.group LIKE "%baz%" OR f0_.role LIKE "%baz%") 272 | ``` 273 | 274 | This can be done by using a counter after the operator separated by ```|``` 275 | 276 | ## Search into relations 277 | 278 | If you want to search inside an entity where a condition is inside another entity you can do this: 279 | 280 | ``` 281 | /api/users?filtering[_embedded.group.name|contains =bar 282 | ``` 283 | 284 | This request will produce a query like this: 285 | 286 | ``` 287 | SELECT u0_.id AS id_0 u0_.username AS username_1, u0_.group_id AS group_id_2 " . 288 | FROM User u0_ 289 | LEFT JOIN Group g1_ ON u0_.group_id = g1_.id " . 290 | WHERE g1_.name LIKE "%bar%" 291 | ``` 292 | 293 | To do this you need to add inside the user entity some Hateoas annotations like this: 294 | 295 | ``` 296 | * @Hateoas\Relation( 297 | * "groups", 298 | * embedded = @Hateoas\Embedded( 299 | * "expr(object.getGroups())", 300 | * exclusion = @Hateoas\Exclusion( 301 | * excludeIf = "expr(object.getGroups().isEmpty() === true)", 302 | * groups={"groups"}, 303 | * maxDepth = 1 304 | * ) 305 | * ), 306 | * exclusion = @Hateoas\Exclusion( 307 | * excludeIf = "expr(object.getGroups().isEmpty() === true)", 308 | * groups={"groups"}, 309 | * maxDepth = 1 310 | * ) 311 | * ) 312 | 313 | ``` 314 | 315 | If you add Hateoas annotations correctly, you can search deeper than only "one level". Here an example: 316 | 317 | ``` 318 | /api/users?filtering[_embedded.profile.location.country.name|contains]=italy 319 | ``` 320 | 321 | In this example you search all users that have a profile with a country location name: Italy. 322 | Profile, location and country are entities and name is the field. 323 | 324 | You can use _embedded filter also into filtering_or conditions. -------------------------------------------------------------------------------- /UPGRADE-2.1.md: -------------------------------------------------------------------------------- 1 | UPGRADE FROM 2.0 to 2.1 2 | ======================= 3 | 4 | NestingException 5 | ---------------- 6 | 7 | * Notice if some loop is nested too many times. 8 | 9 | UnespectedValueException 10 | ------------------------ 11 | 12 | * Throwed whenever parent root entity of a relation not exists. 13 | 14 | UnreachablePathException 15 | ------------------------ 16 | 17 | * An exception that's throwed when is not possibile to get the path. 18 | 19 | UndefinedPathException 20 | ---------------------- 21 | 22 | * An exception that's throwed when entities path is requested too early. 23 | 24 | MapBuilder 25 | ---------- 26 | 27 | * Build a map based on Doctrine's DataMapper. 28 | 29 | DataMapper 30 | ---------- 31 | 32 | * An interface to build maps of relations of database entities. 33 | 34 | JsonPathFinder 35 | -------------- 36 | 37 | * Build the path of relations between entities. 38 | 39 | ```php 40 | use Mado\QueryBundle\Meta\DataMapper; 41 | 42 | class MyCustomMapper implements DataMapper 43 | { 44 | public function getMap() : array 45 | { 46 | return [ 47 | "FooBundle\\Entity\\Start" => [ 48 | "relations" => [ 49 | "end" => "AppBundle\\Entity\\End", 50 | "foo" => "AppBundle\\Entity\\Foo", 51 | ] 52 | ] 53 | ]; 54 | } 55 | }; 56 | 57 | $finder = new JsonPathFinder(new MyCustomMapper($entityManager)); 58 | $finder->setQueryStartEntity("FooBundle\\Entity\\Start"); 59 | $finder->getPathToEntity("AppBundle\\Entity\\End"); // _embedded.start.end 60 | ``` 61 | 62 | Dijkstra 63 | -------- 64 | 65 | * Navigate the graph to find the minimum spanning tree 66 | 67 | ```php 68 | $dijkstra = new Dijkstra(new MyCustomMapper($entityManager)); 69 | $entities = $dijkstra->shortestPaths($from, $to) 70 | ``` 71 | 72 | DijkstraWalker 73 | -------------- 74 | 75 | * Use Dijkstra to find paths to use in querystring. For example to query all 76 | users with a group that is inside certain category with id 2, 3 or 5 we 77 | need this in query string. 78 | 79 | `filtering[_embedded.groups.category.id|list]=2,3,5` 80 | 81 | If we want to force this filter in queryBundle 82 | 83 | ```php 84 | $walker = new DijkstraWalker( 85 | new \Mado\QueryBundle\Component\Meta\MapBuilder($manager) 86 | new Dijkstra() 87 | ); 88 | 89 | $walker->buildPathBetween( 90 | \AppBundle\Entity\Start::class, 91 | \FooBundle\Entity\End:class 92 | ); 93 | 94 | $filter = $walker->getPath(); 95 | 96 | $repository = $this->getDoctrine() 97 | ->getRepository('AppBundle:User') 98 | ->setRequestWithFilter($request, [ 99 | $filter . '.id|list' => '2,3,5' 100 | ]); 101 | ``` 102 | 103 | Objects\Value 104 | ------------- 105 | 106 | * When a value is not a string, it comes from an additional filter. 107 | Additional filters should be stored as ... 108 | 109 | ```php 110 | { 111 | "filter_name": { 112 | "list" : [23, 666], 113 | "entities" : [ 114 | {id:23,foo:"foo",bar:"bar"}, 115 | {id:666,foo:"foo",bar:"bar"}, 116 | ] 117 | }, 118 | "filter_name": { 119 | "list" : [/* array of id */], 120 | "entities" : [ 121 | // complete objects 122 | ] 123 | } 124 | } 125 | ``` 126 | -------------------------------------------------------------------------------- /UPGRADE-2.2.md: -------------------------------------------------------------------------------- 1 | UPGRADE FROM 2.1 to 2.2 2 | ======================= 3 | 4 | ## Deprecations 5 | 6 | \Dictionary\Operators 7 | --------------------- 8 | 9 | * Marked as deprecated. It will be removed in version 2.3. 10 | 11 | \Queries\Objects\Operator 12 | ------------------------- 13 | 14 | * This component is now marked as deprecated and will be removed in version 15 | 2.3. 16 | 17 | ## Enhancements 18 | 19 | \Dictionary 20 | ----------- 21 | 22 | * Old Dictionary\Operators is now moved here in `Mado\QueryBundle\Dictionary`. 23 | 24 | * Add `nlist` operator. With this operator is requested a field returned 25 | must not be included in results. For example, to get every records of an 26 | entity except those whose ids are 42, 23 and 44 the query string should be: 27 | 28 | filtering[id|nlist]=42,23,44 29 | 30 | and all other ids will be returned. 31 | 32 | \Queries\Objects\FilterObject 33 | ----------------------------- 34 | 35 | * This new component take te responsibility to manage a filtering option. For 36 | example, inside the `filtering[foo|bar]=42` query, FilterObject aims to 37 | manage the `foo|bar` part. It knows field name and operator name. 38 | -------------------------------------------------------------------------------- /UPGRADE-2.3.md: -------------------------------------------------------------------------------- 1 | UPGRADE FROM 2.2 to 2.3 2 | ======================= 3 | 4 | Dictionary\Operators 5 | -------------------- 6 | 7 | * The `componet` was removed. 8 | 9 | Queries\QueryBuilderFactory 10 | --------------------------- 11 | 12 | * Use `setAndFilters` instead of `setFilters` 13 | 14 | * `QueryBuilderFactory::setRel()` now accept only arrays and relations are 15 | always stored as array. 16 | 17 | * Deprecate `customQueryStringValues` so is unnecessary to overwrite It inside entities 18 | -------------------------------------------------------------------------------- /UPGRADE-2.4.md: -------------------------------------------------------------------------------- 1 | UPGRADE FROM 2.3 to 2.4 2 | ======================= 3 | 4 | Repositories\BaseRepository 5 | --------------------------- 6 | 7 | * Removed `customQueryStringValues`; 8 | -------------------------------------------------------------------------------- /agile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | php bin/phpunit -c phpunit.xml --testdox 3 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "studiomado/query-bundle", 3 | "type": "symfony-bundle", 4 | "authors": [ 5 | { 6 | "name": "Simone Gentili", 7 | "email": "sensorario@gmail.com" 8 | } 9 | ], 10 | "autoload": { 11 | "psr-4": { 12 | "": ["src", "tests"] 13 | } 14 | }, 15 | "config": { 16 | "platform": { 17 | "php": "7.0.9" 18 | }, 19 | "bin-dir": "bin" 20 | }, 21 | "require": { 22 | "symfony/dependency-injection": ">=3.3", 23 | "symfony/config": ">=3.3", 24 | "symfony/http-kernel": ">=3.3", 25 | "pagerfanta/pagerfanta": "^1.0", 26 | "willdurand/hateoas": "2.10", 27 | "symfony/console": "~3.4.3", 28 | "symfony/debug": "~3.4.3", 29 | "symfony/event-dispatcher": "~3.4.3", 30 | "symfony/filesystem": "~3.4.3", 31 | "symfony/http-foundation": "~3.4.3" 32 | }, 33 | "require-dev": { 34 | "phpunit/phpunit": "^6.3" 35 | }, 36 | "extra": { 37 | "branch-alias": { 38 | "dev-master": "2.5-dev" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | php bin/phpunit -c phpunit.xml --coverage-html=html && open html/index.html 3 | -------------------------------------------------------------------------------- /doc/additional-filters.md: -------------------------------------------------------------------------------- 1 | # Additional Filters 2 | 3 | Aims to limit information in response, based on predefined rules. Those rule 4 | must be stored inside your application's user. 5 | 6 | ## User 7 | 8 | User must respect `\Mado\QueryBundle\Interfaces\AdditionalFilterable` interface 9 | and implement methods `getAdditionalFilters()`. 10 | 11 | ## Custom Filter 12 | 13 | To work with additional filters, is necessary a custom filter. This should 14 | respect the interface `Mado\QueryBundle\Interfaces\CustomFilter`. 15 | 16 | ### Example 17 | 18 | You can create your custom filter using this syntax. 19 | 20 | ```php 21 | \Bundle\To\EntityClass::class, 29 | ]; 30 | 31 | public function __construct( 32 | EntityManagerInterface $manager, 33 | GraphWalker $dijkstraWalker, 34 | RequestStack $requestStack, 35 | LoggerInterface $logger 36 | ) { 37 | $this->manager = $manager; 38 | $this->dijkstraWalker = $dijkstraWalker; 39 | $this->requestStack = $requestStack; 40 | $this->logger = $logger; 41 | } 42 | 43 | public function setUser(AdditionalFilterable $user) 44 | { 45 | $this->additionalFilters = AdditionalFilterExtractor::fromUser($user); 46 | return $this; 47 | } 48 | 49 | public function allItemsTo(string $entity) 50 | { 51 | $this->entity = $entity; 52 | $filters = []; 53 | $translations = [ 54 | 'filter_name' => [ 55 | 'from' => '_embedded.shops.id', 56 | 'to' => 'id', 57 | ], 58 | ]; 59 | foreach ($translations as $filterName => $filterTranslation) { 60 | if ($this->additionalFilters->getFilters($filterName) != '') { 61 | $path = $this->getPathTo($filterName); 62 | $genericAdditionalFilter = Filter::box([ 63 | 'ids' => $this->additionalFilters->getFilters($filterName), 64 | 'path' => $path, 65 | ]); 66 | $filterKey = $genericAdditionalFilter->getFieldAndOperator(); 67 | if ([] != $filterTranslation) { 68 | if ($filterKey == $filterTranslation['from'] . '|' . $genericAdditionalFilter->getOperator()) { 69 | $filterKey = $filterTranslation['to'] . '|' . $genericAdditionalFilter->getOperator(); 70 | $genericAdditionalFilter = $genericAdditionalFilter->withFullPath($filterKey); 71 | } 72 | } 73 | $filtering = $this->requestStack->getCurrentRequest()->query->get('filtering', []); 74 | $haveCheckedAdditionalFilters = false; 75 | $field = $genericAdditionalFilter->getField(); 76 | foreach( $filtering as $filterKey => $value) { 77 | $genericQueryStringFilter = Filter::fromQueryStringFilter([ 78 | $filterKey => $value 79 | ]); 80 | if ($genericAdditionalFilter->getField() == $genericQueryStringFilter->getField()) { 81 | if ( 82 | $genericAdditionalFilter->getOperator() == 'list' 83 | && $genericAdditionalFilter->getOperator() == $genericQueryStringFilter->getOperator() 84 | ) { 85 | $haveCheckedAdditionalFilters = true; 86 | $additionalFiltersIds = explode(',', $genericAdditionalFilter->getIds()); 87 | $querystringIds = explode(',', $genericQueryStringFilter->getIds()); 88 | $intersection = array_intersect($querystringIds, $additionalFiltersIds); 89 | $ids = join(',', $intersection); 90 | if ($intersection == []) { 91 | throw new ForbiddenContentException( 92 | 'Oops! Forbidden requested id ' . $value 93 | . ' is not available. Available are ' 94 | . $genericAdditionalFilter->getIds() 95 | ); 96 | } 97 | $filters[$genericAdditionalFilter->getFieldAndOperator()] = $ids; 98 | } 99 | } 100 | } 101 | if (!$haveCheckedAdditionalFilters) { 102 | $ids = $genericAdditionalFilter->getIds(); 103 | $filters[$genericAdditionalFilter->getFieldAndOperator()] = $ids; 104 | } 105 | } 106 | } 107 | 108 | return $filters; 109 | } 110 | 111 | public function getPathTo(string $filter) 112 | { 113 | $this->dijkstraWalker->buildPathBetween( 114 | $this->entity, 115 | self::getEntityFromFilter($filter) 116 | ); 117 | return $this->dijkstraWalker->getPath(); 118 | } 119 | 120 | public static function getEntityFromFilter(string $filterName) 121 | { 122 | return self::$filterMap[$filterName]; 123 | } 124 | 125 | public function setEntity(string $entity) 126 | { 127 | $this->entity = $entity; 128 | return $this; 129 | } 130 | } 131 | ``` 132 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | src 8 | tests 9 | 10 | 11 | 12 | 13 | src/Mado/QueryBundle/Component 14 | src/Mado/QueryBundle/Dictionary 15 | src/Mado/QueryBundle/Queries 16 | src/Mado/QueryBundle/Objects 17 | src/Mado/QueryBundle/Repositories 18 | src/Mado/QueryBundle/Resources 19 | src/Mado/QueryBundle/Services 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | composer install 3 | php bin/phpunit -c phpunit.xml 4 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Component/Meta/DataMapper.php: -------------------------------------------------------------------------------- 1 | $metaData) { 21 | foreach ($metaData['relations'] as $itemKey => $itemValue) { 22 | $this->map[$nodeName][$itemValue] = 1; 23 | } 24 | } 25 | } 26 | 27 | private function processQueue(array $excluded) 28 | { 29 | $this->ensureMapIsDefined(); 30 | 31 | $node = array_search(min($this->visited), $this->visited); 32 | 33 | if (!empty($this->map[$node]) && !in_array($node, $excluded)) { 34 | foreach ($this->map[$node] as $neighbor => $cost) { 35 | if (isset($this->distance[$neighbor])) { 36 | if ($this->distance[$node] + $cost < $this->distance[$neighbor]) { 37 | $this->distance[$neighbor] = $this->distance[$node] + $cost; 38 | $this->prev[$neighbor] = [$node]; 39 | $this->visited[$neighbor] = $this->distance[$neighbor]; 40 | } elseif ($this->distance[$node] + $cost === $this->distance[$neighbor]) { 41 | $this->prev[$neighbor][] = $node; 42 | $this->visited[$neighbor] = $this->distance[$neighbor]; 43 | } 44 | } 45 | } 46 | } 47 | 48 | unset($this->visited[$node]); 49 | } 50 | 51 | private function extractPaths($target) 52 | { 53 | $paths = [[$target]]; 54 | while (current($paths) !== false) { 55 | $key = key($paths); 56 | $path = current($paths); 57 | next($paths); 58 | if (!empty($this->prev[$path[0]])) { 59 | foreach ($this->prev[$path[0]] as $prev) { 60 | $copy = $path; 61 | array_unshift($copy, $prev); 62 | $paths[] = $copy; 63 | } 64 | unset($paths[$key]); 65 | } 66 | } 67 | return array_values($paths); 68 | } 69 | 70 | public function shortestPaths($source, $target, array $excluded = array()) 71 | { 72 | $this->ensureMapIsDefined(); 73 | 74 | $this->distance = array_fill_keys(array_keys($this->map), INF); 75 | $this->distance[$source] = 0; 76 | $this->prev = array_fill_keys(array_keys($this->map), []); 77 | $this->visited = [$source => 0]; 78 | 79 | while (!empty($this->visited)) { 80 | $this->processQueue($excluded); 81 | } 82 | 83 | if ($source === $target) { 84 | return [[$source]]; 85 | } 86 | 87 | if (empty($this->prev[$target])) { 88 | return []; 89 | } 90 | 91 | return $this->extractPaths($target); 92 | } 93 | 94 | public function ensureMapIsDefined() 95 | { 96 | if (!$this->map) { 97 | throw new \RuntimeException( 98 | 'Oops! Map is not defined' 99 | ); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Component/Meta/DijkstraWalker.php: -------------------------------------------------------------------------------- 1 | builder = $builder; 24 | $this->dijkstra = $dijkstra; 25 | 26 | $this->init(); 27 | } 28 | 29 | public function buildPathBetween($start, $end) : bool 30 | { 31 | $this->builder->rebuildRelationMap(); 32 | 33 | $shortestPath = $this->dijkstra->shortestPaths($start, $end); 34 | $prevRelations = $this->map[$start]['relations']; 35 | 36 | $this->path = '_embedded'; 37 | 38 | foreach ($shortestPath[0] as $meta) { 39 | if ($relationName = array_search($meta, $prevRelations)) { 40 | $this->path .= '.' . $relationName; 41 | } 42 | 43 | $prevRelations = $this->map[$meta]['relations']; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | public function getPath() : string 50 | { 51 | if (!$this->path) { 52 | throw new \RuntimeException( 53 | 'Oops! path was never builded.' 54 | ); 55 | } 56 | 57 | return $this->path; 58 | } 59 | 60 | public function init() 61 | { 62 | $this->dijkstra->setMap( 63 | $this->map = $this->builder->getMap() 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Component/Meta/Exceptions/UnInitializedQueryBuilderException.php: -------------------------------------------------------------------------------- 1 | manager = $manager; 20 | } 21 | 22 | public function setMap(array $map) : bool 23 | { 24 | $this->map = $map; 25 | 26 | return true; 27 | } 28 | 29 | public function getMap() : array 30 | { 31 | if ($this->map == []) { 32 | $this->rebuildRelationMap(); 33 | } 34 | 35 | return $this->map; 36 | } 37 | 38 | public function forceCache(array $map) 39 | { 40 | $this->map = $map; 41 | } 42 | 43 | /** @codeCoverageIgnore */ 44 | public static function relations(ClassMetadata $classMetadata) 45 | { 46 | $encoded = json_encode($classMetadata); 47 | $decoded = json_decode($encoded, true); 48 | $relations = $decoded['associationMappings']; 49 | 50 | $relMap = []; 51 | 52 | foreach ($relations as $name => $meta) { 53 | $relMap[$name] = $meta['targetEntity']; 54 | } 55 | 56 | return $relMap; 57 | } 58 | 59 | public function rebuildRelationMap() : bool 60 | { 61 | $allMetadata = $this->manager 62 | ->getMetadataFactory() 63 | ->getAllMetadata(); 64 | 65 | foreach ($allMetadata as $singleEntityMetadata) { 66 | // @codeCoverageIgnoreStart 67 | $this->map[$singleEntityMetadata->getName()]['relations'] = self::relations($singleEntityMetadata); 68 | // @codeCoverageIgnoreEnd 69 | } 70 | 71 | return true; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | root('mado_query'); 22 | 23 | // Here you should define the parameters that are allowed to 24 | // configure your bundle. See the documentation linked above for 25 | // more information on that topic. 26 | 27 | return $treeBuilder; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/DependencyInjection/MadoQueryExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 16 | 17 | $loader = new Loader\YamlFileLoader( 18 | $container, 19 | new FileLocator(__DIR__ . '/../Resources/config') 20 | ); 21 | 22 | $loader->load('services.yml'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Dictionary.php: -------------------------------------------------------------------------------- 1 | [ 28 | self::FIELD_LIST, 29 | self::FIELD_NOT_IN_LIST, 30 | self::FIELD_EQUALITY, 31 | self::NUMBER_EQUAL, 32 | self::NUMBER_NOT_EQUAL, 33 | self::NUMBER_GREATER, 34 | self::NUMBER_GREATER_EQUAL, 35 | self::NUMBER_LITTLE, 36 | self::NUMBER_LITTLE_EQUAL, 37 | self::STRING_STARTS_WITH, 38 | self::STRING_CONTAINS, 39 | self::STRING_NOT_CONTAINS, 40 | self::STRING_ENDS_WITH, 41 | 'isnull', 42 | 'isnotnull', 43 | 'listcontains', 44 | ], 45 | 46 | 'fields' => [ 47 | self::FIELD_LIST, 48 | self::FIELD_NOT_IN_LIST, 49 | self::FIELD_EQUALITY, 50 | ], 51 | 52 | 'integer' => [ 53 | self::NUMBER_EQUAL, 54 | self::NUMBER_NOT_EQUAL, 55 | self::NUMBER_GREATER, 56 | self::NUMBER_GREATER_EQUAL, 57 | self::NUMBER_LITTLE, 58 | self::NUMBER_LITTLE_EQUAL, 59 | ], 60 | 61 | 'string' => [ 62 | self::STRING_STARTS_WITH, 63 | self::STRING_CONTAINS, 64 | self::STRING_NOT_CONTAINS, 65 | self::STRING_ENDS_WITH, 66 | ], 67 | 68 | ]; 69 | 70 | private static $operatorMap = [ 71 | 72 | self::NUMBER_EQUAL => [ 'meta' => ' =' ], 73 | self::NUMBER_NOT_EQUAL => [ 'meta' => '!=' ], 74 | self::NUMBER_GREATER => [ 'meta' => '>' ], 75 | self::NUMBER_GREATER_EQUAL => [ 'meta' => '>=' ], 76 | self::NUMBER_LITTLE => [ 'meta' => '<' ], 77 | self::NUMBER_LITTLE_EQUAL => [ 'meta' => '<=' ], 78 | 79 | self::STRING_STARTS_WITH => [ 'meta' => 'LIKE', 'substitution_pattern' => '{string}%' ], 80 | self::STRING_CONTAINS => [ 'meta' => 'LIKE', 'substitution_pattern' => '%{string}%' ], 81 | self::STRING_NOT_CONTAINS => [ 'meta' => 'NOT LIKE', 'substitution_pattern' => '%{string}%' ], 82 | self::STRING_ENDS_WITH => [ 'meta' => 'LIKE', 'substitution_pattern' => '%{string}' ], 83 | 84 | self::FIELD_LIST => [ 'meta' => 'IN', 'substitution_pattern' => '({string})' ], 85 | self::FIELD_NOT_IN_LIST => [ 'meta' => 'NOT IN', 'substitution_pattern' => '({string})' ], 86 | self::FIELD_EQUALITY => [ 'meta' => '=' ], 87 | 88 | 'isnull' => [ 89 | 'meta' => 'IS NULL', 90 | ], 91 | 92 | 'isnotnull' => [ 93 | 'meta' => 'IS NOT NULL', 94 | ], 95 | 96 | 'listcontains' => [ 97 | 'meta' => 'LIKE', 98 | 'substitution_pattern' => '({string})', 99 | ], 100 | 101 | ]; 102 | 103 | public static function getOperators() 104 | { 105 | return self::$operatorMap; 106 | } 107 | 108 | public static function getPublicOperators() 109 | { 110 | return self::$doctrineTypeToOperatorsMap; 111 | } 112 | 113 | public static function getOperatorsFromDoctrineType(string $type) 114 | { 115 | try { 116 | self::ensureTypeIsDefined($type); 117 | return self::$doctrineTypeToOperatorsMap[$type]; 118 | } catch (\Exception $e) { 119 | return self::$doctrineTypeToOperatorsMap['default']; 120 | } 121 | } 122 | 123 | public static function ensureTypeIsDefined($type) 124 | { 125 | if (!isset(self::$doctrineTypeToOperatorsMap[$type])) { 126 | throw new \RuntimeException( 127 | 'Oops! Type "'.$type.'" is not yet defined.' 128 | ); 129 | } 130 | } 131 | } 132 | 133 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Exceptions/ForbiddenContentException.php: -------------------------------------------------------------------------------- 1 | self::buildRawFilter($path, $operator), 25 | 'ids' => $ids, 26 | 'operator' => $operator, 27 | 'path' => $path, 28 | ]); 29 | } 30 | 31 | private static function buildRawFilter($path, $operator) 32 | { 33 | return $path . '.id|' . $operator; 34 | } 35 | 36 | private function __construct(array $params) 37 | { 38 | $this->rawFilter = $params['raw_filter']; 39 | $this->ids = $params['ids']; 40 | $this->operator = $params['operator']; 41 | $this->path = $params['path']; 42 | } 43 | 44 | public function getFieldAndOperator() 45 | { 46 | return $this->rawFilter; 47 | } 48 | 49 | public function getIds() 50 | { 51 | return $this->ids; 52 | } 53 | 54 | public function getOperator() 55 | { 56 | return $this->operator; 57 | } 58 | 59 | public function getPath() 60 | { 61 | return $this->path; 62 | } 63 | 64 | public function withPath($path) 65 | { 66 | $rawFilter = self::buildRawFilter($path, $this->operator); 67 | 68 | if ($path == '') { 69 | $rawFilter = str_replace('.', '', $rawFilter); 70 | } 71 | 72 | return new self([ 73 | 'raw_filter' => $rawFilter, 74 | 'ids' => $this->ids, 75 | 'operator' => $this->operator, 76 | 'path' => $path, 77 | ]); 78 | } 79 | 80 | public function withFullPath($path) 81 | { 82 | $explodedPath = explode('|', $path); 83 | 84 | $path = $explodedPath[0]; 85 | $operator = $explodedPath[1]; 86 | 87 | return new self([ 88 | 'raw_filter' => join('|', $explodedPath), 89 | 'ids' => $this->ids, 90 | 'operator' => $operator, 91 | 'path' => $path, 92 | ]); 93 | } 94 | 95 | public function getField() 96 | { 97 | $explodedPath = explode('|', $this->getFieldAndOperator()); 98 | 99 | return $explodedPath[0]; 100 | } 101 | 102 | public static function fromQueryStringFilter(array $params) 103 | { 104 | return new self([ 105 | 'raw_filter' => key($params), 106 | 'ids' => current($params), 107 | 'operator' => explode('|', key($params))[1], 108 | 'path' => null, 109 | ]); 110 | } 111 | 112 | public function getValue() 113 | { 114 | return $this->ids; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Objects/HalResponse.php: -------------------------------------------------------------------------------- 1 | json = $json; 12 | } 13 | 14 | public static function fromJson(string $json) 15 | { 16 | return self::fromArray(json_decode($json, true)); 17 | } 18 | 19 | public static function fromArray(array $json) 20 | { 21 | return new self($json); 22 | } 23 | 24 | public function total() 25 | { 26 | return $this->json['total']; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Objects/MetaDataAdapter.php: -------------------------------------------------------------------------------- 1 | metadata = $metadata; 14 | } 15 | 16 | public function setEntityName($entityName) 17 | { 18 | $this->entityName = $entityName; 19 | } 20 | 21 | public function getFields() 22 | { 23 | return array_keys($this->metadata->fieldMappings); 24 | } 25 | 26 | public function getEntityAlias() 27 | { 28 | $entityName = explode('\\', strtolower($this->entityName)); 29 | 30 | $entityName = $entityName[count($entityName) - 1][0]; 31 | 32 | return $entityName[0]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Objects/PagerfantaBuilder.php: -------------------------------------------------------------------------------- 1 | pagerfantaFactory = $pagerfantaFactory; 18 | $this->ormAdapter = $ormAdapter; 19 | } 20 | 21 | public function create($limit, $page) :Pagerfanta 22 | { 23 | $pager = new Pagerfanta($this->ormAdapter); 24 | $pager->setNormalizeOutOfRangePages(true); 25 | $pager->setMaxPerPage($limit); 26 | $pager->setCurrentPage($page); 27 | 28 | return $pager; 29 | } 30 | 31 | public function createRepresentation($route, $limit, $page) 32 | { 33 | $pager = $this->create( 34 | $limit, 35 | $page 36 | ); 37 | 38 | return $this->pagerfantaFactory->createRepresentation($pager, $route); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Queries/AbstractQuery.php: -------------------------------------------------------------------------------- 1 | manager = $manager; 27 | $this->parser = new StringParser(); 28 | } 29 | 30 | public function createSelectAndGroupBy($entityName, $alias, $groupByField) 31 | { 32 | $select = $alias . '.' . $groupByField . ', count(' . $alias . '.id) as num'; 33 | $groupBy = $alias . '.' . $groupByField . ''; 34 | 35 | $this->entityName = $entityName; 36 | $this->entityAlias = $alias; 37 | 38 | $this->qBuilder = $this->manager->createQueryBuilder() 39 | ->select($select) 40 | ->groupBy($groupBy); 41 | 42 | $this->joinFactory = new Join($this->getEntityName(), $this->entityAlias, $this->manager); 43 | } 44 | 45 | public function createQueryBuilder($entityName, $alias) 46 | { 47 | $this->entityName = $entityName; 48 | $this->entityAlias = $alias; 49 | 50 | $this->qBuilder = $this->manager->createQueryBuilder() 51 | ->select($alias) 52 | ->from($this->entityName, $alias); 53 | 54 | $this->joinFactory = new Join($this->getEntityName(), $this->entityAlias, $this->manager); 55 | } 56 | 57 | public function getEntityName() 58 | { 59 | return $this->entityName; 60 | } 61 | 62 | public function loadMetadataAndOptions( 63 | MetaDataAdapter $metadata, 64 | QueryBuilderOptions $options 65 | ) { 66 | $this->setFields($metadata->getFields()); 67 | 68 | $this->setAndFilters($options->getAndFilters()); 69 | $this->setOrFilters($options->getOrFilters()); 70 | $this->setSorting($options->getSorting()); 71 | $this->setRel([$options->getRel()]); 72 | $this->setPrinting($options->getPrinting()); 73 | $this->setSelect($options->getSelect()); 74 | } 75 | 76 | abstract public function setFields(array $fields = []); 77 | abstract public function setAndFilters(array $andFilters = []); 78 | abstract public function setOrFilters(array $orFilters = []); 79 | abstract public function setSorting(array $sorting = []); 80 | abstract public function setRel(array $rel); 81 | abstract public function setPrinting($printing); 82 | abstract public function setSelect($select); 83 | } 84 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Queries/AndFilter.php: -------------------------------------------------------------------------------- 1 | entityAlias = $entityAlias; 26 | $this->fields = $fields; 27 | $this->join = $join; 28 | 29 | $this->conditions = []; 30 | $this->parameters = []; 31 | $this->parser = new StringParser(); 32 | } 33 | 34 | public function createFilter(array $andFilters) 35 | { 36 | foreach ($andFilters as $filter => $value) { 37 | $this->applyFilter( 38 | Objects\FilterObject::fromRawFilter($filter), 39 | $value, 40 | Objects\Value::fromFilter($value) 41 | ); 42 | } 43 | } 44 | 45 | private function applyFilter( 46 | Objects\FilterObject $filterObject, 47 | $value, 48 | Objects\Value $filterValue 49 | ) { 50 | $whereCondition = $this->entityAlias . '.' . $filterObject->getFieldName() . ' ' 51 | . $filterObject->getOperatorMeta(); 52 | 53 | if (in_array($filterObject->getFieldName(), $this->fields)) { 54 | $salt = '_' . random_int(111, 999); 55 | 56 | if ($filterObject->isListContainsType()) { 57 | $fieldName = $this->entityAlias . '.' . $filterObject->getFieldName(); 58 | $whereCondition = $this->createWhereConditionForListContains($value, $fieldName, $filterObject->getFieldName(), $salt); 59 | } elseif ($filterObject->isListType()) { 60 | $whereCondition .= ' (:field_' . $filterObject->getFieldName() . $salt . ')'; 61 | } elseif ($filterObject->isFieldEqualityType()) { 62 | $whereCondition .= ' ' . $this->entityAlias . '.' . $value; 63 | } elseif ($filterObject->isNullType()) { 64 | $whereCondition .= ' '; 65 | } else { 66 | $whereCondition .= ' :field_' . $filterObject->getFieldName() . $salt; 67 | } 68 | 69 | $this->conditions[] = $whereCondition; 70 | 71 | if ($filterObject->haveOperatorSubstitutionPattern()) { 72 | if ($filterObject->isListContainsType()) { 73 | $value = $this->encapsulateValueForLike($value); 74 | } elseif ($filterObject->isListType()) { 75 | $value = explode(',', $value); 76 | } else { 77 | $value = str_replace( 78 | '{string}', 79 | $value, 80 | $filterObject->getOperatorsSubstitutionPattern() 81 | ); 82 | } 83 | } 84 | 85 | if (!$filterObject->isNullType()) { 86 | if ($filterObject->isListContainsType()) { 87 | $this->addMultipleParameters($value, $filterObject->getFieldName(), $salt); 88 | } else { 89 | $param = []; 90 | $param['field'] = 'field_' . $filterObject->getFieldName() . $salt; 91 | $param['value'] = $value; 92 | $this->parameters[] = $param; 93 | } 94 | } 95 | } else { 96 | if (strpos($filterObject->getFieldName(), 'Embedded.') === false) { 97 | $whereCondition .= ' ' . $this->entityAlias . '.' . $value; 98 | $this->conditions[] = $whereCondition; 99 | } 100 | } 101 | 102 | // controllo se il filtro si riferisce ad una relazione dell'entità quindi devo fare dei join 103 | // esempio per users: filtering[_embedded.groups.name|eq]=admin 104 | if (strstr($filterObject->getRawFilter(), '_embedded.')) { 105 | $this->join->join($filterObject->getRawFilter()); 106 | $this->relationEntityAlias = $this->join->getRelationEntityAlias(); 107 | 108 | $embeddedFields = explode('.', $filterObject->getFieldName()); 109 | $embeddedFieldName = $this->parser->camelize($embeddedFields[count($embeddedFields) - 1]); 110 | 111 | $salt = '_' . random_int(111, 999); 112 | 113 | $whereCondition = $this->relationEntityAlias . '.' . $embeddedFieldName . ' ' 114 | . $filterObject->getOperatorMeta(); 115 | 116 | if ($filterObject->isListContainsType()) { 117 | $fieldName = $this->relationEntityAlias . '.' . $embeddedFieldName; 118 | $whereCondition = $this->createWhereConditionForListContains($value, $fieldName, $embeddedFieldName, $salt); 119 | } elseif ($filterObject->isListType()) { 120 | $whereCondition .= ' (:field_' . $embeddedFieldName . $salt . ')'; 121 | } elseif ($filterObject->isNullType()) { 122 | $whereCondition .= ' '; 123 | } else { 124 | $whereCondition .= ' :field_' . $embeddedFieldName . $salt; 125 | } 126 | 127 | $this->conditions[] = $whereCondition; 128 | if ($filterObject->haveOperatorSubstitutionPattern()) { 129 | if ($filterObject->isListContainsType()) { 130 | $value = $this->encapsulateValueForLike($value); 131 | } elseif ($filterObject->isListType()) { 132 | $value = explode(',', $filterValue->getFilter()); 133 | } else { 134 | $value = str_replace( 135 | '{string}', 136 | $value, 137 | $filterObject->getOperatorsSubstitutionPattern() 138 | ); 139 | } 140 | } 141 | 142 | if (!$filterObject->isNullType()) { 143 | if ($filterObject->isListContainsType()) { 144 | $this->addMultipleParameters($value, $embeddedFieldName, $salt); 145 | } else { 146 | $param = []; 147 | $param['field'] = 'field_' . $embeddedFieldName . $salt; 148 | $param['value'] = $value; 149 | $this->parameters[] = $param; 150 | } 151 | } 152 | } 153 | } 154 | 155 | private function addMultipleParameters($value, $fieldName, $salt) 156 | { 157 | foreach ($value as $key => $val) { 158 | $param = []; 159 | $param['field'] = 'field_' . $fieldName . $salt . $key; 160 | $param['value'] = $val; 161 | $this->parameters[] = $param; 162 | } 163 | } 164 | 165 | private function createWhereConditionForListContains($value, $fieldName, $fieldNameWithoutAlias, $salt) :string 166 | { 167 | $whereCondition = ''; 168 | $values = explode(',', $value); 169 | foreach ($values as $key => $val) { 170 | if ($whereCondition == '') { 171 | $whereCondition = ' ('; 172 | } else { 173 | $whereCondition .= ' OR '; 174 | } 175 | 176 | $whereCondition .= $fieldName . 177 | ' LIKE :field_' . str_replace('.', '_', $fieldNameWithoutAlias) . $salt . $key; 178 | } 179 | 180 | $whereCondition .= ')'; 181 | 182 | return $whereCondition; 183 | } 184 | 185 | private function encapsulateValueForLike(string $value) : array 186 | { 187 | $values = explode(',', $value); 188 | foreach ($values as $key => $val) { 189 | $values[$key] = '%' . $val . '%'; 190 | } 191 | 192 | return $values; 193 | } 194 | 195 | public function getConditions() :array 196 | { 197 | return $this->conditions; 198 | } 199 | 200 | public function getParameters() :array 201 | { 202 | return $this->parameters; 203 | } 204 | 205 | public function getInnerJoin() :array 206 | { 207 | return $this->join->getInnerJoin(); 208 | } 209 | } -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Queries/Join.php: -------------------------------------------------------------------------------- 1 | parser = new StringParser(); 33 | $this->entityName = $entityName; 34 | $this->entityAlias = $entityAlias; 35 | $this->manager = $manager; 36 | $this->innerJoin = []; 37 | $this->leftJoin = []; 38 | } 39 | 40 | /** 41 | * @param String $relation Nome della relazione semplice (groups.name) o con embedded (_embedded.groups.name) 42 | * @return $this 43 | */ 44 | public function join(String $relation, $logicOperator = self::AND_OPERATOR_LOGIC) 45 | { 46 | $relation = explode('|', $relation)[0]; 47 | $relations = [$relation]; 48 | 49 | if (strstr($relation, '_embedded.')) { 50 | $embeddedFields = explode('.', $relation); 51 | $this->parser->camelize($embeddedFields[1]); 52 | 53 | // elimino l'ultimo elemento che dovrebbe essere il nome del campo 54 | unset($embeddedFields[count($embeddedFields) - 1]); 55 | 56 | // elimino il primo elemento _embedded 57 | unset($embeddedFields[0]); 58 | 59 | $relations = $embeddedFields; 60 | } 61 | 62 | $entityName = $this->entityName; 63 | $entityAlias = $this->entityAlias; 64 | 65 | foreach ($relations as $relation) { 66 | $relation = $this->parser->camelize($relation); 67 | $relationEntityAlias = 'table_' . $relation; 68 | 69 | $metadata = $this->manager->getClassMetadata($entityName); 70 | 71 | if ($metadata->hasAssociation($relation)) { 72 | $association = $metadata->getAssociationMapping($relation); 73 | 74 | $fieldName = $this->parser->camelize($association['fieldName']); 75 | 76 | if ($this->noExistsJoin($relationEntityAlias, $relation)) { 77 | if ($logicOperator === self::AND_OPERATOR_LOGIC) { 78 | $param = []; 79 | $param['field'] = $entityAlias . "." . $fieldName; 80 | $param['relation'] = $relationEntityAlias; 81 | $this->innerJoin[] = $param; 82 | } elseif ($logicOperator === self::OR_OPERATOR_LOGIC) { 83 | $param = []; 84 | $param['field'] = $entityAlias . "." . $fieldName; 85 | $param['relation'] = $relationEntityAlias; 86 | $this->leftJoin[] = $param; 87 | } else { 88 | throw new \Exception('Missing Logic operator'); 89 | } 90 | 91 | $this->storeJoin($relationEntityAlias, $relation); 92 | } 93 | 94 | $entityName = $association['targetEntity']; 95 | $entityAlias = $relationEntityAlias; 96 | } 97 | 98 | $this->setRelationEntityAlias($relationEntityAlias); 99 | } 100 | 101 | return $this; 102 | } 103 | 104 | private function storeJoin($prevEntityAlias, $currentEntityAlias) 105 | { 106 | $needle = $prevEntityAlias . '_' . $currentEntityAlias; 107 | $this->joins[$needle] = $needle; 108 | } 109 | 110 | private function noExistsJoin($prevEntityAlias, $currentEntityAlias) 111 | { 112 | if (null === $this->joins) { 113 | $this->joins = []; 114 | } 115 | 116 | $needle = $prevEntityAlias . '_' . $currentEntityAlias; 117 | 118 | return !in_array($needle, $this->joins); 119 | } 120 | 121 | public function getInnerJoin() :array 122 | { 123 | return $this->innerJoin; 124 | } 125 | 126 | public function getLeftJoin() :array 127 | { 128 | return $this->leftJoin; 129 | } 130 | 131 | private function setRelationEntityAlias(string $relationEntityAlias) 132 | { 133 | $this->relationEntityAlias = $relationEntityAlias; 134 | } 135 | 136 | public function getRelationEntityAlias() 137 | { 138 | return $this->relationEntityAlias; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Queries/Objects/FilterObject.php: -------------------------------------------------------------------------------- 1 | setRawFilter($rawFilter); 28 | 29 | $explodedRawFilter = explode('|', $rawFilter); 30 | if (!isset($explodedRawFilter[self::OPERATOR])) { 31 | $explodedRawFilter[self::OPERATOR] = Dictionary::DEFAULT_OPERATOR; 32 | } 33 | 34 | $fieldName = $explodedRawFilter[self::FIELD]; 35 | $parser = new StringParser(); 36 | $this->fieldName = $parser->camelize($fieldName); 37 | 38 | $this->operatorName = $explodedRawFilter[self::OPERATOR]; 39 | 40 | $position = 0; 41 | if (isset($explodedRawFilter[self::POSITION])) { 42 | $position = $explodedRawFilter[self::POSITION]; 43 | } 44 | 45 | $this->position = $position; 46 | } 47 | 48 | public static function fromRawFilter(string $filter) : FilterObject 49 | { 50 | return new self($filter); 51 | } 52 | 53 | public function getFieldName() : string 54 | { 55 | return $this->fieldName; 56 | } 57 | 58 | public function getOperatorName() : string 59 | { 60 | return $this->operatorName; 61 | } 62 | 63 | public function isListType() : bool 64 | { 65 | return in_array( 66 | $this->getOperatorName(), 67 | $listOperators = ['list', 'nlist'] 68 | ); 69 | } 70 | 71 | public function isFieldEqualityType() : bool 72 | { 73 | return $this->getOperatorName() == 'field_eq'; 74 | } 75 | 76 | public function getOperatorMeta() : string 77 | { 78 | return Dictionary::getOperators()[$this->getOperatorName()]['meta']; 79 | } 80 | 81 | public function haveOperatorSubstitutionPattern() : bool 82 | { 83 | $operator = Dictionary::getOperators()[$this->getOperatorName()]; 84 | 85 | return isset($operator['substitution_pattern']); 86 | } 87 | 88 | public function getOperatorsSubstitutionPattern() : string 89 | { 90 | $operator = Dictionary::getOperators()[$this->getOperatorName()]; 91 | 92 | return $operator['substitution_pattern']; 93 | } 94 | 95 | public function setRawFilter(string $rawFilter) 96 | { 97 | $this->rawFilter = $rawFilter; 98 | } 99 | 100 | public function getRawFilter() : string 101 | { 102 | return $this->rawFilter; 103 | } 104 | 105 | public function getOperator() 106 | { 107 | return $this->operatorName; 108 | } 109 | 110 | public function isNullType() : bool 111 | { 112 | return $this->getOperatorName() === 'isnull' || $this->getOperatorName() === 'isnotnull'; 113 | } 114 | 115 | public function isListContainsType() : bool 116 | { 117 | return $this->getOperatorName() === 'listcontains'; 118 | } 119 | 120 | public function getPosition() 121 | { 122 | return $this->position; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Queries/Objects/Value.php: -------------------------------------------------------------------------------- 1 | filter = $filter; 14 | } 15 | 16 | public function getFilter() 17 | { 18 | if ($this->camesFromAdditionalFilters()) { 19 | return $this->filter[$this->getOperator()][0]; 20 | } 21 | 22 | return $this->filter; 23 | } 24 | 25 | public function getValues() 26 | { 27 | return $this->filter[$this->getOperator()]; 28 | } 29 | 30 | public function getOperator() 31 | { 32 | return key($this->filter); 33 | } 34 | 35 | public static function fromFilter($filter) 36 | { 37 | return new self($filter); 38 | } 39 | 40 | public function camesFromQueryString() 41 | { 42 | return is_string($this->filter); 43 | } 44 | 45 | public function camesFromAdditionalFilters() 46 | { 47 | return !$this->camesFromQueryString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Queries/Options/QueryOptionsBuilder.php: -------------------------------------------------------------------------------- 1 | entityAlias = $entityAlias; 16 | } 17 | 18 | public function getEntityAlias() 19 | { 20 | return $this->entityAlias; 21 | } 22 | 23 | public function fromRequest(Request $request = null) 24 | { 25 | $this->ensureEntityAliasIsDefined(); 26 | 27 | $requestAttributes = []; 28 | foreach ($request->attributes->all() as $attributeName => $attributeValue) { 29 | $requestAttributes[$attributeName] = $request->attributes->get( 30 | $attributeName, 31 | $attributeValue); 32 | } 33 | 34 | $filters = $request->query->get('filtering', []); 35 | $orFilters = $request->query->get('filtering_or', []); 36 | $sorting = $request->query->get('sorting', []); 37 | $printing = $request->query->get('printing', []); 38 | $rel = $request->query->get('rel', ''); 39 | $page = $request->query->get('page', ''); 40 | $select = $request->query->get('select', $this->getEntityAlias()); 41 | $filtering = $request->query->get('filtering', ''); 42 | $limit = $request->query->get('limit', ''); 43 | 44 | $filterOrCorrected = []; 45 | 46 | $count = 0; 47 | foreach ($orFilters as $key => $filter) { 48 | if (is_array($filter)) { 49 | foreach ($filter as $keyInternal => $internal) { 50 | $filterOrCorrected[$keyInternal . '|' . $count] = $internal; 51 | $count += 1; 52 | } 53 | } else { 54 | $filterOrCorrected[$key] = $filter; 55 | } 56 | } 57 | 58 | $requestProperties = [ 59 | 'filtering' => $filtering, 60 | 'orFiltering' => $filterOrCorrected, 61 | 'limit' => $limit, 62 | 'page' => $page, 63 | 'filters' => $filters, 64 | 'orFilters' => $filterOrCorrected, 65 | 'sorting' => $sorting, 66 | 'rel' => $rel, 67 | 'printing' => $printing, 68 | 'select' => $select, 69 | ]; 70 | 71 | $options = array_merge( 72 | $requestAttributes, 73 | $requestProperties 74 | ); 75 | 76 | return QueryBuilderOptions::fromArray($options); 77 | } 78 | 79 | public function ensureEntityAliasIsDefined() 80 | { 81 | if (!$this->entityAlias) { 82 | throw new \RuntimeException( 83 | 'Oops! Entity alias is missing' 84 | ); 85 | } 86 | } 87 | 88 | public function buildFromRequestAndCustomFilter(Request $request, $filter) 89 | { 90 | $this->ensureEntityAliasIsDefined(); 91 | 92 | $filters = $request->query->get('filtering', []); 93 | $orFilters = $request->query->get('filtering_or', []); 94 | $sorting = $request->query->get('sorting', []); 95 | $printing = $request->query->get('printing', []); 96 | $rel = $request->query->get('rel', ''); 97 | $page = $request->query->get('page', ''); 98 | $select = $request->query->get('select', $this->getEntityAlias()); 99 | $filtering = $request->query->get('filtering', ''); 100 | $limit = $request->query->get('limit', ''); 101 | $justCount = $request->query->get('justCount', 'false'); 102 | 103 | $this->ensureFilterIsValid($filters); 104 | 105 | $filters = array_merge($filters, $filter); 106 | 107 | $filterOrCorrected = []; 108 | 109 | $count = 0; 110 | foreach ($orFilters as $key => $filterValue) { 111 | if (is_array($filterValue)) { 112 | foreach ($filterValue as $keyInternal => $internal) { 113 | $filterOrCorrected[$keyInternal . '|' . $count] = $internal; 114 | $count += 1; 115 | } 116 | } else { 117 | $filterOrCorrected[$key] = $filterValue; 118 | } 119 | } 120 | 121 | return QueryBuilderOptions::fromArray([ 122 | '_route' => $request->attributes->get('_route'), 123 | '_route_params' => $request->attributes->get('_route_params', []), 124 | 'id' => $request->attributes->get('id'), 125 | 'filtering' => $filtering, 126 | 'limit' => $limit, 127 | 'page' => $page, 128 | 'filters' => $filters, 129 | 'orFilters' => $filterOrCorrected, 130 | 'sorting' => $sorting, 131 | 'rel' => $rel, 132 | 'printing' => $printing, 133 | 'select' => $select, 134 | 'justCount' => $justCount, 135 | ]); 136 | } 137 | 138 | private function ensureFilterIsValid($filters) 139 | { 140 | if (!is_array($filters)) { 141 | throw new InvalidFiltersException( 142 | "Wrong query string exception: " 143 | . var_export($filters, true) . "\n\n" 144 | . "Please check query string should be something like " 145 | . "http://:/?filtering[|]=" 146 | ); 147 | } 148 | } 149 | 150 | public function buildForOrFilter(Request $request, array $orFilter) 151 | { 152 | $this->ensureEntityAliasIsDefined(); 153 | 154 | $filters = $request->query->get('filtering', []); 155 | $orFilters = $request->query->get('filtering_or', []); 156 | $sorting = $request->query->get('sorting', []); 157 | $printing = $request->query->get('printing', []); 158 | $rel = $request->query->get('rel', ''); 159 | $page = $request->query->get('page', ''); 160 | $select = $request->query->get('select', $this->getEntityAlias()); 161 | $filtering = $request->query->get('filtering', ''); 162 | $limit = $request->query->get('limit', ''); 163 | 164 | $orFilters = array_merge($orFilters, $orFilter); 165 | 166 | $filterOrCorrected = []; 167 | 168 | $count = 0; 169 | foreach ($orFilters as $key => $filter) { 170 | if (is_array($filter)) { 171 | foreach ($filter as $keyInternal => $internal) { 172 | $filterOrCorrected[$keyInternal . '|' . $count] = $internal; 173 | $count += 1; 174 | } 175 | } else { 176 | $filterOrCorrected[$key] = $filter; 177 | } 178 | } 179 | 180 | return QueryBuilderOptions::fromArray([ 181 | '_route' => $request->attributes->get('_route'), 182 | '_route_params' => $request->attributes->get('_route_params', []), 183 | 'id' => $request->attributes->get('id'), 184 | 'filtering' => $filtering, 185 | 'limit' => $limit, 186 | 'page' => $page, 187 | 'filters' => $filters, 188 | 'orFilters' => $filterOrCorrected, 189 | 'sorting' => $sorting, 190 | 'rel' => $rel, 191 | 'printing' => $printing, 192 | 'select' => $select, 193 | ]); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Queries/OrFilter.php: -------------------------------------------------------------------------------- 1 | entityAlias = $entityAlias; 28 | $this->fields = $fields; 29 | $this->join = $join; 30 | 31 | $this->conditions = []; 32 | $this->parameters = []; 33 | $this->parser = new StringParser(); 34 | } 35 | 36 | public function createFilter(array $orFilters) 37 | { 38 | foreach ($orFilters as $filter => $value) { 39 | $this->applyFilter( 40 | Objects\FilterObject::fromRawFilter($filter), 41 | $value 42 | ); 43 | } 44 | } 45 | 46 | private function applyFilter(Objects\FilterObject $filterObject, $value) 47 | { 48 | $position = $filterObject->getPosition(); 49 | 50 | if (!array_key_exists($position, $this->conditions)) { 51 | $this->conditions[$position] = ''; 52 | } 53 | 54 | $whereCondition = $this->entityAlias . '.' . $filterObject->getFieldName() . ' ' 55 | . $filterObject->getOperatorMeta(); 56 | 57 | // controllo se il filtro che mi arriva dalla richiesta è una proprietà di questa entità 58 | // esempio per users: filtering[username|contains]=mado 59 | if (in_array($filterObject->getFieldName(), $this->fields)) { 60 | $salt = '_' . random_int(111, 999); 61 | 62 | if ($filterObject->isListType()) { 63 | $whereCondition .= ' (:field_' . $filterObject->getFieldName() . $salt . ')'; 64 | } else if ($filterObject->isFieldEqualityType()) { 65 | $whereCondition .= $this->entityAlias . '.' . $value; 66 | } elseif ($filterObject->isNullType()) { 67 | $whereCondition .= ' '; 68 | } else { 69 | $whereCondition .= ' :field_' . $filterObject->getFieldName() . $salt; 70 | } 71 | 72 | if ('' != $this->conditions[$position]) { 73 | $this->conditions[$position] .= ' OR ' . $whereCondition; 74 | } else { 75 | $this->conditions[$position] = $whereCondition; 76 | } 77 | 78 | if ($filterObject->haveOperatorSubstitutionPattern()) { 79 | if ($filterObject->isListType()) { 80 | $value = explode(',', $value); 81 | } else { 82 | $value = str_replace( 83 | '{string}', 84 | $value, 85 | $filterObject->getOperatorsSubstitutionPattern() 86 | ); 87 | } 88 | } 89 | 90 | if (!$filterObject->isNullType()) { 91 | $this->parameters[] = [ 92 | 'field' => 'field_' . $filterObject->getFieldName() . $salt, 93 | 'value' => $value 94 | ]; 95 | } 96 | } else { 97 | $isNotARelation = 0 !== strpos($filterObject->getFieldName(), 'Embedded.'); 98 | if ($isNotARelation) { 99 | $whereCondition .= ' ' . $this->entityAlias . '.' . $value; 100 | if ('' != $this->conditions[$position]) { 101 | $this->conditions[$position] .= ' OR ' . $whereCondition; 102 | } else { 103 | $this->conditions[$position] = $whereCondition; 104 | } 105 | } 106 | } 107 | 108 | // controllo se il filtro si riferisce ad una relazione dell'entità quindi devo fare dei join 109 | // esempio per users: filtering[_embedded.groups.name|eq]=admin 110 | if (strstr($filterObject->getRawFilter(), '_embedded.')) { 111 | $this->join->join($filterObject->getRawFilter(), self::OR_OPERATOR_LOGIC); 112 | $this->relationEntityAlias = $this->join->getRelationEntityAlias(); 113 | 114 | $embeddedFields = explode('.', $filterObject->getFieldName()); 115 | $embeddableFieldName = $this->parser->camelize($embeddedFields[count($embeddedFields) - 1]); 116 | 117 | $salt = '_' . random_int(111, 999); 118 | 119 | $whereCondition = $this->relationEntityAlias . '.' . $embeddableFieldName . ' ' 120 | . $filterObject->getOperatorMeta(); 121 | 122 | if ($filterObject->isListType()) { 123 | $whereCondition .= ' (:field_' . $embeddableFieldName . $salt . ')'; 124 | } elseif ($filterObject->isNullType()) { 125 | $whereCondition .= ' '; 126 | } else { 127 | $whereCondition .= ' :field_' . $embeddableFieldName . $salt; 128 | } 129 | 130 | if ('' != $this->conditions[$position]) { 131 | $this->conditions[$position] .= ' OR ' . $whereCondition; 132 | } else { 133 | $this->conditions[$position] = $whereCondition; 134 | } 135 | 136 | if ($filterObject->haveOperatorSubstitutionPattern()) { 137 | if ($filterObject->isListType()) { 138 | $value = explode(',', $value); 139 | } else { 140 | $value = str_replace( 141 | '{string}', 142 | $value, 143 | $filterObject->getOperatorsSubstitutionPattern() 144 | ); 145 | } 146 | } 147 | 148 | if (!$filterObject->isNullType()) { 149 | $this->parameters[] = [ 150 | 'field' => 'field_' . $embeddableFieldName . $salt, 151 | 'value' => $value 152 | ]; 153 | } 154 | } 155 | } 156 | 157 | public function getConditions() :array 158 | { 159 | return $this->conditions; 160 | } 161 | 162 | public function getParameters() :array 163 | { 164 | return $this->parameters; 165 | } 166 | 167 | public function getLeftJoin() :array 168 | { 169 | return $this->join->getLeftJoin(); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Queries/QueryBuilderFactory.php: -------------------------------------------------------------------------------- 1 | getValueAvailableFilters()); 48 | } 49 | 50 | public function getValueAvailableFilters() 51 | { 52 | return Dictionary::getOperators(); 53 | } 54 | 55 | public function setFields(array $fields = []) 56 | { 57 | $this->fields = $fields; 58 | 59 | return $this; 60 | } 61 | 62 | public function getFields() 63 | { 64 | if (null === $this->fields) { 65 | throw new \RuntimeException( 66 | 'Oops! Fields are not defined' 67 | ); 68 | } 69 | 70 | return $this->fields; 71 | } 72 | 73 | /** @since version 2.2 */ 74 | public function setAndFilters(array $andFilters = []) 75 | { 76 | $this->andFilters = $andFilters; 77 | 78 | return $this; 79 | } 80 | 81 | public function setOrFilters(array $orFilters = []) 82 | { 83 | $this->orFilters = $orFilters; 84 | 85 | return $this; 86 | } 87 | 88 | public function setSorting(array $sorting = []) 89 | { 90 | $this->sorting = $sorting; 91 | 92 | return $this; 93 | } 94 | 95 | public function getAndFilters() 96 | { 97 | return $this->andFilters; 98 | } 99 | 100 | public function getOrFilters() 101 | { 102 | return $this->orFilters; 103 | } 104 | 105 | /** 106 | * @param String $relation Nome della relazione semplice (groups.name) o con embedded (_embedded.groups.name) 107 | * @return $this 108 | */ 109 | public function join(String $relation, $logicOperator = self::AND_OPERATOR_LOGIC) 110 | { 111 | $this->joinFactory->join($relation, $logicOperator); 112 | 113 | $innerJoins = $this->joinFactory->getInnerJoin(); 114 | $leftJoins = $this->joinFactory->getLeftJoin(); 115 | 116 | foreach ($innerJoins as $join) { 117 | if (!$this->joinAlreadyDone($join)) { 118 | $this->storeJoin($join); 119 | $this->qBuilder->innerJoin($join['field'], $join['relation']); 120 | } 121 | } 122 | 123 | foreach ($leftJoins as $join) { 124 | if (!$this->joinAlreadyDone($join)) { 125 | $this->storeJoin($join); 126 | $this->qBuilder->leftJoin($join['field'], $join['relation']); 127 | } 128 | } 129 | } 130 | 131 | private function joinAlreadyDone($join) :bool 132 | { 133 | $needle = $join['field'] . '_' . $join['relation']; 134 | if (in_array($needle, $this->joins)) { 135 | return true; 136 | } 137 | 138 | return false; 139 | } 140 | 141 | private function storeJoin($join) 142 | { 143 | $needle = $join['field'] . '_' . $join['relation']; 144 | $this->joins[] = $needle; 145 | } 146 | 147 | public function filter() 148 | { 149 | if (null === $this->andFilters && null === $this->orFilters) { 150 | throw new Exceptions\MissingFiltersException(); 151 | } 152 | 153 | if (!$this->fields) { 154 | throw new Exceptions\MissingFieldsException(); 155 | } 156 | 157 | if (null !== $this->andFilters) { 158 | $andFilterFactory = new AndFilter($this->entityAlias, $this->fields, $this->joinFactory); 159 | $andFilterFactory->createFilter($this->andFilters); 160 | 161 | $conditions = $andFilterFactory->getConditions(); 162 | $parameters = $andFilterFactory->getParameters(); 163 | $innerJoins = $andFilterFactory->getInnerJoin(); 164 | 165 | foreach($conditions as $condition) { 166 | $this->qBuilder->andWhere($condition); 167 | } 168 | 169 | foreach($parameters as $parameter) { 170 | $this->qBuilder->setParameter($parameter['field'], $parameter['value']); 171 | } 172 | 173 | foreach ($innerJoins as $join) { 174 | if (!$this->joinAlreadyDone($join)) { 175 | $this->storeJoin($join); 176 | $this->qBuilder->innerJoin($join['field'], $join['relation']); 177 | } 178 | } 179 | } 180 | 181 | if (null !== $this->orFilters) { 182 | $orFilterFactory = new OrFilter($this->entityAlias, $this->fields, $this->joinFactory); 183 | $orFilterFactory->createFilter($this->orFilters); 184 | 185 | $conditions = $orFilterFactory->getConditions(); 186 | $parameters = $orFilterFactory->getParameters(); 187 | $leftJoins = $orFilterFactory->getLeftJoin(); 188 | 189 | foreach ($conditions as $condition) { 190 | if ($condition !== '') { 191 | $this->qBuilder->andWhere($condition); 192 | 193 | foreach ($parameters as $parameter) { 194 | $this->qBuilder->setParameter($parameter['field'], $parameter['value']); 195 | } 196 | 197 | foreach ($leftJoins as $join) { 198 | if (!$this->joinAlreadyDone($join)) { 199 | $this->storeJoin($join); 200 | $this->qBuilder->leftJoin($join['field'], $join['relation']); 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | return $this; 208 | } 209 | 210 | public function sort() 211 | { 212 | if (!$this->fields) { 213 | throw new \RuntimeException( 214 | 'Oops! Fields are not defined' 215 | ); 216 | } 217 | 218 | if (null === $this->sorting) { 219 | throw new \RuntimeException( 220 | 'Oops! Sorting is not defined' 221 | ); 222 | } 223 | 224 | foreach ($this->sorting as $sort => $val) { 225 | $val = strtolower($val); 226 | 227 | $fieldName = $this->parser->camelize($sort); 228 | 229 | if (in_array($fieldName, $this->fields)) { 230 | $direction = ($val === self::DIRECTION_AZ) ? self::DIRECTION_AZ : self::DIRECTION_ZA; 231 | $this->ensureQueryBuilderIsDefined(); 232 | $this->qBuilder->addOrderBy($this->entityAlias . '.' . $fieldName, $direction); 233 | } 234 | 235 | if (strstr($sort, '_embedded.')) { 236 | $this->join($sort); 237 | $relationEntityAlias = $this->joinFactory->getRelationEntityAlias(); 238 | 239 | $embeddedFields = explode('.', $sort); 240 | $fieldName = $this->parser->camelize($embeddedFields[count($embeddedFields) - 1]); 241 | $direction = ($val === self::DIRECTION_AZ) ? self::DIRECTION_AZ : self::DIRECTION_ZA; 242 | 243 | $this->qBuilder->addOrderBy($relationEntityAlias . '.' . $fieldName, $direction); 244 | } 245 | 246 | } 247 | 248 | return $this; 249 | } 250 | 251 | public function getQueryBuilder() :QueryBuilder 252 | { 253 | if (!$this->qBuilder) { 254 | throw new UnInitializedQueryBuilderException(); 255 | } 256 | 257 | return $this->qBuilder; 258 | } 259 | 260 | public function setRel(array $rel) 261 | { 262 | $this->rel = $rel; 263 | 264 | return $this; 265 | } 266 | 267 | public function getRel() : array 268 | { 269 | return $this->rel; 270 | } 271 | 272 | public function addRel($relation) 273 | { 274 | array_push($this->rel, $relation); 275 | } 276 | 277 | public function setPrinting($printing) 278 | { 279 | $this->printing = $printing; 280 | 281 | return $this; 282 | } 283 | 284 | public function getPrinting() 285 | { 286 | return $this->printing; 287 | } 288 | 289 | public function setPage(int $page) 290 | { 291 | $this->page = $page; 292 | 293 | return $this; 294 | } 295 | 296 | public function getPage() :int 297 | { 298 | return $this->page; 299 | } 300 | 301 | public function setPageLength($pageLength) 302 | { 303 | $this->pageLength = $pageLength; 304 | 305 | return $this; 306 | } 307 | 308 | public function getPageLength() 309 | { 310 | return $this->pageLength; 311 | } 312 | 313 | public function setSelect($select) : QueryBuilderFactory 314 | { 315 | $this->select = $select; 316 | 317 | return $this; 318 | } 319 | 320 | public function getSelect() 321 | { 322 | return $this->select; 323 | } 324 | 325 | public function getEntityManager() : EntityManager 326 | { 327 | return $this->manager; 328 | } 329 | 330 | public function ensureQueryBuilderIsDefined() 331 | { 332 | if (!$this->qBuilder) { 333 | throw new \RuntimeException( 334 | 'Oops! QueryBuilder was never initialized. ' 335 | . "\n" . 'QueryBuilderFactory::createQueryBuilder()' 336 | . "\n" . 'QueryBuilderFactory::createSelectAndGroupBy()' 337 | ); 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Queries/QueryBuilderOptions.php: -------------------------------------------------------------------------------- 1 | options = $options; 12 | } 13 | 14 | public static function fromArray(array $options) 15 | { 16 | return new self($options); 17 | } 18 | 19 | public function get($option, $defaultValue = null) 20 | { 21 | $this->validateOption($option, $defaultValue); 22 | 23 | if ( 24 | !isset($this->options[$option]) 25 | || empty($this->options[$option]) 26 | ) { 27 | return $defaultValue; 28 | } 29 | 30 | return $this->options[$option]; 31 | } 32 | 33 | public function getAndFilters() 34 | { 35 | return $this->get('filters', []); 36 | } 37 | 38 | public function getOrFilters() 39 | { 40 | return $this->get('orFilters', []); 41 | } 42 | 43 | public function getSorting() 44 | { 45 | return $this->get('sorting', []); 46 | } 47 | 48 | public function getRel() 49 | { 50 | return $this->get('rel', []); 51 | } 52 | 53 | public function getPrinting() 54 | { 55 | return $this->get('printing', []); 56 | } 57 | 58 | public function getSelect() 59 | { 60 | return $this->get('select'); 61 | } 62 | 63 | public function validateOption($option, $defaultValue) 64 | { 65 | $optionIsDefinedNegativeAndNotNull = ( 66 | !isset($this->options[$option]) 67 | || $this->options[$option] < 0 68 | ) && $defaultValue == null; 69 | 70 | if ('limit' == $option && $optionIsDefinedNegativeAndNotNull) { 71 | $this->options[$option] = PHP_INT_MAX; 72 | } 73 | } 74 | 75 | public function requireJustCount() 76 | { 77 | return isset($this->options['justCount']) 78 | && $this->options['justCount'] === 'true'; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Repositories/BaseRepository.php: -------------------------------------------------------------------------------- 1 | metadata = new MetaDataAdapter(); 47 | $this->metadata->setClassMetadata($this->getClassMetadata()); 48 | $this->metadata->setEntityName($this->getEntityName()); 49 | 50 | $this->queryBuilderFactory = new QueryBuilderFactory($this->getEntityManager()); 51 | 52 | $this->queryOptionBuilder = new QueryOptionsBuilder(); 53 | $entityAlias = $this->metadata->getEntityAlias(); 54 | $this->queryOptionBuilder->setEntityAlias($entityAlias); 55 | } 56 | 57 | public function initFromQueryBuilderOptions(QueryBuilderOptions $options) 58 | { 59 | $this->queryBuilderFactory->createQueryBuilder( 60 | $this->getEntityName(), 61 | $this->metadata->getEntityAlias() 62 | ); 63 | 64 | $this->queryBuilderFactory->loadMetadataAndOptions( 65 | $this->metadata, 66 | $options 67 | ); 68 | } 69 | 70 | public function getQueryBuilderFactory() 71 | { 72 | $this->initFromQueryBuilderOptions($this->queryOptions); 73 | 74 | return $this->queryBuilderFactory; 75 | } 76 | 77 | public function useResultCache($bool) 78 | { 79 | $this->useResultCache = $bool; 80 | } 81 | 82 | public function setRequest(Request $request) 83 | { 84 | return $this->setQueryOptionsFromRequest($request); 85 | } 86 | 87 | public function setRequestWithFilter(Request $request, $filter) 88 | { 89 | return $this->setQueryOptionsFromRequestWithCustomFilter($request, $filter); 90 | } 91 | 92 | public function setRequestWithOrFilter(Request $request, $orFilter) 93 | { 94 | return $this->setQueryOptionsFromRequestWithCustomOrFilter($request, $orFilter); 95 | } 96 | 97 | public function setQueryOptions(QueryBuilderOptions $options) 98 | { 99 | $this->queryOptions = $options; 100 | } 101 | 102 | public function setQueryOptionsFromRequest(Request $request = null) 103 | { 104 | $this->queryOptions = $this->queryOptionBuilder->fromRequest($request); 105 | 106 | return $this; 107 | } 108 | 109 | public function setQueryOptionsFromRequestWithCustomFilter(Request $request = null, $filter) 110 | { 111 | $this->queryOptions = $this->queryOptionBuilder->buildFromRequestAndCustomFilter($request, $filter); 112 | 113 | return $this; 114 | } 115 | 116 | public function setQueryOptionsFromRequestWithCustomOrFilter(Request $request = null, $orFilter) 117 | { 118 | $this->queryOptions = $this->queryOptionBuilder->buildForOrFilter($request); 119 | 120 | return $this; 121 | } 122 | 123 | public function getRequest() 124 | { 125 | return $this->request; 126 | } 127 | 128 | public function setRouteName($routeName = '') 129 | { 130 | $this->routeName = $routeName; 131 | return $this; 132 | } 133 | 134 | public function findAllNoPaginated() 135 | { 136 | $queryBuilderFactory = $this->getQueryBuilderFactory() 137 | ->filter() 138 | ->sort(); 139 | 140 | $doctrineQueryBuilder = $queryBuilderFactory->getQueryBuilder(); 141 | 142 | return $doctrineQueryBuilder->getQuery()->getResult(); 143 | } 144 | 145 | public function findAllPaginated() 146 | { 147 | $this->initFromQueryBuilderOptions($this->queryOptions); 148 | 149 | $this->queryBuilderFactory->filter(); 150 | $this->queryBuilderFactory->sort(); 151 | 152 | $queryBuilder = $this->queryBuilderFactory->getQueryBuilder(); 153 | 154 | if ($this->queryOptions->requireJustCount()) { 155 | $metadata = $this->metadata; 156 | $rootEntityAlias = $metadata->getEntityAlias(); 157 | $select = 'count(' . $rootEntityAlias . '.id)'; 158 | 159 | $count = $queryBuilder 160 | ->select($select) 161 | ->getQuery() 162 | ->getSingleScalarResult(); 163 | 164 | return [ 'count' => $count ]; 165 | } 166 | 167 | $this->lastQuery = $queryBuilder->getQuery()->getSql(); 168 | $this->lastParameters = $queryBuilder->getQuery()->getParameters(); 169 | 170 | return $this->paginateResults($queryBuilder); 171 | } 172 | 173 | public function getLastQuery() 174 | { 175 | return [ 176 | 'query' => $this->lastQuery, 177 | 'params' => $this->lastParameters, 178 | ]; 179 | } 180 | 181 | protected function paginateResults(QueryBuilder $queryBuilder) 182 | { 183 | $ormAdapter = new DoctrineORMAdapter($queryBuilder); 184 | $pagerfantaBuilder = new PagerfantaBuilder(new PagerfantaFactory(), $ormAdapter); 185 | $pager = new Pager(); 186 | return $pager->paginateResults( 187 | $this->queryOptions, 188 | $ormAdapter, 189 | $pagerfantaBuilder, 190 | $this->routeName, 191 | $this->useResultCache 192 | ); 193 | } 194 | 195 | protected function getCurrentEntityAlias() : string 196 | { 197 | return $this->currentEntityAlias; 198 | } 199 | 200 | protected function setCurrentEntityAlias(string $currentEntityAlias) 201 | { 202 | $this->currentEntityAlias = $currentEntityAlias; 203 | } 204 | 205 | protected function getEmbeddedFields() : array 206 | { 207 | return $this->embeddedFields; 208 | } 209 | 210 | protected function setEmbeddedFields(array $embeddedFields) 211 | { 212 | $this->embeddedFields = $embeddedFields; 213 | } 214 | 215 | public function getEntityAlias() : string 216 | { 217 | return $this->metadata->getEntityAlias(); 218 | } 219 | 220 | protected function relationship($queryBuilder) 221 | { 222 | return $queryBuilder; 223 | } 224 | 225 | public function getQueryBuilderFactoryWithoutInitialization() 226 | { 227 | return $this->queryBuilderFactory; 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | mado.query_builder_factory: 4 | class: Mado\QueryBundle\Queries\QueryBuilderFactory 5 | arguments: 6 | - "@doctrine.orm.entity_manager" 7 | 8 | mado.sherlock: 9 | class: Mado\QueryBundle\Component\Sherlock\Sherlock 10 | arguments: 11 | - "@doctrine.orm.entity_manager" 12 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Services/AdditionalFilterExtractor.php: -------------------------------------------------------------------------------- 1 | additionalFilters = $user->getAdditionalFilters(); 14 | } 15 | 16 | public static function fromUser(AdditionalFilterable $user) 17 | { 18 | return new self($user); 19 | } 20 | 21 | public function getFilters(string $filterName) 22 | { 23 | if (isset($this->additionalFilters[$filterName])) { 24 | return $this->additionalFilters[$filterName]; 25 | } 26 | 27 | return ''; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Services/Pager.php: -------------------------------------------------------------------------------- 1 | setRouter(new Router()); 22 | } 23 | 24 | public function setRouter(Router $router) 25 | { 26 | $this->router = $router; 27 | } 28 | 29 | public function paginateResults ( 30 | QueryBuilderOptions $queryOptions, 31 | DoctrineORMAdapter $ormAdapter, 32 | PagerfantaBuilder $pagerfantaBuilder, 33 | $routeName, 34 | $useResultCache 35 | ) { 36 | $limit = $queryOptions->get('limit', self::DEFAULT_LIMIT); 37 | $page = $queryOptions->get('page', self::DEFAULT_PAGE); 38 | 39 | $query = $ormAdapter->getQuery(); 40 | if (isset($useResultCache) && $useResultCache) { 41 | $query->useResultCache(true, self::DEFAULT_LIFETIME); 42 | } 43 | 44 | $route = $this->router->createRouter($queryOptions, $routeName); 45 | 46 | return $pagerfantaBuilder->createRepresentation($route, $limit, $page); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Services/Router.php: -------------------------------------------------------------------------------- 1 | get('_route_params')) { 16 | $routeParams = array_keys($queryOptions->get('_route_params')); 17 | } 18 | 19 | $list = array_merge([ 20 | 'filtering', 21 | 'limit', 22 | 'page', 23 | 'sorting', 24 | ], $routeParams); 25 | 26 | foreach ($list as $itemKey => $itemValue) { 27 | $params[$itemValue] = $queryOptions->get($itemValue); 28 | } 29 | 30 | if (!isset($routeName)) { 31 | $routeName = $queryOptions->get('_route'); 32 | } 33 | 34 | return new Route($routeName, $params); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Mado/QueryBundle/Services/StringParser.php: -------------------------------------------------------------------------------- 1 | exploded($string)); 10 | } 11 | 12 | private function exploded(string $string) 13 | { 14 | return explode('_', $string); 15 | } 16 | 17 | public function tokenize(string $string, int $position) 18 | { 19 | return $this->exploded($string)[$position]; 20 | } 21 | 22 | public function camelize($string) 23 | { 24 | $camelized = $this->tokenize($string, 0); 25 | 26 | for ($i = 1; $i < $this->numberOfTokens($string); $i++) { 27 | $camelized .= ucfirst($this->tokenize($string, $i)); 28 | } 29 | 30 | return $camelized; 31 | } 32 | 33 | public static function dotNotationFor(string $class) 34 | { 35 | $dottedFullyQualifiedClassName = str_replace( '\\', '.', $class); 36 | return strtolower($dottedFullyQualifiedClassName); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Component/Meta/DijkstraTest.php: -------------------------------------------------------------------------------- 1 | samepleJson = [ 11 | "AppBundle\\Entity\\a" => [ 12 | "relations" => [ 13 | "item" => "AppBundle\\Entity\\Fizz", 14 | ] 15 | ], 16 | "AppBundle\\Entity\\mood" => [ 17 | "relations" => [ 18 | "item" => "AppBundle\\Entity\\b", 19 | ] 20 | ], 21 | "AppBundle\\Entity\\Fizz" => [ 22 | "relations" => [ 23 | "item" => "AppBundle\\Entity\\mood", 24 | "item" => "AppBundle\\Entity\\b", 25 | ] 26 | ], 27 | "AppBundle\\Entity\\b" => [ 28 | "relations" => [ 29 | "item" => "AppBundle\\Entity\\Fizz", 30 | "icdsatem" => "AppBundle\\Entity\\a", 31 | ] 32 | ], 33 | ]; 34 | 35 | $dijkstra = new Dijkstra(); 36 | $dijkstra->setMap($this->samepleJson); 37 | 38 | $paths = $dijkstra->shortestPaths( 39 | 'AppBundle\\Entity\\a', 40 | 'AppBundle\\Entity\\b' 41 | ); 42 | 43 | $this->assertEquals( 44 | [[ 45 | 'AppBundle\\Entity\\a', 46 | 'AppBundle\\Entity\\Fizz', 47 | 'AppBundle\\Entity\\b', 48 | ]], 49 | $paths 50 | ); 51 | } 52 | 53 | public function testFindAlternativePaths() 54 | { 55 | $this->samepleJson = [ 56 | "AppBundle\\Entity\\a" => [ 57 | "relations" => [ 58 | "item" => "AppBundle\\Entity\\Fizz", 59 | ] 60 | ], 61 | "AppBundle\\Entity\\mood" => [ 62 | "relations" => [ 63 | "item" => "AppBundle\\Entity\\b", 64 | ] 65 | ], 66 | "AppBundle\\Entity\\Fizz" => [ 67 | "relations" => [ 68 | "item" => "AppBundle\\Entity\\mood", 69 | "item" => "AppBundle\\Entity\\b", 70 | ] 71 | ], 72 | "AppBundle\\Entity\\b" => [ 73 | "relations" => [ 74 | "item" => "AppBundle\\Entity\\Fizz", 75 | "icdsatem" => "AppBundle\\Entity\\a", 76 | ] 77 | ], 78 | ]; 79 | 80 | $dijkstra = new Dijkstra(); 81 | $dijkstra->setMap($this->samepleJson); 82 | 83 | $paths = $dijkstra->shortestPaths( 84 | 'AppBundle\\Entity\\a', 85 | 'AppBundle\\Entity\\b', 86 | $excluded = [ 87 | 'AppBundle\\Entity\\mood', 88 | ] 89 | ); 90 | 91 | $this->assertEquals( 92 | [ 93 | 'AppBundle\\Entity\\a', 94 | 'AppBundle\\Entity\\Fizz', 95 | 'AppBundle\\Entity\\b', 96 | ], 97 | $paths[0] 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Component/Meta/DijkstraWalkerTest.php: -------------------------------------------------------------------------------- 1 | mapper = $this 14 | ->getMockBuilder('Mado\QueryBundle\Component\Meta\DataMapper') 15 | ->disableOriginalConstructor() 16 | ->getMock(); 17 | 18 | $this->dijkstra = $this 19 | ->getMockBuilder('Mado\QueryBundle\Component\Meta\Dijkstra') 20 | ->disableOriginalConstructor() 21 | ->getMock(); 22 | 23 | $this->walker = new DijkstraWalker( 24 | $this->mapper, 25 | $this->dijkstra 26 | ); 27 | 28 | $this->walker->getPath(); 29 | } 30 | 31 | public function testBuildPathUsingDijkstra() 32 | { 33 | $this->mapper = $this 34 | ->getMockBuilder('Mado\QueryBundle\Component\Meta\DataMapper') 35 | ->disableOriginalConstructor() 36 | ->getMock(); 37 | $this->mapper->expects($this->once()) 38 | ->method('getMap') 39 | ->will($this->returnValue($laMappa = [ 40 | 'start' => [ 41 | 'relations' => [ 42 | 'fine' => 'end', 43 | ] 44 | ], 45 | 'end' => [ 46 | 'relations' => [ 47 | 'inizio' => 'start', 48 | ] 49 | ] 50 | ])); 51 | 52 | $this->dijkstra = $this 53 | ->getMockBuilder('Mado\QueryBundle\Component\Meta\Dijkstra') 54 | ->disableOriginalConstructor() 55 | ->getMock(); 56 | $this->dijkstra->expects($this->once()) 57 | ->method('setMap') 58 | ->with($laMappa); 59 | $this->dijkstra->expects($this->once()) 60 | ->method('shortestPaths') 61 | ->will($this->returnValue([[ 62 | 'start', 63 | 'end' 64 | ]])); 65 | 66 | $this->walker = new DijkstraWalker( 67 | $this->mapper, 68 | $this->dijkstra 69 | ); 70 | 71 | $this->walker->buildPathBetween('start', 'end'); 72 | 73 | $pathFound = $this->walker->getPath(); 74 | 75 | $this->assertEquals( 76 | '_embedded.fine', 77 | $pathFound 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Component/Meta/MapBuilderTest.php: -------------------------------------------------------------------------------- 1 | factory = $this 13 | ->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataFactory') 14 | ->disableOriginalConstructor() 15 | ->getMock(); 16 | $this->factory->expects($this->once()) 17 | ->method('getAllMetadata') 18 | ->will($this->returnValue($expectedMap)); 19 | 20 | $this->manager = $this 21 | ->getMockBuilder('Doctrine\ORM\EntityManager') 22 | ->disableOriginalConstructor() 23 | ->getMock(); 24 | $this->manager->expects($this->once()) 25 | ->method('getMetadataFactory') 26 | ->will($this->returnValue($this->factory)); 27 | 28 | $mapBuilder = new MapBuilder( 29 | $this->manager 30 | ); 31 | 32 | $map = $mapBuilder->getMap(); 33 | 34 | $this->assertEquals( 35 | $expectedMap, 36 | $map 37 | ); 38 | } 39 | 40 | public function testBuildMapWithParentAndRelationEntities() 41 | { 42 | $expectedMap = []; 43 | 44 | $this->factory = $this 45 | ->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataFactory') 46 | ->disableOriginalConstructor() 47 | ->getMock(); 48 | $this->factory->expects($this->once()) 49 | ->method('getAllMetadata') 50 | ->will($this->returnValue(function () { 51 | return [ 52 | 'SomeBundle\Entity\ParentEntity' => [ 53 | 'relations' => [ 54 | 'relName' => 'SomeOtherBundle\Entity\ChildEntity', 55 | ] 56 | ] 57 | ]; 58 | })); 59 | 60 | $this->manager = $this 61 | ->getMockBuilder('Doctrine\ORM\EntityManager') 62 | ->disableOriginalConstructor() 63 | ->getMock(); 64 | $this->manager->expects($this->once()) 65 | ->method('getMetadataFactory') 66 | ->will($this->returnValue($this->factory)); 67 | 68 | $mapBuilder = new MapBuilder( 69 | $this->manager 70 | ); 71 | 72 | $map = $mapBuilder->getMap(); 73 | 74 | $this->assertEquals( 75 | $expectedMap, 76 | $map 77 | ); 78 | } 79 | 80 | public function testUsingCacheDoctirneIsNotCalled() 81 | { 82 | $expectedMap = [ 83 | 'root' => [ 84 | 'relations' => [ 85 | 'rel_name' => 'Entity' 86 | ] 87 | ] 88 | ]; 89 | 90 | $this->manager = $this 91 | ->getMockBuilder('Doctrine\ORM\EntityManager') 92 | ->disableOriginalConstructor() 93 | ->getMock(); 94 | 95 | $this->manager->expects($this->never()) 96 | ->method('getMetadataFactory'); 97 | 98 | $mapBuilder = new MapBuilder($this->manager); 99 | 100 | $mapBuilder->forceCache($expectedMap); 101 | 102 | $map = $mapBuilder->getMap(); 103 | 104 | $this->assertEquals( 105 | $expectedMap, 106 | $map 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Objects/FilterTest.php: -------------------------------------------------------------------------------- 1 | ['list' => [2, 3, 4]], 12 | 'path' => 'path.to', 13 | ]); 14 | 15 | $this->assertEquals('list',$f->getOperator()); 16 | $this->assertEquals('path.to',$f->getPath()); 17 | $this->assertEquals('2,3,4',$f->getIds()); 18 | $this->assertEquals('path.to.id|list',$f->getFieldAndOperator()); 19 | $this->assertEquals('path.to.id',$f->getField()); 20 | } 21 | 22 | public function testAllowPathChange() 23 | { 24 | $old = Filter::box([ 25 | 'ids' => ['list' => [2, 3, 4]], 26 | 'path' => 'path.to', 27 | ]); 28 | 29 | $new = $old->withPath('new.path'); 30 | 31 | $this->assertEquals('new.path',$new->getPath()); 32 | $this->assertEquals('new.path.id|list',$new->getFieldAndOperator()); 33 | $this->assertEquals('new.path.id',$new->getField()); 34 | } 35 | 36 | public function testAllowFullPathChange() 37 | { 38 | $old = Filter::box([ 39 | 'ids' => ['list' => [2, 3, 4]], 40 | 'path' => 'path.to', 41 | ]); 42 | 43 | $new = $old->withFullPath('new.path|foo'); 44 | 45 | $this->assertEquals('new.path',$new->getPath()); 46 | $this->assertEquals('new.path|foo',$new->getFieldAndOperator()); 47 | $this->assertEquals('new.path',$new->getField()); 48 | } 49 | 50 | public function testPathChangeEmpty() 51 | { 52 | $old = Filter::box([ 53 | 'ids' => ['list' => [2, 3, 4]], 54 | 'path' => 'path.to', 55 | ]); 56 | 57 | $new = $old->withPath(''); 58 | 59 | $this->assertEquals('',$new->getPath()); 60 | $this->assertEquals('id|list',$new->getFieldAndOperator()); 61 | $this->assertEquals('id',$new->getField()); 62 | } 63 | 64 | public function testBuildFromQueryString() 65 | { 66 | $queryStringFilter = Filter::fromQueryStringFilter([ 67 | '_embedded.attributes.alfanumerico12|eq' => 'GTK' 68 | ]); 69 | 70 | $this->assertEquals('_embedded.attributes.alfanumerico12|eq',$queryStringFilter->getFieldAndOperator()); 71 | $this->assertEquals('_embedded.attributes.alfanumerico12',$queryStringFilter->getField()); 72 | $this->assertEquals('GTK',$queryStringFilter->getValue()); 73 | $this->assertEquals('eq',$queryStringFilter->getOperator()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Objects/HalResponseTest.php: -------------------------------------------------------------------------------- 1 | 42, 12 | ]); 13 | 14 | $this->assertEquals(42, $response->total()); 15 | } 16 | 17 | public function testExtractTotalFromRawJson() 18 | { 19 | $response = HalResponse::fromJson('{"total":"42"}'); 20 | 21 | $this->assertEquals(42, $response->total()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Objects/MetaDataAdapterTest.php: -------------------------------------------------------------------------------- 1 | adapter = new MetaDataAdapter(); 11 | } 12 | 13 | public function testProvideEntityAliasFromEntityName() 14 | { 15 | $this->adapter->setEntityName('Foo\\Bar'); 16 | $this->assertEquals('b', $this->adapter->getEntityAlias()); 17 | } 18 | 19 | public function testProvideEntityFieldsFromMetadata() 20 | { 21 | $metadata = new \stdClass(); 22 | $metadata->fieldMappings = [ 23 | 'foo' => 'fizz', 24 | 'bar' => 'buzz', 25 | ]; 26 | 27 | $this->adapter->setClassMetadata($metadata); 28 | $this->assertEquals([ 29 | 'foo', 30 | 'bar', 31 | ], $this->adapter->getFields()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Objects/PagerfantaBuilderTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('Hateoas\Representation\PaginatedRepresentation') 17 | ->disableOriginalConstructor() 18 | ->getMock(); 19 | 20 | $this->pagerfantaFactory = $this 21 | ->getMockBuilder('Hateoas\Representation\Factory\PagerfantaFactory') 22 | ->disableOriginalConstructor() 23 | ->getMock(); 24 | 25 | $this->pagerfantaFactory 26 | ->expects($this->exactly(1)) 27 | ->method('createRepresentation') 28 | ->willReturn($paginatedRepresentation); 29 | 30 | $this->ormAdapter = $this 31 | ->getMockBuilder('Pagerfanta\Adapter\DoctrineORMAdapter') 32 | ->disableOriginalConstructor() 33 | ->getMock(); 34 | 35 | $this->pagerfantaBuilder = new \Mado\QueryBundle\Objects\PagerfantaBuilder( 36 | $this->pagerfantaFactory, 37 | $this->ormAdapter 38 | ); 39 | 40 | $route = $this 41 | ->getMockBuilder('Hateoas\Configuration\Route') 42 | ->disableOriginalConstructor() 43 | ->getMock(); 44 | 45 | $this->pagerfantaBuilder->createRepresentation($route, random_int(0, 9999), random_int(0, 9999)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Objects/QueryBuilderFactoryTest.php: -------------------------------------------------------------------------------- 1 | manager = \Doctrine\ORM\EntityManager::create(array( 14 | 'driver' => 'pdo_sqlite', 15 | 'path' => __DIR__ . '/../../data/db.sqlite', 16 | ), 17 | \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration( 18 | array(__DIR__), 19 | true 20 | )); 21 | } 22 | 23 | public function testExposeEntityManager() 24 | { 25 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 26 | $this->assertSame( 27 | $this->manager, 28 | $queryBuilderFactory->getEntityManager() 29 | ); 30 | } 31 | 32 | public function testProvideOneSingleResult() 33 | { 34 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 35 | $queryBuilderFactory->setFields([ 'id' ]); 36 | $queryBuilderFactory->setAndFilters([ 'id|eq' => 33 ]); 37 | $queryBuilderFactory->createQueryBuilder(MySimpleEntity::class, 'e'); 38 | $queryBuilderFactory->filter(); 39 | 40 | $doctrineQueryBuilder = $queryBuilderFactory->getQueryBuilder(); 41 | $doctrineQueryBuilder->setMaxResults(1); 42 | 43 | $this->assertEquals( 44 | "SELECT m0_.id AS id_0 FROM MySimpleEntity m0_ WHERE m0_.id = ? LIMIT 1", 45 | $doctrineQueryBuilder->getQuery()->getSql() 46 | ); 47 | } 48 | 49 | public function testSampleQueryMakedWithQueryBuilderFactory() 50 | { 51 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 52 | $queryBuilderFactory->setFields([ 'id' ]); 53 | $queryBuilderFactory->setAndFilters([ 'id|eq' => 33 ]); 54 | $queryBuilderFactory->createQueryBuilder(MySimpleEntity::class, 'e'); 55 | $queryBuilderFactory->filter(); 56 | 57 | $this->assertEquals( 58 | "SELECT m0_.id AS id_0 FROM MySimpleEntity m0_ WHERE m0_.id = ?", 59 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 60 | ); 61 | 62 | $this->assertContains( 63 | "SELECT e FROM Mado\QueryBundle\Tests\Objects\MySimpleEntity e WHERE e.id = :field_id", 64 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getDql() 65 | ); 66 | } 67 | 68 | public function testFilterWithListType() 69 | { 70 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 71 | $queryBuilderFactory->setFields([ 'id' ]); 72 | $queryBuilderFactory->setAndFilters([ 'id|list' => '42, 33' ]); 73 | $queryBuilderFactory->createQueryBuilder(MySimpleEntity::class, 'e'); 74 | $queryBuilderFactory->filter(); 75 | 76 | $this->assertEquals( 77 | "SELECT m0_.id AS id_0 FROM MySimpleEntity m0_ WHERE m0_.id IN (?)", 78 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 79 | ); 80 | } 81 | 82 | public function testFilterWithListTypeOnEmbeddedField() 83 | { 84 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 85 | $queryBuilderFactory->setFields([ 'id' ]); 86 | $queryBuilderFactory->setAndFilters([ '_embedded.group.id|list' => '42, 33']); 87 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 88 | $queryBuilderFactory->filter(); 89 | 90 | $this->assertEquals( 91 | "SELECT " 92 | . "u0_.id AS id_0, " 93 | . "u0_.username AS username_1, " 94 | . "u0_.group_id AS group_id_2 " 95 | . "FROM User u0_ " 96 | . "INNER JOIN Group g1_ ON u0_.group_id = g1_.id " 97 | . "WHERE g1_.id IN (?)", 98 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 99 | ); 100 | } 101 | 102 | public function testFilterWithContainsOperator() 103 | { 104 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 105 | $queryBuilderFactory->setFields([ 'username', 'group' ]); 106 | $queryBuilderFactory->setAndFilters([ 'username|contains' => 'orar' ]); 107 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 108 | $queryBuilderFactory->filter(); 109 | 110 | $this->assertEquals( 111 | "SELECT " 112 | . "u0_.id AS id_0, " 113 | . "u0_.username AS username_1, " 114 | . "u0_.group_id AS group_id_2 " 115 | . "FROM User u0_ " 116 | . "WHERE u0_.username LIKE ?", 117 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 118 | ); 119 | } 120 | 121 | public function testFilterWithFieldEqualityOperator() 122 | { 123 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 124 | $queryBuilderFactory->setFields([ 'username', 'group' ]); 125 | $queryBuilderFactory->setAndFilters([ 'username|field_eq' => 'group' ]); 126 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 127 | $queryBuilderFactory->filter(); 128 | 129 | $this->assertEquals( 130 | "SELECT " 131 | . "u0_.id AS id_0, " 132 | . "u0_.username AS username_1, " 133 | . "u0_.group_id AS group_id_2 " 134 | . "FROM User u0_ " 135 | . "WHERE u0_.username = u0_.group_id", 136 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 137 | ); 138 | } 139 | 140 | public function testOneToManyQueryMakedHandly() 141 | { 142 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 143 | $queryBuilderFactory->setFields([ 'id' ]); 144 | $queryBuilderFactory->setRel([ 'group' ]); 145 | $queryBuilderFactory->setAndFilters([ 146 | '_embedded.group.name|contains|1' => 'ad', 147 | '_embedded.group.name|contains|2' => 'ns', 148 | '_embedded.group.name|contains|3' => 'dm', 149 | '_embedded.group.name|contains|4' => 'mi', 150 | ]); 151 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 152 | $queryBuilderFactory->filter(); 153 | 154 | $this->assertEquals( 155 | "SELECT" . 156 | " u0_.id AS id_0," . 157 | " u0_.username AS username_1," . 158 | " u0_.group_id AS group_id_2 " . 159 | "FROM User u0_ " . 160 | "INNER JOIN Group g1_ ON u0_.group_id = g1_.id " . 161 | "WHERE g1_.name LIKE ? " . 162 | "AND g1_.name LIKE ? " . 163 | "AND g1_.name LIKE ? " . 164 | "AND g1_.name LIKE ?", 165 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 166 | ); 167 | } 168 | 169 | public function testFiltersMustContainsAlsoFieldEquality() 170 | { 171 | $factory = new QueryBuilderFactory($this->manager); 172 | 173 | $validFilters = [ 174 | 'eq', 175 | 'neq', 176 | 'gt', 177 | 'gte', 178 | 'lt', 179 | 'lte', 180 | 'startswith', 181 | 'contains', 182 | 'notcontains', 183 | 'endswith', 184 | 'list', 185 | 'nlist', 186 | 'field_eq', 187 | 'isnull', 188 | 'isnotnull', 189 | 'listcontains', 190 | ]; 191 | 192 | $this->assertEquals( 193 | $validFilters, 194 | $factory->getAvailableFilters() 195 | ); 196 | } 197 | 198 | public function testGetFields() 199 | { 200 | $fields = ['id']; 201 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 202 | $queryBuilderFactory->setFields($fields); 203 | $fieldsReturned = $queryBuilderFactory->getFields(); 204 | 205 | $this->assertEquals($fields, $fieldsReturned); 206 | } 207 | 208 | /** 209 | * @expectedException \RuntimeException 210 | */ 211 | public function testGetFieldsThrowExceptionIfNull() 212 | { 213 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 214 | $queryBuilderFactory->getFields(); 215 | } 216 | 217 | public function testSetOrFilters() 218 | { 219 | $filters = ['id']; 220 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 221 | $queryBuilderFactory->setOrFilters($filters); 222 | 223 | $this->assertAttributeEquals($filters, 'orFilters', $queryBuilderFactory); 224 | } 225 | 226 | public function testGetOrFilters() 227 | { 228 | $filters = ['id']; 229 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 230 | $queryBuilderFactory->setOrFilters($filters); 231 | $fieldsReturned = $queryBuilderFactory->getOrFilters(); 232 | 233 | $this->assertEquals($filters, $fieldsReturned); 234 | } 235 | 236 | public function testSetSorting() 237 | { 238 | $sorting = ['id']; 239 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 240 | $queryBuilderFactory->setSorting($sorting); 241 | 242 | $this->assertAttributeEquals($sorting, 'sorting', $queryBuilderFactory); 243 | } 244 | 245 | public function testGetFilters() 246 | { 247 | $filters = ['id']; 248 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 249 | $queryBuilderFactory->setAndFilters($filters); 250 | $fieldsReturned = $queryBuilderFactory->getAndFilters(); 251 | 252 | $this->assertEquals($filters, $fieldsReturned); 253 | } 254 | 255 | /** 256 | * @expectedException Mado\QueryBundle\Component\Meta\Exceptions\UnInitializedQueryBuilderException 257 | */ 258 | public function testGetQueryBuilderThrowExceptionIfNull() 259 | { 260 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 261 | $queryBuilderFactory->getQueryBuilder(); 262 | } 263 | 264 | public function testGetRel() 265 | { 266 | $rel = 'foo'; 267 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 268 | $queryBuilderFactory->setRel([$rel]); 269 | $relReturned = $queryBuilderFactory->getRel(); 270 | 271 | $this->assertEquals([$rel], $relReturned); 272 | } 273 | 274 | public function testSetPrinting() 275 | { 276 | $print = 'foo'; 277 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 278 | $queryBuilderFactory->setPrinting($print); 279 | 280 | $this->assertAttributeEquals($print, 'printing', $queryBuilderFactory); 281 | } 282 | 283 | public function testGetPrinting() 284 | { 285 | $print = 'foo'; 286 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 287 | $queryBuilderFactory->setPrinting($print); 288 | $printReturned = $queryBuilderFactory->getPrinting(); 289 | 290 | $this->assertEquals($print, $printReturned); 291 | } 292 | 293 | public function testSetPage() 294 | { 295 | $page = 100; 296 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 297 | $queryBuilderFactory->setPage($page); 298 | 299 | $this->assertAttributeEquals($page, 'page', $queryBuilderFactory); 300 | } 301 | 302 | public function testGetPage() 303 | { 304 | $page = 100; 305 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 306 | $queryBuilderFactory->setPage($page); 307 | $pageReturned = $queryBuilderFactory->getPage(); 308 | 309 | $this->assertEquals($page, $pageReturned); 310 | } 311 | 312 | public function testSetPageLength() 313 | { 314 | $pageLength = 100; 315 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 316 | $queryBuilderFactory->setPageLength($pageLength); 317 | 318 | $this->assertAttributeEquals($pageLength, 'pageLength', $queryBuilderFactory); 319 | } 320 | 321 | public function testGetPageLength() 322 | { 323 | $pageLength = 100; 324 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 325 | $queryBuilderFactory->setPageLength($pageLength); 326 | $pageLengthReturned = $queryBuilderFactory->getPageLength(); 327 | 328 | $this->assertEquals($pageLength, $pageLengthReturned); 329 | } 330 | 331 | public function testSetSelect() 332 | { 333 | $select = 'foo'; 334 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 335 | $queryBuilderFactory->setSelect($select); 336 | 337 | $this->assertAttributeEquals($select, 'select', $queryBuilderFactory); 338 | } 339 | 340 | public function testGetSelect() 341 | { 342 | $select = 'foo'; 343 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 344 | $queryBuilderFactory->setSelect($select); 345 | $selectReturned = $queryBuilderFactory->getSelect(); 346 | 347 | $this->assertEquals($select, $selectReturned); 348 | } 349 | 350 | public function testListOnEmbeddedOrFilter() 351 | { 352 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 353 | $queryBuilderFactory->setFields([ 'id' ]); 354 | $queryBuilderFactory->setRel([ 'group' ]); 355 | $queryBuilderFactory->setOrFilters([ 356 | '_embedded.group.id|list' => '1,2,3', 357 | ]); 358 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 359 | $queryBuilderFactory->filter(); 360 | 361 | $this->assertEquals( 362 | "SELECT " 363 | . "u0_.id AS id_0, " 364 | . "u0_.username AS username_1, " 365 | . "u0_.group_id AS group_id_2 " 366 | . "FROM User u0_ " 367 | . "LEFT JOIN Group g1_ ON u0_.group_id = g1_.id " 368 | . "WHERE g1_.id IN (?)", 369 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 370 | ); 371 | } 372 | 373 | public function testFieldsArentInEntity() 374 | { 375 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 376 | $queryBuilderFactory->setFields([ 'id' ]); 377 | $queryBuilderFactory->setRel([ 'group' ]); 378 | $queryBuilderFactory->setOrFilters([ 379 | 'id|list' => '1,2,3', 380 | ]); 381 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 382 | $queryBuilderFactory->filter(); 383 | 384 | $this->assertEquals( 385 | "SELECT" . 386 | " u0_.id AS id_0," . 387 | " u0_.username AS username_1," . 388 | " u0_.group_id AS group_id_2 " . 389 | "FROM User u0_ " . 390 | "WHERE u0_.id IN (?)", 391 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 392 | ); 393 | } 394 | 395 | public function testCheckFieldsEqualitiWithOrOperator() 396 | { 397 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 398 | $queryBuilderFactory->setFields([ 'username', 'group' ]); 399 | $queryBuilderFactory->setOrFilters([ 'username|field_eq' => 'group' ]); 400 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 401 | $queryBuilderFactory->filter(); 402 | 403 | $this->assertEquals( 404 | "SELECT " 405 | . "u0_.id AS id_0, " 406 | . "u0_.username AS username_1, " 407 | . "u0_.group_id AS group_id_2 " 408 | . "FROM User u0_ " 409 | . "WHERE u0_.username = u0_.group_id", 410 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 411 | ); 412 | } 413 | 414 | public function testCheckFieldsGreaterThan() 415 | { 416 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 417 | $queryBuilderFactory->setFields([ 'id', 'group' ]); 418 | $queryBuilderFactory->setOrFilters([ 'id|gte' => '42' ]); 419 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 420 | $queryBuilderFactory->filter(); 421 | 422 | $this->assertEquals( 423 | "SELECT " 424 | . "u0_.id AS id_0, " 425 | . "u0_.username AS username_1, " 426 | . "u0_.group_id AS group_id_2 " 427 | . "FROM User u0_ " 428 | . "WHERE u0_.id >= ?", 429 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 430 | ); 431 | } 432 | 433 | public function testFilteringOrWithContains() 434 | { 435 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 436 | $queryBuilderFactory->setFields([ 'username', 'group' ]); 437 | $queryBuilderFactory->setOrFilters([ 'username|contains' => 'sorar' ]); 438 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 439 | $queryBuilderFactory->filter(); 440 | 441 | $this->assertEquals( 442 | "SELECT " 443 | . "u0_.id AS id_0, " 444 | . "u0_.username AS username_1, " 445 | . "u0_.group_id AS group_id_2 " 446 | . "FROM User u0_ " 447 | . "WHERE u0_.username LIKE ?", 448 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 449 | ); 450 | } 451 | 452 | public function testCanBuildQueriesUsingOrOperator() 453 | { 454 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 455 | $queryBuilderFactory->setFields([ 'id' ]); 456 | $queryBuilderFactory->setRel([ 'group' ]); 457 | $queryBuilderFactory->setOrFilters([ 458 | '_embedded.group.name|contains|1' => 'ad', 459 | '_embedded.group.description|contains|1' => 'ns', 460 | '_embedded.group.name|contains|2' => 'dm', 461 | '_embedded.group.description|contains|2' => 'mi', 462 | ]); 463 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 464 | $queryBuilderFactory->filter(); 465 | 466 | $this->assertEquals( 467 | "SELECT" . 468 | " u0_.id AS id_0," . 469 | " u0_.username AS username_1," . 470 | " u0_.group_id AS group_id_2 " . 471 | "FROM User u0_ " . 472 | "LEFT JOIN Group g1_ ON u0_.group_id = g1_.id " . 473 | "WHERE (g1_.name LIKE ? OR g1_.description LIKE ?) AND " . 474 | "(g1_.name LIKE ? OR g1_.description LIKE ?)", 475 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 476 | ); 477 | } 478 | 479 | /** @expectedException \Mado\QueryBundle\Exceptions\MissingFiltersException */ 480 | public function testThrowMissingFiltersExceptionsWheneverFiltersAreMissing() 481 | { 482 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 483 | $queryBuilderFactory->filter(); 484 | } 485 | 486 | /** @expectedException \Mado\QueryBundle\Exceptions\MissingFieldsException */ 487 | public function testThrowExceptionWheneverFieldsWereNotDefined() 488 | { 489 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 490 | $queryBuilderFactory->setAndFilters([ 'foo|eq' => 'bar' ]); 491 | $queryBuilderFactory->filter(); 492 | } 493 | 494 | /** 495 | * @expectedException \RuntimeException 496 | * @expectedExceptionMessage Oops! Fields are not defined 497 | */ 498 | public function testCantSortWithoutFields() 499 | { 500 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 501 | $queryBuilderFactory->sort(); 502 | } 503 | 504 | /** 505 | * @expectedException \RuntimeException 506 | * @expectedExceptionMessage Oops! Sorting is not defined 507 | */ 508 | public function testThrowExceptionWheneverSortIsRequestedWithoutSorting() 509 | { 510 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 511 | $queryBuilderFactory->setFields(['foo', 'bar']); 512 | $queryBuilderFactory->sort(); 513 | } 514 | 515 | /** 516 | * @expectedException \RuntimeException 517 | * @expectedExceptionMessage Oops! QueryBuilder was never initialized 518 | */ 519 | public function testThrowExceptionWhenQueryBuilderIsNotInitialized() 520 | { 521 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 522 | $queryBuilderFactory->setFields(['foo', 'bar']); 523 | $queryBuilderFactory->setSorting(['foo' => 'bar']); 524 | $queryBuilderFactory->sort(); 525 | } 526 | 527 | public function testApplySortingJustForEntityFields() 528 | { 529 | $this->queryBuilder = $this 530 | ->getMockBuilder('Doctrine\ORM\QueryBuilder') 531 | ->disableOriginalConstructor() 532 | ->getMock(); 533 | $this->queryBuilder->expects($this->once()) 534 | ->method('select') 535 | ->with('alias') 536 | ->willReturn($this->queryBuilder); 537 | $this->queryBuilder->expects($this->once()) 538 | ->method('from') 539 | ->with('EntityName') 540 | ->willReturn($this->queryBuilder); 541 | 542 | $this->manager = $this 543 | ->getMockBuilder('Doctrine\ORM\EntityManager') 544 | ->disableOriginalConstructor() 545 | ->getMock(); 546 | $this->manager->expects($this->once()) 547 | ->method('createQueryBuilder') 548 | ->willReturn($this->queryBuilder); 549 | 550 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 551 | $queryBuilderFactory->setFields(['foo', 'bar']); 552 | $queryBuilderFactory->setSorting(['foo' => 'bar']); 553 | $queryBuilderFactory->createQueryBuilder('EntityName', 'alias'); 554 | $queryBuilderFactory->sort(); 555 | } 556 | 557 | public function testApplySortingAlsoOnRelationsField() 558 | { 559 | $this->queryBuilder = $this 560 | ->getMockBuilder('Doctrine\ORM\QueryBuilder') 561 | ->disableOriginalConstructor() 562 | ->getMock(); 563 | $this->queryBuilder->expects($this->once()) 564 | ->method('select') 565 | ->with('alias') 566 | ->willReturn($this->queryBuilder); 567 | $this->queryBuilder->expects($this->once()) 568 | ->method('from') 569 | ->with('EntityName') 570 | ->willReturn($this->queryBuilder); 571 | $this->queryBuilder->expects($this->once()) 572 | ->method('innerJoin') 573 | ->with('alias.ciao', 'table_fizz'); 574 | 575 | $this->metadata = $this 576 | ->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata') 577 | ->disableOriginalConstructor() 578 | ->getMock(); 579 | $this->metadata->expects($this->once()) 580 | ->method('hasAssociation') 581 | ->with('fizz') 582 | ->willReturn(true); 583 | $this->metadata->expects($this->once()) 584 | ->method('getAssociationMapping') 585 | ->with('fizz') 586 | ->willReturn([ 587 | 'fieldName' => 'ciao', 588 | 'targetEntity' => 'someEntityName', 589 | ]); 590 | 591 | $this->manager = $this 592 | ->getMockBuilder('Doctrine\ORM\EntityManager') 593 | ->disableOriginalConstructor() 594 | ->getMock(); 595 | $this->manager->expects($this->once()) 596 | ->method('createQueryBuilder') 597 | ->willReturn($this->queryBuilder); 598 | $this->manager->expects($this->once()) 599 | ->method('getClassMetadata') 600 | ->with('EntityName') 601 | ->willReturn($this->metadata); 602 | 603 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 604 | $queryBuilderFactory->setFields(['foo', 'bar']); 605 | $queryBuilderFactory->setSorting(['_embedded.fizz.buzz' => 'bar']); 606 | $queryBuilderFactory->createQueryBuilder('EntityName', 'alias'); 607 | $queryBuilderFactory->sort(); 608 | } 609 | 610 | public function testGetAvailableFilters() 611 | { 612 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 613 | 614 | $expectedFilters = Dictionary::getOperators(); 615 | 616 | $availableFilters = $queryBuilderFactory->getValueAvailableFilters(); 617 | 618 | $this->assertEquals($expectedFilters, $availableFilters); 619 | } 620 | 621 | public function testAcceptRelationsToAdd() 622 | { 623 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 624 | $queryBuilderFactory->setFields([ 'id' ]); 625 | $queryBuilderFactory->setRel([ 'group' ]); 626 | $queryBuilderFactory->addRel('foo'); 627 | 628 | $this->assertEquals( 629 | ['group', 'foo'], 630 | $queryBuilderFactory->getRel() 631 | ); 632 | } 633 | 634 | public function testAndFilterUseInnerJoin() 635 | { 636 | $expectJoinType = 'innerJoin'; 637 | 638 | $this->queryBuilder = $this 639 | ->getMockBuilder('Doctrine\ORM\QueryBuilder') 640 | ->disableOriginalConstructor() 641 | ->getMock(); 642 | $this->queryBuilder 643 | ->method('select') 644 | ->willReturn($this->queryBuilder); 645 | $this->queryBuilder 646 | ->method('from') 647 | ->willReturn($this->queryBuilder); 648 | $this->queryBuilder->expects($this->once()) 649 | ->method($expectJoinType) 650 | ->with('alias.baz', 'table_foo'); 651 | 652 | $this->prepareDataForFilter(); 653 | 654 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 655 | $queryBuilderFactory->setFields([ 'id' ]); 656 | $queryBuilderFactory->createQueryBuilder('EntityName', 'alias'); 657 | $queryBuilderFactory->setAndFilters([ '_embedded.foo.baz|eq' => 'bar' ]); 658 | $queryBuilderFactory->filter(); 659 | } 660 | 661 | public function testOrFilterUseLeftJoin() 662 | { 663 | $expectJoinType = 'leftJoin'; 664 | 665 | $this->queryBuilder = $this 666 | ->getMockBuilder('Doctrine\ORM\QueryBuilder') 667 | ->disableOriginalConstructor() 668 | ->getMock(); 669 | $this->queryBuilder 670 | ->method('select') 671 | ->willReturn($this->queryBuilder); 672 | $this->queryBuilder 673 | ->method('from') 674 | ->willReturn($this->queryBuilder); 675 | $this->queryBuilder->expects($this->once()) 676 | ->method($expectJoinType) 677 | ->with('alias.baz', 'table_foo'); 678 | 679 | $this->prepareDataForFilter(); 680 | 681 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 682 | $queryBuilderFactory->setFields([ 'id' ]); 683 | $queryBuilderFactory->createQueryBuilder('EntityName', 'alias'); 684 | $queryBuilderFactory->setOrFilters([ '_embedded.foo.baz|eq' => 'bar' ]); 685 | $queryBuilderFactory->filter(); 686 | } 687 | 688 | private function prepareDataForFilter() 689 | { 690 | $this->metadata = $this 691 | ->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata') 692 | ->disableOriginalConstructor() 693 | ->getMock(); 694 | $this->metadata 695 | ->method('hasAssociation') 696 | ->with('foo') 697 | ->willReturn(true); 698 | $this->metadata 699 | ->method('getAssociationMapping') 700 | ->with('foo') 701 | ->willReturn([ 702 | 'fieldName' => 'baz', 703 | 'targetEntity' => 'someEntityName', 704 | ]); 705 | 706 | $this->manager = $this 707 | ->getMockBuilder('Doctrine\ORM\EntityManager') 708 | ->disableOriginalConstructor() 709 | ->getMock(); 710 | $this->manager 711 | ->method('createQueryBuilder') 712 | ->willReturn($this->queryBuilder); 713 | $this->manager 714 | ->method('getClassMetadata') 715 | ->with('EntityName') 716 | ->willReturn($this->metadata); 717 | } 718 | 719 | public function testFilteringWithIsNull() 720 | { 721 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 722 | $queryBuilderFactory->setFields([ 'username', 'group' ]); 723 | $queryBuilderFactory->setAndFilters([ 'username|isnull' => '' ]); 724 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 725 | $queryBuilderFactory->filter(); 726 | 727 | $this->assertEquals( 728 | "SELECT " 729 | . "u0_.id AS id_0, " 730 | . "u0_.username AS username_1, " 731 | . "u0_.group_id AS group_id_2 " 732 | . "FROM User u0_ " 733 | . "WHERE u0_.username IS NULL", 734 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 735 | ); 736 | } 737 | 738 | public function testFilteringOrWithIsNull() 739 | { 740 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 741 | $queryBuilderFactory->setFields([ 'username', 'group' ]); 742 | $queryBuilderFactory->setOrFilters([ 'username|isnull' => '' ]); 743 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 744 | $queryBuilderFactory->filter(); 745 | 746 | $this->assertEquals( 747 | "SELECT " 748 | . "u0_.id AS id_0, " 749 | . "u0_.username AS username_1, " 750 | . "u0_.group_id AS group_id_2 " 751 | . "FROM User u0_ " 752 | . "WHERE u0_.username IS NULL", 753 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 754 | ); 755 | } 756 | 757 | public function testFilteringAndWithIsNullIntoEmbedded() 758 | { 759 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 760 | $queryBuilderFactory->setFields([ 'id' ]); 761 | $queryBuilderFactory->setRel([ 'group' ]); 762 | $queryBuilderFactory->setAndFilters([ 763 | '_embedded.group.name|isnull' => '' 764 | ]); 765 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 766 | $queryBuilderFactory->filter(); 767 | 768 | $this->assertEquals( 769 | "SELECT" . 770 | " u0_.id AS id_0," . 771 | " u0_.username AS username_1," . 772 | " u0_.group_id AS group_id_2 " . 773 | "FROM User u0_ " . 774 | "INNER JOIN Group g1_ ON u0_.group_id = g1_.id " . 775 | "WHERE g1_.name IS NULL", 776 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 777 | ); 778 | } 779 | 780 | public function testFilteringOrWithIsNullIntoEmbedded() 781 | { 782 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 783 | $queryBuilderFactory->setFields([ 'id' ]); 784 | $queryBuilderFactory->setRel([ 'group' ]); 785 | $queryBuilderFactory->setOrFilters([ 786 | '_embedded.group.name|isnull' => '' 787 | ]); 788 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 789 | $queryBuilderFactory->filter(); 790 | 791 | $this->assertEquals( 792 | "SELECT" . 793 | " u0_.id AS id_0," . 794 | " u0_.username AS username_1," . 795 | " u0_.group_id AS group_id_2 " . 796 | "FROM User u0_ " . 797 | "LEFT JOIN Group g1_ ON u0_.group_id = g1_.id " . 798 | "WHERE g1_.name IS NULL", 799 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 800 | ); 801 | } 802 | 803 | public function testFilteringWithIsNotNull() 804 | { 805 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 806 | $queryBuilderFactory->setFields([ 'username', 'group' ]); 807 | $queryBuilderFactory->setAndFilters([ 'username|isnotnull' => '' ]); 808 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 809 | $queryBuilderFactory->filter(); 810 | 811 | $this->assertEquals( 812 | "SELECT " 813 | . "u0_.id AS id_0, " 814 | . "u0_.username AS username_1, " 815 | . "u0_.group_id AS group_id_2 " 816 | . "FROM User u0_ " 817 | . "WHERE u0_.username IS NOT NULL", 818 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 819 | ); 820 | } 821 | 822 | public function testFilteringOrWithIsNotNull() 823 | { 824 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 825 | $queryBuilderFactory->setFields([ 'username', 'group' ]); 826 | $queryBuilderFactory->setOrFilters([ 'username|isnotnull' => '' ]); 827 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 828 | $queryBuilderFactory->filter(); 829 | 830 | $this->assertEquals( 831 | "SELECT " 832 | . "u0_.id AS id_0, " 833 | . "u0_.username AS username_1, " 834 | . "u0_.group_id AS group_id_2 " 835 | . "FROM User u0_ " 836 | . "WHERE u0_.username IS NOT NULL", 837 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 838 | ); 839 | } 840 | 841 | public function testFilteringAndWithIsNotNullIntoEmbedded() 842 | { 843 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 844 | $queryBuilderFactory->setFields([ 'id' ]); 845 | $queryBuilderFactory->setRel([ 'group' ]); 846 | $queryBuilderFactory->setAndFilters([ 847 | '_embedded.group.name|isnotnull' => '' 848 | ]); 849 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 850 | $queryBuilderFactory->filter(); 851 | 852 | $this->assertEquals( 853 | "SELECT" . 854 | " u0_.id AS id_0," . 855 | " u0_.username AS username_1," . 856 | " u0_.group_id AS group_id_2 " . 857 | "FROM User u0_ " . 858 | "INNER JOIN Group g1_ ON u0_.group_id = g1_.id " . 859 | "WHERE g1_.name IS NOT NULL", 860 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 861 | ); 862 | } 863 | 864 | public function testFilteringOrWithIsNotNullIntoEmbedded() 865 | { 866 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 867 | $queryBuilderFactory->setFields([ 'id' ]); 868 | $queryBuilderFactory->setRel([ 'group' ]); 869 | $queryBuilderFactory->setOrFilters([ 870 | '_embedded.group.name|isnotnull' => '' 871 | ]); 872 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 873 | $queryBuilderFactory->filter(); 874 | 875 | $this->assertEquals( 876 | "SELECT" . 877 | " u0_.id AS id_0," . 878 | " u0_.username AS username_1," . 879 | " u0_.group_id AS group_id_2 " . 880 | "FROM User u0_ " . 881 | "LEFT JOIN Group g1_ ON u0_.group_id = g1_.id " . 882 | "WHERE g1_.name IS NOT NULL", 883 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 884 | ); 885 | } 886 | 887 | public function testFilteringListContains() 888 | { 889 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 890 | $queryBuilderFactory->setFields([ 'id', 'username' ]); 891 | $queryBuilderFactory->setRel([ 'group' ]); 892 | $queryBuilderFactory->setAndFilters([ 893 | 'username|listcontains' => 'a,b', 894 | '_embedded.group.name|listcontains' => 'c,d' 895 | ]); 896 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 897 | $queryBuilderFactory->filter(); 898 | 899 | $this->assertEquals( 900 | "SELECT u0_.id AS id_0, u0_.username AS username_1, u0_.group_id AS group_id_2" . 901 | " FROM User u0_" . 902 | " INNER JOIN Group g1_ ON u0_.group_id = g1_.id " . 903 | "WHERE ((u0_.username LIKE ? OR u0_.username LIKE ?)) " . 904 | "AND ((g1_.name LIKE ? OR g1_.name LIKE ?))", 905 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 906 | ); 907 | } 908 | 909 | public function testSortingIntoTwoLevelsEmbedded() 910 | { 911 | $queryBuilderFactory = new QueryBuilderFactory($this->manager); 912 | $queryBuilderFactory->setFields([ 'id' ]); 913 | $queryBuilderFactory->setRel([ 'group', 'company' ]); 914 | $queryBuilderFactory->setSorting([ 915 | '_embedded.group.company.id' => 'asc' 916 | ]); 917 | $queryBuilderFactory->createQueryBuilder(User::class, 'e'); 918 | $queryBuilderFactory->sort(); 919 | 920 | $this->assertEquals( 921 | "SELECT" . 922 | " u0_.id AS id_0," . 923 | " u0_.username AS username_1," . 924 | " u0_.group_id AS group_id_2 " . 925 | "FROM User u0_ " . 926 | "INNER JOIN Group g1_ ON u0_.group_id = g1_.id " . 927 | "INNER JOIN Company c2_ ON g1_.company_id = c2_.id " . 928 | "ORDER BY c2_.id ASC", 929 | $queryBuilderFactory->getQueryBuilder()->getQuery()->getSql() 930 | ); 931 | } 932 | } 933 | 934 | /** @Entity() */ 935 | class MySimpleEntity 936 | { 937 | /** @Id @Column(type="integer") */ 938 | private $id; 939 | } 940 | 941 | /** @Entity() */ 942 | class User 943 | { 944 | /** @Id @Column(type="integer") */ 945 | private $id; 946 | /** @Column(type="string") */ 947 | private $username; 948 | /** @ManyToOne(targetEntity="Group", inversedBy="member") */ 949 | private $group; 950 | } 951 | 952 | /** @Entity() */ 953 | class Group 954 | { 955 | /** @Id @Column(type="integer") */ 956 | private $id; 957 | /** @Column(type="string") */ 958 | private $name; 959 | /** @Column(type="string") */ 960 | private $description; 961 | /** @OneToMany(targetEntity="User", mappedBy="member") */ 962 | private $members; 963 | /** @ManyToOne(targetEntity="Company", inversedBy="groups") */ 964 | private $company; 965 | } 966 | 967 | /** @Entity() */ 968 | class Company 969 | { 970 | /** @Id @Column(type="integer") */ 971 | private $id; 972 | /** @Column(type="string") */ 973 | private $name; 974 | /** @OneToMany(targetEntity="Group", mappedBy="company") */ 975 | private $group; 976 | } 977 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Objects/QueryBuilderOptionsTest.php: -------------------------------------------------------------------------------- 1 | 'andFilterValue', 14 | 'orFilters' => 'orFiltersValue', 15 | 'sorting' => 'asc', 16 | 'rel' => 'azione', 17 | 'printing' => 'pppp', 18 | 'select' => 'a,b,c', 19 | ]); 20 | 21 | $this->assertEquals('andFilterValue', $op->getAndFilters()); 22 | $this->assertEquals('orFiltersValue', $op->getOrFilters()); 23 | $this->assertEquals('asc', $op->getSorting()); 24 | $this->assertEquals('azione', $op->getRel()); 25 | $this->assertEquals('pppp', $op->getPrinting()); 26 | $this->assertEquals('a,b,c', $op->getSelect()); 27 | 28 | $this->assertEquals( 29 | $op->get('filters'), 30 | $op->getAndFilters() 31 | ); 32 | 33 | $this->assertNull($op->get('fake')); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Objects/ValueTest.php: -------------------------------------------------------------------------------- 1 | getFilter(); 15 | 16 | $this->assertEquals('foo', $filter); 17 | } 18 | 19 | public function testCamesFromQuerystringWheneverIsComposedByAString() 20 | { 21 | $value = Value::fromFilter('foo'); 22 | 23 | $this->assertSame(true, $value->camesFromQueryString()); 24 | } 25 | 26 | public function testCamesFromAdditionalFiltersWheneverComposedByAnArray() 27 | { 28 | $value = Value::fromFilter(['op' => [1, 2, 3]]); 29 | 30 | $this->assertSame(false, $value->camesFromQueryString()); 31 | } 32 | 33 | public function testDetectOperator() 34 | { 35 | $value = Value::fromFilter(['op' => [1, 2, 3]]); 36 | 37 | $this->assertEquals('op', $value->getOperator()); 38 | } 39 | 40 | public function testDetectIds() 41 | { 42 | $value = Value::fromFilter(['op' => [1, 2, 3]]); 43 | 44 | $this->assertEquals([1, 2, 3], $value->getValues()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Queries/Objects/FilterObjectTest.php: -------------------------------------------------------------------------------- 1 | getOperator(); 15 | $this->assertEquals('bar', $operator); 16 | } 17 | 18 | public function testWheneverOperatorIsntDefinedUseDefaultOperator() 19 | { 20 | $filter = FilterObject::fromRawFilter('foo'); 21 | $operator = $filter->getOperator(); 22 | $this->assertEquals(Dictionary::DEFAULT_OPERATOR, $operator); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Queries/Objects/QueryOptionsBuilderTest.php: -------------------------------------------------------------------------------- 1 | builder = new QueryOptionsBuilder(); 12 | $this->builder->setEntityAlias($alias = 'asdf'); 13 | $this->assertEquals( 14 | $alias, 15 | $this->builder->getEntityAlias() 16 | ); 17 | } 18 | 19 | /** @expectedException \RuntimeException */ 20 | public function testRequireEntityAliasDefinition() 21 | { 22 | $this->builder = new QueryOptionsBuilder(); 23 | $this->builder->ensureEntityAliasIsDefined(); 24 | } 25 | 26 | public function testShouldBuildOptionsFromEmtpyRequest() 27 | { 28 | $this->request = $this 29 | ->getMockBuilder('Symfony\Component\HttpFoundation\Request') 30 | ->disableOriginalConstructor() 31 | ->getMock(); 32 | $this->request->attributes = $this 33 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 34 | ->disableOriginalConstructor() 35 | ->getMock(); 36 | $this->request->attributes->expects($this->once()) 37 | ->method('all') 38 | ->willReturn([]); 39 | $this->request->query = $this 40 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 41 | ->disableOriginalConstructor() 42 | ->getMock(); 43 | 44 | $this->request->query->expects($this->at(0))->method('get')->with('filtering', [])->willReturn([]); 45 | $this->request->query->expects($this->at(1))->method('get')->with('filtering_or', [])->willReturn([]); 46 | $this->request->query->expects($this->at(2))->method('get')->with('sorting', [])->willReturn([]); 47 | $this->request->query->expects($this->at(3))->method('get')->with('printing', [])->willReturn([]); 48 | $this->request->query->expects($this->at(4))->method('get')->with('rel', '')->willReturn([]); 49 | $this->request->query->expects($this->at(5))->method('get')->with('page', '')->willReturn([]); 50 | $this->request->query->expects($this->at(6))->method('get')->with('select', 'asdf')->willReturn([]); 51 | $this->request->query->expects($this->at(7))->method('get')->with('filtering', '')->willReturn([]); 52 | $this->request->query->expects($this->at(8))->method('get')->with('limit', '')->willReturn([]); 53 | 54 | $this->builder = new QueryOptionsBuilder(); 55 | $this->builder->setEntityAlias($alias = 'asdf'); 56 | $options = $this->builder->fromRequest($this->request); 57 | 58 | $this->assertEquals( 59 | QueryBuilderOptions::fromArray([ 60 | 'filtering' => [], 61 | 'orFilters' => [], 62 | 'limit' => [], 63 | 'page' => [], 64 | 'filters' => [], 65 | 'orFiltering' => [], 66 | 'sorting' => [], 67 | 'rel' => [], 68 | 'printing' => [], 69 | 'select' => [], 70 | ]), 71 | $options 72 | ); 73 | } 74 | 75 | public function testFilteringOrIsConvertedInBothOriltersAndOrfiltering() 76 | { 77 | $this->request = $this 78 | ->getMockBuilder('Symfony\Component\HttpFoundation\Request') 79 | ->disableOriginalConstructor() 80 | ->getMock(); 81 | $this->request->attributes = $this 82 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 83 | ->disableOriginalConstructor() 84 | ->getMock(); 85 | $this->request->attributes->expects($this->once()) 86 | ->method('all') 87 | ->willReturn([]); 88 | $this->request->query = $this 89 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 90 | ->disableOriginalConstructor() 91 | ->getMock(); 92 | 93 | $this->request->query->expects($this->at(0))->method('get')->with('filtering', [])->willReturn([]); 94 | $this->request->query->expects($this->at(1))->method('get')->with('filtering_or', [])->willReturn([ 95 | 'foo' => 'bar', 96 | ]); 97 | $this->request->query->expects($this->at(2))->method('get')->with('sorting', [])->willReturn([]); 98 | $this->request->query->expects($this->at(3))->method('get')->with('printing', [])->willReturn([]); 99 | $this->request->query->expects($this->at(4))->method('get')->with('rel', '')->willReturn([]); 100 | $this->request->query->expects($this->at(5))->method('get')->with('page', '')->willReturn([]); 101 | $this->request->query->expects($this->at(6))->method('get')->with('select', 'asdf')->willReturn([]); 102 | $this->request->query->expects($this->at(7))->method('get')->with('filtering', '')->willReturn([]); 103 | $this->request->query->expects($this->at(8))->method('get')->with('limit', '')->willReturn([]); 104 | 105 | $this->builder = new QueryOptionsBuilder(); 106 | $this->builder->setEntityAlias($alias = 'asdf'); 107 | $options = $this->builder->fromRequest($this->request); 108 | 109 | $this->assertEquals( 110 | QueryBuilderOptions::fromArray([ 111 | 'filtering' => [], 112 | 'orFilters' => [ 113 | 'foo' => 'bar', 114 | ], 115 | 'limit' => [], 116 | 'page' => [], 117 | 'filters' => [], 118 | 'orFiltering' => [ 119 | 'foo' => 'bar', 120 | ], 121 | 'sorting' => [], 122 | 'rel' => [], 123 | 'printing' => [], 124 | 'select' => [], 125 | ]), 126 | $options 127 | ); 128 | } 129 | 130 | public function testAdjustOrFilteringWheneverIsAnArray() 131 | { 132 | $this->request = $this 133 | ->getMockBuilder('Symfony\Component\HttpFoundation\Request') 134 | ->disableOriginalConstructor() 135 | ->getMock(); 136 | $this->request->attributes = $this 137 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 138 | ->disableOriginalConstructor() 139 | ->getMock(); 140 | $this->request->attributes->expects($this->once()) 141 | ->method('all') 142 | ->willReturn([]); 143 | $this->request->query = $this 144 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 145 | ->disableOriginalConstructor() 146 | ->getMock(); 147 | 148 | $this->request->query->expects($this->at(0))->method('get')->with('filtering', [])->willReturn([]); 149 | $this->request->query->expects($this->at(1))->method('get')->with('filtering_or', [])->willReturn([ 150 | 'foo' => [ 151 | 'bar', 152 | 'foo', 153 | ] 154 | ]); 155 | $this->request->query->expects($this->at(2))->method('get')->with('sorting', [])->willReturn([]); 156 | $this->request->query->expects($this->at(3))->method('get')->with('printing', [])->willReturn([]); 157 | $this->request->query->expects($this->at(4))->method('get')->with('rel', '')->willReturn([]); 158 | $this->request->query->expects($this->at(5))->method('get')->with('page', '')->willReturn([]); 159 | $this->request->query->expects($this->at(6))->method('get')->with('select', 'asdf')->willReturn([]); 160 | $this->request->query->expects($this->at(7))->method('get')->with('filtering', '')->willReturn([]); 161 | $this->request->query->expects($this->at(8))->method('get')->with('limit', '')->willReturn([]); 162 | 163 | $this->builder = new QueryOptionsBuilder(); 164 | $this->builder->setEntityAlias($alias = 'asdf'); 165 | $options = $this->builder->fromRequest($this->request); 166 | 167 | $this->assertEquals( 168 | QueryBuilderOptions::fromArray([ 169 | 'filtering' => [], 170 | 'orFilters' => [ 171 | '0|0' => 'bar', 172 | '1|1' => 'foo', 173 | ], 174 | 'limit' => [], 175 | 'page' => [], 176 | 'filters' => [], 177 | 'orFiltering' => [ 178 | '0|0' => 'bar', 179 | '1|1' => 'foo', 180 | ], 181 | 'sorting' => [], 182 | 'rel' => [], 183 | 'printing' => [], 184 | 'select' => [], 185 | ]), 186 | $options 187 | ); 188 | } 189 | 190 | public function testShouldBuildOptionsFromEmptyRequestAndCustomFilters() 191 | { 192 | $this->request = $this 193 | ->getMockBuilder('Symfony\Component\HttpFoundation\Request') 194 | ->disableOriginalConstructor() 195 | ->getMock(); 196 | $this->request->attributes = $this 197 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 198 | ->disableOriginalConstructor() 199 | ->getMock(); 200 | //$this->request->attributes->expects($this->once()) 201 | //->method('all') 202 | //->willReturn([]); 203 | $this->request->query = $this 204 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 205 | ->disableOriginalConstructor() 206 | ->getMock(); 207 | 208 | $this->request->query->expects($this->at(0))->method('get')->with('filtering', [])->willReturn([]); 209 | $this->request->query->expects($this->at(1))->method('get')->with('filtering_or', [])->willReturn([]); 210 | $this->request->query->expects($this->at(2))->method('get')->with('sorting', [])->willReturn([]); 211 | $this->request->query->expects($this->at(3))->method('get')->with('printing', [])->willReturn([]); 212 | $this->request->query->expects($this->at(4))->method('get')->with('rel', '')->willReturn([]); 213 | $this->request->query->expects($this->at(5))->method('get')->with('page', '')->willReturn([]); 214 | $this->request->query->expects($this->at(6))->method('get')->with('select', 'asdf')->willReturn([]); 215 | $this->request->query->expects($this->at(7))->method('get')->with('filtering', '')->willReturn([]); 216 | $this->request->query->expects($this->at(8))->method('get')->with('limit', '')->willReturn([]); 217 | 218 | $this->builder = new QueryOptionsBuilder(); 219 | $this->builder->setEntityAlias($alias = 'asdf'); 220 | $options = $this->builder->buildFromRequestAndCustomFilter($this->request, [ 221 | 'custom' => 'filter', 222 | ]); 223 | 224 | $this->assertEquals( 225 | QueryBuilderOptions::fromArray([ 226 | 'filtering' => [], 227 | 'orFilters' => [], 228 | 'limit' => [], 229 | 'page' => [], 230 | 'filters' => [ 231 | 'custom' => 'filter', 232 | ], 233 | '_route_params' => null, 234 | 'sorting' => [], 235 | 'rel' => [], 236 | 'printing' => [], 237 | 'select' => [], 238 | 'justCount' => null, 239 | 'id' => null, 240 | '_route' => null, 241 | ]), 242 | $options 243 | ); 244 | } 245 | 246 | public function testShouldBuildQueryWithOrfiltersAndCustomFilters() 247 | { 248 | $this->request = $this 249 | ->getMockBuilder('Symfony\Component\HttpFoundation\Request') 250 | ->disableOriginalConstructor() 251 | ->getMock(); 252 | $this->request->attributes = $this 253 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 254 | ->disableOriginalConstructor() 255 | ->getMock(); 256 | //$this->request->attributes->expects($this->once()) 257 | //->method('all') 258 | //->willReturn([]); 259 | $this->request->query = $this 260 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 261 | ->disableOriginalConstructor() 262 | ->getMock(); 263 | 264 | $this->request->query->expects($this->at(0))->method('get')->with('filtering', [])->willReturn([]); 265 | $this->request->query->expects($this->at(1))->method('get')->with('filtering_or', [])->willReturn([ 266 | 'foo' => 'bar', 267 | ]); 268 | $this->request->query->expects($this->at(2))->method('get')->with('sorting', [])->willReturn([]); 269 | $this->request->query->expects($this->at(3))->method('get')->with('printing', [])->willReturn([]); 270 | $this->request->query->expects($this->at(4))->method('get')->with('rel', '')->willReturn([]); 271 | $this->request->query->expects($this->at(5))->method('get')->with('page', '')->willReturn([]); 272 | $this->request->query->expects($this->at(6))->method('get')->with('select', 'asdf')->willReturn([]); 273 | $this->request->query->expects($this->at(7))->method('get')->with('filtering', '')->willReturn([]); 274 | $this->request->query->expects($this->at(8))->method('get')->with('limit', '')->willReturn([]); 275 | 276 | $this->builder = new QueryOptionsBuilder(); 277 | $this->builder->setEntityAlias($alias = 'asdf'); 278 | $options = $this->builder->buildFromRequestAndCustomFilter($this->request, [ 279 | 'custom' => 'filter', 280 | ]); 281 | 282 | $this->assertEquals( 283 | QueryBuilderOptions::fromArray([ 284 | 'filtering' => [], 285 | 'orFilters' => [ 286 | 'foo' => 'bar', 287 | ], 288 | 'limit' => [], 289 | 'page' => [], 290 | 'filters' => [ 291 | 'custom' => 'filter', 292 | ], 293 | '_route_params' => null, 294 | 'sorting' => [], 295 | 'rel' => [], 296 | 'printing' => [], 297 | 'select' => [], 298 | 'justCount' => null, 299 | 'id' => null, 300 | '_route' => null, 301 | ]), 302 | $options 303 | ); 304 | } 305 | 306 | public function testBuildOptionsFromRequestWithOrFiltersAsArrayAndCustomFilters() 307 | { 308 | $this->request = $this 309 | ->getMockBuilder('Symfony\Component\HttpFoundation\Request') 310 | ->disableOriginalConstructor() 311 | ->getMock(); 312 | $this->request->attributes = $this 313 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 314 | ->disableOriginalConstructor() 315 | ->getMock(); 316 | //$this->request->attributes->expects($this->once()) 317 | //->method('all') 318 | //->willReturn([]); 319 | $this->request->query = $this 320 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 321 | ->disableOriginalConstructor() 322 | ->getMock(); 323 | 324 | $this->request->query->expects($this->at(0))->method('get')->with('filtering', [])->willReturn([]); 325 | $this->request->query->expects($this->at(1))->method('get')->with('filtering_or', [])->willReturn([ 326 | 'foo' => [ 327 | 'bar', 328 | 'atro', 329 | ] 330 | ]); 331 | $this->request->query->expects($this->at(2))->method('get')->with('sorting', [])->willReturn([]); 332 | $this->request->query->expects($this->at(3))->method('get')->with('printing', [])->willReturn([]); 333 | $this->request->query->expects($this->at(4))->method('get')->with('rel', '')->willReturn([]); 334 | $this->request->query->expects($this->at(5))->method('get')->with('page', '')->willReturn([]); 335 | $this->request->query->expects($this->at(6))->method('get')->with('select', 'asdf')->willReturn([]); 336 | $this->request->query->expects($this->at(7))->method('get')->with('filtering', '')->willReturn([]); 337 | $this->request->query->expects($this->at(8))->method('get')->with('limit', '')->willReturn([]); 338 | 339 | $this->builder = new QueryOptionsBuilder(); 340 | $this->builder->setEntityAlias($alias = 'asdf'); 341 | $options = $this->builder->buildFromRequestAndCustomFilter($this->request, [ 342 | 'custom' => 'filter', 343 | ]); 344 | 345 | $this->assertEquals( 346 | QueryBuilderOptions::fromArray([ 347 | 'filtering' => [], 348 | 'orFilters' => [ 349 | '0|0' => 'bar', 350 | '1|1' => 'atro', 351 | ], 352 | 'limit' => [], 353 | 'page' => [], 354 | 'filters' => [ 355 | 'custom' => 'filter', 356 | ], 357 | '_route_params' => null, 358 | 'sorting' => [], 359 | 'rel' => [], 360 | 'printing' => [], 361 | 'select' => [], 362 | 'justCount' => null, 363 | 'id' => null, 364 | '_route' => null, 365 | ]), 366 | $options 367 | ); 368 | } 369 | 370 | public function testShouldBuildQueryWithOrfiltersAndCustomOrFilters() 371 | { 372 | $this->request = $this 373 | ->getMockBuilder('Symfony\Component\HttpFoundation\Request') 374 | ->disableOriginalConstructor() 375 | ->getMock(); 376 | $this->request->attributes = $this 377 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 378 | ->disableOriginalConstructor() 379 | ->getMock(); 380 | //$this->request->attributes->expects($this->once()) 381 | //->method('all') 382 | //->willReturn([]); 383 | $this->request->query = $this 384 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 385 | ->disableOriginalConstructor() 386 | ->getMock(); 387 | 388 | $this->request->query->expects($this->at(0))->method('get')->with('filtering', [])->willReturn([]); 389 | $this->request->query->expects($this->at(1))->method('get')->with('filtering_or', [])->willReturn([]); 390 | $this->request->query->expects($this->at(2))->method('get')->with('sorting', [])->willReturn([]); 391 | $this->request->query->expects($this->at(3))->method('get')->with('printing', [])->willReturn([]); 392 | $this->request->query->expects($this->at(4))->method('get')->with('rel', '')->willReturn([]); 393 | $this->request->query->expects($this->at(5))->method('get')->with('page', '')->willReturn([]); 394 | $this->request->query->expects($this->at(6))->method('get')->with('select', 'asdf')->willReturn([]); 395 | $this->request->query->expects($this->at(7))->method('get')->with('filtering', '')->willReturn([]); 396 | $this->request->query->expects($this->at(8))->method('get')->with('limit', '')->willReturn([]); 397 | 398 | $this->builder = new QueryOptionsBuilder(); 399 | $this->builder->setEntityAlias($alias = 'asdf'); 400 | $options = $this->builder->buildForOrFilter($this->request, [ 401 | 'fizz' => 'buzz', 402 | ]); 403 | 404 | $this->assertEquals( 405 | QueryBuilderOptions::fromArray([ 406 | 'filtering' => [], 407 | 'orFilters' => [ 408 | 'fizz' => 'buzz', 409 | ], 410 | 'limit' => [], 411 | 'page' => [], 412 | 'filters' => [], 413 | '_route_params' => null, 414 | 'sorting' => [], 415 | 'rel' => [], 416 | 'printing' => [], 417 | 'select' => [], 418 | 'id' => null, 419 | '_route' => null, 420 | ]), 421 | $options 422 | ); 423 | } 424 | 425 | public function testShouldBuildQueryWithOrfiltersAsArrayAndCustomOrFilters() 426 | { 427 | $this->request = $this 428 | ->getMockBuilder('Symfony\Component\HttpFoundation\Request') 429 | ->disableOriginalConstructor() 430 | ->getMock(); 431 | $this->request->attributes = $this 432 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 433 | ->disableOriginalConstructor() 434 | ->getMock(); 435 | //$this->request->attributes->expects($this->once()) 436 | //->method('all') 437 | //->willReturn([]); 438 | $this->request->query = $this 439 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 440 | ->disableOriginalConstructor() 441 | ->getMock(); 442 | 443 | $this->request->query->expects($this->at(0))->method('get')->with('filtering', [])->willReturn([]); 444 | $this->request->query->expects($this->at(1))->method('get')->with('filtering_or', [])->willReturn([ 445 | 'a' => [ 446 | 'b', 447 | 'c', 448 | ], 449 | ]); 450 | $this->request->query->expects($this->at(2))->method('get')->with('sorting', [])->willReturn([]); 451 | $this->request->query->expects($this->at(3))->method('get')->with('printing', [])->willReturn([]); 452 | $this->request->query->expects($this->at(4))->method('get')->with('rel', '')->willReturn([]); 453 | $this->request->query->expects($this->at(5))->method('get')->with('page', '')->willReturn([]); 454 | $this->request->query->expects($this->at(6))->method('get')->with('select', 'asdf')->willReturn([]); 455 | $this->request->query->expects($this->at(7))->method('get')->with('filtering', '')->willReturn([]); 456 | $this->request->query->expects($this->at(8))->method('get')->with('limit', '')->willReturn([]); 457 | 458 | $this->builder = new QueryOptionsBuilder(); 459 | $this->builder->setEntityAlias($alias = 'asdf'); 460 | $options = $this->builder->buildForOrFilter($this->request, [ ]); 461 | 462 | $this->assertEquals( 463 | QueryBuilderOptions::fromArray([ 464 | 'filtering' => [], 465 | 'orFilters' => [ 466 | '0|0' => 'b', 467 | '1|1' => 'c', 468 | ], 469 | 'limit' => [], 470 | 'page' => [], 471 | 'filters' => [], 472 | '_route_params' => null, 473 | 'sorting' => [], 474 | 'rel' => [], 475 | 'printing' => [], 476 | 'select' => [], 477 | 'id' => null, 478 | '_route' => null, 479 | ]), 480 | $options 481 | ); 482 | } 483 | 484 | /** @expectedException Mado\QueryBundle\Exceptions\InvalidFiltersException */ 485 | public function testInvalidFilterThrownException() 486 | { 487 | $this->request = $this 488 | ->getMockBuilder('Symfony\Component\HttpFoundation\Request') 489 | ->disableOriginalConstructor() 490 | ->getMock(); 491 | $this->request->attributes = $this 492 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 493 | ->disableOriginalConstructor() 494 | ->getMock(); 495 | $this->request->query = $this 496 | ->getMockBuilder('Symfony\Component\HttpFoundation\ParameterBag') 497 | ->disableOriginalConstructor() 498 | ->getMock(); 499 | 500 | $this->request->query->expects($this->at(0))->method('get')->with('filtering', [])->willReturn(''); 501 | $this->request->query->expects($this->at(1))->method('get')->with('filtering_or', [])->willReturn([]); 502 | $this->request->query->expects($this->at(2))->method('get')->with('sorting', [])->willReturn([]); 503 | $this->request->query->expects($this->at(3))->method('get')->with('printing', [])->willReturn([]); 504 | $this->request->query->expects($this->at(4))->method('get')->with('rel', '')->willReturn([]); 505 | $this->request->query->expects($this->at(5))->method('get')->with('page', '')->willReturn([]); 506 | $this->request->query->expects($this->at(6))->method('get')->with('select', 'asdf')->willReturn([]); 507 | $this->request->query->expects($this->at(7))->method('get')->with('filtering', '')->willReturn([]); 508 | $this->request->query->expects($this->at(8))->method('get')->with('limit', '')->willReturn([]); 509 | 510 | $this->builder = new QueryOptionsBuilder(); 511 | $this->builder->setEntityAlias($alias = 'asdf'); 512 | $this->builder->buildFromRequestAndCustomFilter($this->request, []); 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Queries/QueryBuilderOptionsTest.php: -------------------------------------------------------------------------------- 1 | -1]); 13 | $this->assertEquals(PHP_INT_MAX, $options->get('limit')); 14 | } 15 | 16 | public function testUndefinedLimitMeansInfiniteByDefault() 17 | { 18 | $options = QueryBuilderOptions::fromArray([]); 19 | $this->assertEquals(PHP_INT_MAX, $options->get('limit')); 20 | } 21 | 22 | public function testGetDefaultValueIfNotSpecified() 23 | { 24 | $options = QueryBuilderOptions::fromArray([]); 25 | $this->assertEquals(42, $options->get('limit', 42)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Services/AdditionalFilterExtractorTest.php: -------------------------------------------------------------------------------- 1 | $ids, 12 | ]; 13 | 14 | $this->user = $this 15 | ->getMockBuilder('\Mado\QueryBundle\Interfaces\AdditionalFilterable') 16 | ->disableOriginalConstructor() 17 | ->getMock(); 18 | $this->user->expects($this->once()) 19 | ->method('getAdditionalFilters') 20 | ->will($this->returnValue($additionalFilters)); 21 | 22 | $filter = \Mado\QueryBundle\Services\AdditionalFilterExtractor::fromUser($this->user); 23 | 24 | $this->assertEquals( 25 | $ids, 26 | $filter->getFilters('additionalfilter') 27 | ); 28 | } 29 | 30 | public function testExtractEmptyArray() 31 | { 32 | $this->user = $this 33 | ->getMockBuilder('\Mado\QueryBundle\Interfaces\AdditionalFilterable') 34 | ->disableOriginalConstructor() 35 | ->getMock(); 36 | $this->user->expects($this->once()) 37 | ->method('getAdditionalFilters') 38 | ->will($this->returnValue([])); 39 | 40 | $filter = \Mado\QueryBundle\Services\AdditionalFilterExtractor::fromUser($this->user); 41 | 42 | $this->assertEquals('', $filter->getFilters('additionalfilter')); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Services/PagerTest.php: -------------------------------------------------------------------------------- 1 | pagerfantaFactoryMock = $this 27 | ->getMockBuilder(\Hateoas\Representation\Factory\PagerfantaFactory::class) 28 | ->disableOriginalConstructor() 29 | ->getMock(); 30 | 31 | $this->router = $this 32 | ->getMockBuilder(\Mado\QueryBundle\Services\Router::class) 33 | ->disableOriginalConstructor() 34 | ->getMock(); 35 | 36 | $this->queryBuilder = $this 37 | ->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) 38 | ->disableOriginalConstructor() 39 | ->getMock(); 40 | 41 | $this->queryBuilderOptions = $this 42 | ->getMockBuilder(\Mado\QueryBundle\Queries\QueryBuilderOptions::class) 43 | ->disableOriginalConstructor() 44 | ->getMock(); 45 | 46 | $this->pagerfantaBuilder = $this 47 | ->getMockBuilder(\Mado\QueryBundle\Objects\PagerfantaBuilder::class) 48 | ->disableOriginalConstructor() 49 | ->getMock(); 50 | 51 | $this->doctrineOrmAdapter = $this 52 | ->getMockBuilder(\Pagerfanta\Adapter\DoctrineORMAdapter::class) 53 | ->disableOriginalConstructor() 54 | ->getMock(); 55 | 56 | $this->query = $this 57 | ->getMockBuilder(\Doctrine\ORM\AbstractQuery::class) 58 | ->disableOriginalConstructor() 59 | ->getMock(); 60 | 61 | $this->pagerfanta = $this 62 | ->getMockBuilder(\Pagerfanta\Pagerfanta::class) 63 | ->disableOriginalConstructor() 64 | ->getMock(); 65 | 66 | $this->doctrineOrmAdapter 67 | ->method('getQuery') 68 | ->willReturn($this->query); 69 | 70 | $this->queryBuilderOptions 71 | ->expects($this->at(0)) 72 | ->method('get') 73 | ->with('limit') 74 | ->willReturn(1); 75 | 76 | $this->queryBuilderOptions 77 | ->expects($this->at(1)) 78 | ->method('get') 79 | ->with('page') 80 | ->willReturn(1); 81 | 82 | $this->pagerfantaBuilder 83 | ->method('create') 84 | ->willReturn($this->pagerfanta); 85 | 86 | $this->pagerfantaBuilder 87 | ->method('createRepresentation') 88 | ->willReturn(true); 89 | 90 | $this->pager = new Pager(); 91 | } 92 | 93 | public function testPaginateWithoutCache() 94 | { 95 | $this->query 96 | ->expects($this->never()) 97 | ->method('useResultCache'); 98 | 99 | $routeName = 'foo'; 100 | $useCache = null; 101 | 102 | $this->pager->setRouter($this->router); 103 | 104 | $this->pager->paginateResults( 105 | $this->queryBuilderOptions, 106 | $this->doctrineOrmAdapter, 107 | $this->pagerfantaBuilder, 108 | $routeName, 109 | $useCache 110 | ); 111 | } 112 | 113 | public function testPaginateWithCache() 114 | { 115 | $this->query 116 | ->expects($this->once()) 117 | ->method('useResultCache'); 118 | 119 | $routeName = 'foo'; 120 | $useCache = true; 121 | 122 | $this->pager->setRouter($this->router); 123 | 124 | $this->pager->paginateResults( 125 | $this->queryBuilderOptions, 126 | $this->doctrineOrmAdapter, 127 | $this->pagerfantaBuilder, 128 | $routeName, 129 | $useCache 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Services/RouterTest.php: -------------------------------------------------------------------------------- 1 | 'foo', 13 | 'orFilters' => 'bar' 14 | ]); 15 | 16 | $this->router = new Router(); 17 | $route = $this->router->createRouter($queryBuilderOptions, 'foo'); 18 | 19 | $this->assertEquals('foo', $route->getName()); 20 | } 21 | 22 | public function testCreateRouterWithRouteParams() 23 | { 24 | $routeName = 'route'; 25 | $routeParamsKey = 'foo'; 26 | $routeParamsValue = 'bar'; 27 | $queryBuilderOptions = QueryBuilderOptions::fromArray([ 28 | '_route_params' => [$routeParamsKey => $routeParamsValue] 29 | ]); 30 | 31 | $this->router = new Router(); 32 | $route = $this->router->createRouter($queryBuilderOptions, $routeName); 33 | $routeParams = $route->getParameters(); 34 | 35 | $this->assertEquals($routeName, $route->getName()); 36 | $this->assertTrue(array_key_exists($routeParamsKey, $routeParams)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Mado/QueryBundle/Services/StringParserTest.php: -------------------------------------------------------------------------------- 1 | parser = new StringParser(); 11 | 12 | $this->assertEquals( 13 | 'fizzBuzzFooBar', 14 | $this->parser->camelize('fizz_buzz_foo_bar') 15 | ); 16 | } 17 | 18 | public function testDotNotationFor() 19 | { 20 | $result = StringParser::dotNotationFor('Foo\bar'); 21 | 22 | $this->assertEquals('foo.bar', $result); 23 | } 24 | } 25 | --------------------------------------------------------------------------------