├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpcs.xml.dist ├── phpstan.neon ├── phpunit.xml.dist ├── src ├── AbstractAbstractFactory.php ├── ConfigProvider.php ├── Console │ ├── ConfigurationSkeletonController.php │ └── ConfigurationSkeletonControllerFactory.php ├── Context.php ├── Criteria │ ├── CriteriaManager.php │ ├── CriteriaManagerFactory.php │ ├── CriteriaType.php │ ├── CriteriaTypeAbstractFactory.php │ └── Type │ │ ├── AbstractFilterType.php │ │ └── Between.php ├── Documentation │ ├── ApigilityDocumentationProvider.php │ ├── ApigilityDocumentationProviderFactory.php │ ├── DocumentationProviderInterface.php │ ├── HydratorConfigurationDocumentationProvider.php │ └── HydratorConfigurationDocumentationProviderFactory.php ├── Event.php ├── Field │ ├── FieldResolver.php │ └── FieldResolverFactory.php ├── Filter │ ├── FilterManager.php │ ├── FilterManagerFactory.php │ ├── FilterType.php │ ├── FilterTypeAbstractFactory.php │ ├── Loader.php │ ├── LoaderFactory.php │ └── Type │ │ ├── AbstractFilterType.php │ │ └── Between.php ├── Hydrator │ ├── DoctrineHydrator.php │ ├── DoctrineHydratorFactory.php │ ├── Filter │ │ ├── FilterDefault.php │ │ └── Password.php │ ├── HydratorExtractToolDefault.php │ ├── HydratorExtractToolDefaultFactory.php │ ├── HydratorExtractToolInterface.php │ └── Strategy │ │ ├── AssociationDefault.php │ │ ├── FieldDefault.php │ │ ├── NullifyOwningAssociation.php │ │ ├── ToBoolean.php │ │ ├── ToFloat.php │ │ ├── ToInteger.php │ │ └── ToJson.php ├── Module.php ├── Resolve │ ├── EntityResolveAbstractFactory.php │ ├── Loader.php │ ├── LoaderFactory.php │ ├── ResolveManager.php │ └── ResolveManagerFactory.php └── Type │ ├── DateTimeType.php │ ├── EntityType.php │ ├── EntityTypeAbstractFactory.php │ ├── Loader.php │ ├── LoaderFactory.php │ ├── TypeManager.php │ └── TypeManagerFactory.php └── test ├── AbstractTest.php ├── Bootstrap.php ├── GraphQL ├── AddressTest.php ├── ArtistTest.php ├── ContextTest.php ├── CriteriaFiltersTest.php ├── EventTest.php ├── PerformanceTest.php ├── QueryBuilderFiltersTest.php └── UserTest.php ├── config ├── autoload │ ├── local.php │ ├── zf-doctrine-criteria.global.php │ ├── zf-doctrine-graphql.global.php │ └── zf-doctrine-querybuilder.global.php └── test.config.php ├── module └── DbTest │ ├── config │ ├── module.config.php │ └── orm │ │ ├── DbTest.Entity.Address.dcm.xml │ │ ├── DbTest.Entity.Artist.dcm.xml │ │ ├── DbTest.Entity.Performance.dcm.xml │ │ └── DbTest.Entity.User.dcm.xml │ ├── media │ └── GraphQLTest.skipper │ └── src │ ├── Entity │ ├── Address.php │ ├── Artist.php │ ├── Performance.php │ └── User.php │ ├── Hydrator │ └── Filter │ │ └── EventTestFilter.php │ └── Module.php └── start.php /.coveralls.yml: -------------------------------------------------------------------------------- 1 | coverage_clover: build/logs/clover.xml 2 | json_path: build/logs/coveralls-upload.json 3 | exclude_no_stmt: true 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | phpcs.xml 3 | phpunit.xml 4 | composer.lock 5 | vendor/ 6 | coverage/ 7 | data/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: php 4 | 5 | cache: 6 | directories: 7 | - $HOME/.composer/cache 8 | 9 | env: 10 | global: 11 | - COMPOSER_ARGS="--no-interaction" 12 | 13 | matrix: 14 | fast_finish: true 15 | include: 16 | - php: 7.1 17 | env: 18 | - DEPS=lowest 19 | - php: 7.1 20 | env: 21 | - DEPS=latest 22 | - php: 7.2 23 | - php: nightly 24 | allow_failures: 25 | - php: nightly 26 | 27 | before_install: 28 | - composer self-update 29 | 30 | install: 31 | - mkdir -p build/logs 32 | - if [[ $DEPS == 'latest' ]]; then travis_retry composer update $COMPOSER_ARGS ; fi 33 | - if [[ $DEPS == 'lowest' ]]; then travis_retry composer update --prefer-lowest --prefer-stable $COMPOSER_ARGS ; fi 34 | - travis_retry composer install $COMPOSER_ARGS 35 | - composer show --installed 36 | 37 | script: 38 | - ./vendor/bin/phpunit --coverage-clover=build/logs/clover.xml 39 | - ./vendor/bin/phpcs 40 | - ./vendor/bin/phpstan analyze -c phpstan.neon --level=7 src/ 41 | 42 | after_success: 43 | - travis_retry php vendor/bin/php-coveralls 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 API Skeletons 2 | Copyright (c) 2018 phpro 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GraphQL for Doctrine using Hydrators 2 | ==================================== 3 | 4 | [![Build Status](https://travis-ci.org/API-Skeletons/zf-doctrine-graphql.svg)](https://travis-ci.org/API-Skeletons/zf-doctrine-graphql) 5 | [![Coverage](https://coveralls.io/repos/github/API-Skeletons/zf-doctrine-graphql/badge.svg?branch=master&124)](https://coveralls.io/repos/github/API-Skeletons/zf-doctrine-graphql/badge.svg?branch=master&124) 6 | [![PHPStan](https://img.shields.io/badge/PHPStan-enabled-brightgreen.svg?style=flat)](https://github.com/phpstan/phpstan) 7 | [![Gitter](https://badges.gitter.im/api-skeletons/open-source.svg)](https://gitter.im/api-skeletons/open-source) 8 | [![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://www.patreon.com/apiskeletons) 9 | [![Total Downloads](https://poser.pugx.org/api-skeletons/zf-doctrine-graphql/downloads)](https://packagist.org/packages/api-skeletons/zf-doctrine-graphql) 10 | 11 | This library uses Doctrine native traversal of related objects to provide full GraphQL 12 | querying of entities and all related fields and entities. 13 | Entity metadata is introspected and is therefore Doctrine data driver agnostic. 14 | Data is collected with hydrators thereby 15 | allowing full control over each field using hydrator filters, strategies and naming strategies. 16 | Multiple object managers are supported. Multiple hydrator configurations are supported. 17 | Works with [GraphiQL](https://github.com/graphql/graphiql). 18 | 19 | [A range of filters](http://graphql.apiskeletons.com/en/latest/queries.html) 20 | are provided to filter collections at any location in the query. 21 | 22 | Doctrine provides easy taversal of your database. Consider the following imaginary query: 23 | ```php 24 | $entity[where id = 5] 25 | ->getRelation() 26 | ->getField1() 27 | ->getField2() 28 | ->getManyToOne([where name like '%dev%']) 29 | ->getName() 30 | ->getField3() 31 | ->getOtherRelation() 32 | ->getField4() 33 | ->getField5() 34 | ``` 35 | 36 | And see it realized in GraphQL with fine grained control over each field via hydrators: 37 | 38 | ```js 39 | { 40 | entity (filter: { id: 5 }) { 41 | relation { 42 | field1 43 | field2 44 | manyToOne (filter: { name_contains: 'dev' }) { 45 | name 46 | field3 47 | } 48 | } otherRelation { 49 | field4 50 | field5 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | 57 | [Read the Documentation](http://graphql.apiskeletons.com) 58 | ========================================================== 59 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-skeletons/zf-doctrine-graphql", 3 | "description": "GraphQL for Doctrine using Hydrators", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Tom H Anderson", 9 | "email": "tom.h.anderson@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "doctrine/instantiator": "^1.1", 14 | "doctrine/collections": "^1.5", 15 | "zendframework/zend-hydrator": "^2.4", 16 | "zendframework/zend-modulemanager": "^2.8", 17 | "doctrine/common": "^2.8", 18 | "doctrine/doctrine-orm-module": "^1.1.8", 19 | "zendframework/zend-mvc-console": "^1.2", 20 | "zendframework/zend-config": "^3.2", 21 | "api-skeletons/zf-doctrine-criteria": "^1.0", 22 | "zfcampus/zf-doctrine-querybuilder": "^1.7", 23 | "phpro/zf-doctrine-hydration-module": "^3.0", 24 | "webonyx/graphql-php": "^0.12.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "ZF\\Doctrine\\GraphQL\\": "src/" 29 | } 30 | }, 31 | "extra": { 32 | "zf": { 33 | "component": "ZF\\Doctrine\\GraphQL", 34 | "config-provider": "ZF\\Doctrine\\GraphQL\\ConfigProvider" 35 | } 36 | }, 37 | "require-dev": { 38 | "dprevite/lint": "dev-master", 39 | "api-skeletons/coding-standard": "^1.0", 40 | "phpunit/phpunit": "^7.1", 41 | "phpstan/phpstan-doctrine": "^0.10", 42 | "zendframework/zend-test": "^3.2", 43 | "php-coveralls/php-coveralls": "^2.1" 44 | }, 45 | "scripts": { 46 | "ci-check": [ 47 | "@lint", 48 | "@phpcs", 49 | "@test", 50 | "@phpstan" 51 | ], 52 | "lint": "lint src/", 53 | "phpcs": "phpcs", 54 | "phpstan": "phpstan analyze -c phpstan.neon --level=7 src/", 55 | "test": "phpunit --colors=always", 56 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | v1.0 Coding Standard for API Skeletons 4 | 5 | 6 | 7 | src 8 | Bootstrap.php 9 | */data/* 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/phpstan/phpstan-doctrine/extension.neon 3 | 4 | parameters: 5 | fileExtensions: 6 | - php 7 | excludes_analyse: 8 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./test 6 | 7 | 8 | 9 | 10 | ./src 11 | ./config 12 | 13 | ./vendor 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/AbstractAbstractFactory.php: -------------------------------------------------------------------------------- 1 | events = new EventManager( 25 | $sharedEventManager, 26 | [ 27 | Event::class, 28 | ] 29 | ); 30 | 31 | return $this->events; 32 | } 33 | 34 | public function getEventManager() 35 | { 36 | return $this->events; 37 | } 38 | 39 | public function __invoke(ContainerInterface $container, $requestedName, array $options = null) 40 | { 41 | // Setup Events 42 | $this->createEventManager($container->get('SharedEventManager')); 43 | } 44 | 45 | protected function isCached($requestedName, array $options = null) 46 | { 47 | foreach ($this->services as $service) { 48 | if ($service['requestedName'] == $requestedName && $service['options'] == $options) { 49 | return true; 50 | } 51 | } 52 | 53 | return false; 54 | } 55 | 56 | protected function getCache($requestedName, array $options = null) 57 | { 58 | foreach ($this->services as $service) { 59 | if ($service['requestedName'] == $requestedName && $service['options'] == $options) { 60 | return $service['instance']; 61 | } 62 | } 63 | 64 | // @codeCoverageIgnoreStart 65 | throw new Exception('Cache not found for ' . $requestedName); 66 | // @codeCoverageIgnoreEnd 67 | } 68 | 69 | protected function cache($requestedName, array $options = null, $instance) 70 | { 71 | foreach ($this->services as $service) { 72 | if ($service['requestedName'] == $requestedName && $service['options'] == $options) { 73 | return; 74 | } 75 | } 76 | 77 | $this->services[] = [ 78 | 'requestedName' => $requestedName, 79 | 'options' => $options, 80 | 'instance' => $instance, 81 | ]; 82 | 83 | return $instance; 84 | } 85 | 86 | protected function mapFieldType(string $fieldType, TypeManager $typeManager) 87 | { 88 | $graphQLType = null; 89 | 90 | switch ($fieldType) { 91 | case 'tinyint': 92 | case 'smallint': 93 | case 'integer': 94 | case 'int': 95 | case 'bigint': 96 | $graphQLType = Type::int(); 97 | break; 98 | case 'boolean': 99 | $graphQLType = Type::boolean(); 100 | break; 101 | case 'decimal': 102 | case 'float': 103 | $graphQLType = Type::float(); 104 | break; 105 | case 'string': 106 | case 'text': 107 | $graphQLType = Type::string(); 108 | break; 109 | case 'array': 110 | $graphQLType = Type::listOf(Type::string()); 111 | break; 112 | default: 113 | if ($typeManager->has($fieldType)) { 114 | $graphQLType = $typeManager->get($fieldType); 115 | } 116 | break; 117 | } 118 | 119 | return $graphQLType; 120 | } 121 | 122 | /** 123 | * In order to support fields with underscores we need to know 124 | * if the possible filter name we found as the last _part of the 125 | * filter field name is indeed a filter else it could be a field 126 | * e.g. id_name filter resolves to 'name' and is not a filter 127 | * e.g. id_eq filter resolves to 'eq' and is a filter 128 | */ 129 | public function isFilter($filterName) 130 | { 131 | switch (strtolower($filterName)) { 132 | case 'eq': 133 | case 'neq': 134 | case 'gt': 135 | case 'lt': 136 | case 'gte': 137 | case 'lte': 138 | case 'in': 139 | case 'notin': 140 | case 'between': 141 | case 'contains': 142 | case 'startswith': 143 | case 'endswith': 144 | case 'memberof': 145 | case 'isnull': 146 | case 'sort': 147 | case 'distinct': 148 | return true; 149 | // @codeCoverageIgnoreStart 150 | default: 151 | return false; 152 | // @codeCoverageIgnoreEnd 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | $this->getDependencyConfig(), 16 | 'hydrators' => $this->getHydratorConfig(), 17 | 'controllers' => $this->getControllerConfig(), 18 | 'console' => [ 19 | 'router' => $this->getConsoleRouterConfig(), 20 | ], 21 | 'zf-doctrine-graphql-type' => $this->getDoctrineGraphQLTypeConfig(), 22 | 'zf-doctrine-graphql-filter' => $this->getDoctrineGraphQLFilterConfig(), 23 | 'zf-doctrine-graphql-criteria' => $this->getDoctrineGraphQLCriteriaConfig(), 24 | 'zf-doctrine-graphql-resolve' => $this->getDoctrineGraphQLResolveConfig(), 25 | ]; 26 | } 27 | 28 | public function getDependencyConfig() 29 | { 30 | return [ 31 | 'aliases' => [ 32 | 'ZF\Doctrine\GraphQL\Hydrator\HydratorExtractTool' => Hydrator\HydratorExtractToolDefault::class, 33 | 'ZF\Doctrine\GraphQL\Documentation\DocumentationProvider' 34 | => Documentation\HydratorConfigurationDocumentationProvider::class, 35 | ], 36 | 'factories' => [ 37 | Hydrator\HydratorExtractToolDefault::class => Hydrator\HydratorExtractToolDefaultFactory::class, 38 | 39 | Hydrator\Filter\FilterDefault::class => InvokableFactory::class, 40 | Hydrator\Filter\Password::class => InvokableFactory::class, 41 | Hydrator\Strategy\ToBoolean::class => InvokableFactory::class, 42 | Hydrator\Strategy\ToFloat::class => InvokableFactory::class, 43 | Hydrator\Strategy\ToInteger::class => InvokableFactory::class, 44 | Hydrator\Strategy\ToJson::class => InvokableFactory::class, 45 | Hydrator\Strategy\NullifyOwningAssociation::class => InvokableFactory::class, 46 | Hydrator\Strategy\AssociationDefault::class => InvokableFactory::class, 47 | Hydrator\Strategy\FieldDefault::class => InvokableFactory::class, 48 | 49 | Criteria\CriteriaManager::class => Criteria\CriteriaManagerFactory::class, 50 | Field\FieldResolver::class => Field\FieldResolverFactory::class, 51 | Filter\Loader::class => Filter\LoaderFactory::class, 52 | Filter\FilterManager::class => Filter\FilterManagerFactory::class, 53 | Resolve\ResolveManager::class => Resolve\ResolveManagerFactory::class, 54 | Resolve\Loader::class => Resolve\LoaderFactory::class, 55 | Type\Loader::class => Type\LoaderFactory::class, 56 | Type\TypeManager::class => Type\TypeManagerFactory::class, 57 | 58 | Documentation\HydratorConfigurationDocumentationProvider::class => 59 | Documentation\HydratorConfigurationDocumentationProviderFactory::class, 60 | ], 61 | ]; 62 | } 63 | 64 | public function getHydratorConfig() 65 | { 66 | return [ 67 | 'abstract_factories' => [ 68 | Hydrator\DoctrineHydratorFactory::class, 69 | ], 70 | ]; 71 | } 72 | 73 | public function getDoctrineGraphQLTypeConfig() 74 | { 75 | return [ 76 | 'invokables' => [ 77 | 'datetime' => Type\DateTimeType::class, 78 | ], 79 | 'abstract_factories' => [ 80 | Type\EntityTypeAbstractFactory::class, 81 | ], 82 | ]; 83 | } 84 | 85 | public function getDoctrineGraphQLFilterConfig() 86 | { 87 | return [ 88 | 'abstract_factories' => [ 89 | Filter\FilterTypeAbstractFactory::class, 90 | ], 91 | ]; 92 | } 93 | 94 | public function getDoctrineGraphQLCriteriaConfig() 95 | { 96 | return [ 97 | 'abstract_factories' => [ 98 | Criteria\CriteriaTypeAbstractFactory::class, 99 | ], 100 | ]; 101 | } 102 | 103 | public function getDoctrineGraphQLResolveConfig() 104 | { 105 | return [ 106 | 'abstract_factories' => [ 107 | Resolve\EntityResolveAbstractFactory::class, 108 | ], 109 | ]; 110 | } 111 | 112 | public function getControllerConfig() 113 | { 114 | return [ 115 | 'factories' => [ 116 | Console\ConfigurationSkeletonController::class 117 | => Console\ConfigurationSkeletonControllerFactory::class, 118 | ], 119 | ]; 120 | } 121 | 122 | public function getConsoleRouterConfig() 123 | { 124 | return [ 125 | 'routes' => [ 126 | 'graphql-skeleton' => [ 127 | 'type' => 'simple', 128 | 'options' => [ 129 | 'route' => 'graphql:config-skeleton [--hydrator-sections=] [--object-manager=]', 130 | 'defaults' => [ 131 | 'controller' => Console\ConfigurationSkeletonController::class, 132 | 'action' => 'index' 133 | ], 134 | ], 135 | ], 136 | ], 137 | ]; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Console/ConfigurationSkeletonController.php: -------------------------------------------------------------------------------- 1 | container = $container; 24 | } 25 | 26 | public function indexAction() 27 | { 28 | $objectManagerAlias = $this->params()->fromRoute('object-manager') ?? 'doctrine.entitymanager.orm_default'; 29 | $hydratorSections = $this->params()->fromRoute('hydrator-sections') ?? 'default'; 30 | 31 | if (! $this->container->has($objectManagerAlias)) { 32 | throw new Exception('Invalid object manager alias'); 33 | } 34 | $objectManager = $this->container->get($objectManagerAlias); 35 | 36 | // Sort entity names 37 | $metadata = $objectManager->getMetadataFactory()->getAllMetadata(); 38 | usort($metadata, function ($a, $b) { 39 | return $a->getName() <=> $b->getName(); 40 | }); 41 | 42 | $config = [ 43 | 'zf-doctrine-graphql-hydrator' => [] 44 | ]; 45 | 46 | foreach (explode(',', $hydratorSections) as $section) { 47 | foreach ($metadata as $classMetadata) { 48 | $hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' 49 | . str_replace('\\', '_', $classMetadata->getName()); 50 | 51 | $strategies = []; 52 | $filters = []; 53 | $documentation = ['_entity' => '']; 54 | 55 | // Sort field names 56 | $fieldNames = $classMetadata->getFieldNames(); 57 | usort($fieldNames, function ($a, $b) { 58 | if ($a == 'id') { 59 | return -1; 60 | } 61 | 62 | if ($b == 'id') { 63 | return 1; 64 | } 65 | 66 | return $a <=> $b; 67 | }); 68 | 69 | foreach ($fieldNames as $fieldName) { 70 | $documentation[$fieldName] = ''; 71 | $fieldMetadata = $classMetadata->getFieldMapping($fieldName); 72 | 73 | // Handle special named fields 74 | if ($fieldName == 'password' || $fieldName == 'secret') { 75 | $filters['password'] = [ 76 | 'condition' => 'and', 77 | 'filter' => Filter\Password::class, 78 | ]; 79 | continue; 80 | } 81 | 82 | // Handle all other fields 83 | switch ($fieldMetadata['type']) { 84 | case 'tinyint': 85 | case 'smallint': 86 | case 'integer': 87 | case 'int': 88 | case 'bigint': 89 | $strategies[$fieldName] = Strategy\ToInteger::class; 90 | break; 91 | case 'boolean': 92 | $strategies[$fieldName] = Strategy\ToBoolean::class; 93 | break; 94 | case 'decimal': 95 | case 'float': 96 | $strategies[$fieldName] = Strategy\ToFloat::class; 97 | break; 98 | case 'string': 99 | case 'text': 100 | case 'datetime': 101 | default: 102 | $strategies[$fieldName] = Strategy\FieldDefault::class; 103 | break; 104 | } 105 | } 106 | 107 | // Sort association Names 108 | $associationNames = $classMetadata->getAssociationNames(); 109 | usort($associationNames, function ($a, $b) { 110 | return $a <=> $b; 111 | }); 112 | 113 | foreach ($associationNames as $associationName) { 114 | # $documentation[$associationName] = ''; 115 | $mapping = $classMetadata->getAssociationMapping($associationName); 116 | 117 | // See comment on NullifyOwningAssociation for details of why this is done 118 | if ($mapping['type'] == ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide']) { 119 | $strategies[$associationName] = Strategy\NullifyOwningAssociation::class; 120 | } else { 121 | $strategies[$associationName] = Strategy\AssociationDefault::class; 122 | } 123 | } 124 | 125 | $filters['default'] = [ 126 | 'condition' => 'and', 127 | 'filter' => Filter\FilterDefault::class, 128 | ]; 129 | 130 | $config['zf-doctrine-graphql-hydrator'][$hydratorAlias][$section] = [ 131 | 'entity_class' => $classMetadata->getName(), 132 | 'object_manager' => $objectManagerAlias, 133 | 'by_value' => true, 134 | 'use_generated_hydrator' => true, 135 | 'hydrator' => null, 136 | 'naming_strategy' => null, 137 | 'strategies' => $strategies, 138 | 'filters' => $filters, 139 | 'documentation' => $documentation, 140 | ]; 141 | } 142 | } 143 | 144 | $configObject = new Config($config); 145 | $writer = new PhpArray(); 146 | $writer->setUseBracketArraySyntax(true); 147 | $writer->setUseClassNameScalars(true); 148 | 149 | echo $writer->toString($configObject); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Console/ConfigurationSkeletonControllerFactory.php: -------------------------------------------------------------------------------- 1 | get('console'); 19 | $instance->setConsole($console); 20 | 21 | return $instance; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Context.php: -------------------------------------------------------------------------------- 1 | hydratorSection = $value; 20 | 21 | return $this; 22 | } 23 | 24 | public function getHydratorSection() 25 | { 26 | return $this->hydratorSection; 27 | } 28 | 29 | public function setLimit(int $value) 30 | { 31 | $this->limit = $value; 32 | 33 | return $this; 34 | } 35 | 36 | public function getLimit() 37 | { 38 | return $this->limit; 39 | } 40 | 41 | public function getUseHydratorCache() 42 | { 43 | return $this->useHydratorCache; 44 | } 45 | 46 | public function setUseHydratorCache(bool $value) 47 | { 48 | $this->useHydratorCache = $value; 49 | 50 | return $this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Criteria/CriteriaManager.php: -------------------------------------------------------------------------------- 1 | instanceOf) { 28 | throw new Exception\InvalidServiceException(sprintf( 29 | '%s can only create instances of %s; %s is invalid', 30 | get_class($this), 31 | $this->instanceOf, 32 | is_object($instance) ? get_class($instance) : gettype($instance) 33 | )); 34 | } 35 | } 36 | 37 | /** 38 | * Validate the plugin is of the expected type (v2). 39 | * 40 | * Proxies to `validate()`. 41 | * 42 | * @param mixed $plugin 43 | * @return void 44 | * @throws Exception\InvalidArgumentException 45 | * @codeCoverageIgnore 46 | */ 47 | public function validatePlugin($plugin) 48 | { 49 | try { 50 | $this->validate($plugin); 51 | } catch (Exception\InvalidServiceException $e) { 52 | throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Criteria/CriteriaManagerFactory.php: -------------------------------------------------------------------------------- 1 | canCreate($services, $requestedName); 27 | } 28 | 29 | /** 30 | * @codeCoverageIgnore 31 | */ 32 | public function createServiceWithName(ServiceLocatorInterface $services, $name, $requestedName) 33 | { 34 | return $this($services, $requestedName); 35 | } 36 | 37 | /** 38 | * Loop through all configured ORM managers and if the passed $requestedName 39 | * as entity name is managed by the ORM return true; 40 | */ 41 | public function canCreate(ContainerInterface $container, $requestedName) 42 | { 43 | $config = $container->get('config'); 44 | $hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $requestedName); 45 | 46 | return isset($config['zf-doctrine-graphql-hydrator'][$hydratorAlias]); 47 | } 48 | 49 | public function __invoke(ContainerInterface $container, $requestedName, array $options = null) : CriteriaType 50 | { 51 | // @codeCoverageIgnoreStart 52 | if ($this->isCached($requestedName, $options)) { 53 | return $this->getCache($requestedName, $options); 54 | } 55 | // @codeCoverageIgnoreEnd 56 | 57 | parent::__invoke($container, $requestedName, $options); 58 | 59 | $config = $container->get('config'); 60 | $fields = []; 61 | $typeManager = $container->get(TypeManager::class); 62 | $filterManager = $container->get(FilterManager::class); 63 | $orderByManager = $container->get(OrderByManager::class); 64 | $hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $requestedName); 65 | $hydratorExtractTool = $container->get('ZF\\Doctrine\\GraphQL\\Hydrator\\HydratorExtractTool'); 66 | $objectManager = $container 67 | ->get( 68 | $config['zf-doctrine-graphql-hydrator'][$hydratorAlias][$options['hydrator_section']]['object_manager'] 69 | ); 70 | 71 | // Get an array of the hydrator fields 72 | $entityFields = $hydratorExtractTool->getFieldArray($requestedName, $hydratorAlias, $options); 73 | 74 | $classMetadata = $objectManager->getClassMetadata($requestedName); 75 | 76 | foreach ($entityFields as $fieldName) { 77 | $graphQLType = null; 78 | try { 79 | $fieldMetadata = $classMetadata->getFieldMapping($fieldName); 80 | } catch (MappingException $e) { 81 | // For all related data you cannot query on them from the current resource 82 | continue; 83 | } 84 | 85 | $graphQLType = $this->mapFieldType($fieldMetadata['type'], $typeManager); 86 | if ($fieldMetadata['type'] == 'array') { 87 | $graphQLType = Type::string(); 88 | } 89 | 90 | if ($graphQLType && $classMetadata->isIdentifier($fieldMetadata['fieldName'])) { 91 | $graphQLType = Type::id(); 92 | } 93 | 94 | // Send event to allow overriding a field type 95 | $results = $this->getEventManager()->trigger( 96 | Event::MAP_FIELD_TYPE, 97 | $this, 98 | [ 99 | 'fieldName' => $fieldName, 100 | 'graphQLType' => $graphQLType, 101 | 'classMetadata' => $classMetadata, 102 | 'fieldMetadata' => $fieldMetadata, 103 | 'hydratorAlias' => $hydratorAlias, 104 | 'options' => $options, 105 | ] 106 | ); 107 | if ($results->stopped()) { 108 | $graphQLType = $results->last(); 109 | } 110 | 111 | if ($graphQLType) { 112 | if ($orderByManager->has('field')) { 113 | $fields[$fieldName . '_sort'] = [ 114 | 'name' => $fieldName . '_sort', 115 | 'type' => Type::string(), 116 | 'description' => 'Sort the result either ASC or DESC', 117 | ]; 118 | } 119 | 120 | // Add filters 121 | if ($filterManager->has('eq')) { 122 | $fields[$fieldName] = [ 123 | 'name' => $fieldName, 124 | 'type' => $graphQLType, 125 | 'description' => 'Equals; same as name: value. DateTime not supported.', 126 | ]; 127 | 128 | $fields[$fieldName . '_eq'] = [ 129 | 'name' => $fieldName . '_eq', 130 | 'type' => $graphQLType, 131 | 'description' => 'Equals; same as name: value. DateTime not supported.', 132 | ]; 133 | } 134 | 135 | if ($filterManager->has('neq')) { 136 | $fields[$fieldName . '_neq'] = [ 137 | 'name' => $fieldName . '_neq', 138 | 'type' => $graphQLType, 139 | 'description' => 'Not Equals', 140 | ]; 141 | } 142 | 143 | if ($filterManager->has('lt')) { 144 | $fields[$fieldName . '_lt'] = [ 145 | 'name' => $fieldName . '_lt', 146 | 'type' => $graphQLType, 147 | 'description' => 'Less Than', 148 | ]; 149 | } 150 | 151 | if ($filterManager->has('lte')) { 152 | $fields[$fieldName . '_lte'] = [ 153 | 'name' => $fieldName . '_lte', 154 | 'type' => $graphQLType, 155 | 'description' => 'Less Than or Equal To', 156 | ]; 157 | } 158 | 159 | if ($filterManager->has('gt')) { 160 | $fields[$fieldName . '_gt'] = [ 161 | 'name' => $fieldName . '_gt', 162 | 'type' => $graphQLType, 163 | 'description' => 'Greater Than', 164 | ]; 165 | } 166 | 167 | if ($filterManager->has('gte')) { 168 | $fields[$fieldName . '_gte'] = [ 169 | 'name' => $fieldName . '_gte', 170 | 'type' => $graphQLType, 171 | 'description' => 'Greater Than or Equal To', 172 | ]; 173 | } 174 | 175 | if ($filterManager->has('eq') && $filterManager->has('neq')) { 176 | $fields[$fieldName . '_isnull'] = [ 177 | 'name' => $fieldName . '_isnull', 178 | 'type' => Type::boolean(), 179 | 'description' => 'Takes a boolean. If TRUE return results where the field is null. ' 180 | . 'If FALSE returns results where the field is not null. ' 181 | . 'NOTE: acts as "isEmpty" for collection filters. A value of false will ' 182 | . 'be handled as though it were null.', 183 | ]; 184 | } 185 | 186 | if ($filterManager->has('lte') && $filterManager->has('gte')) { 187 | $fields[$fieldName . '_between'] = [ 188 | 'name' => $fieldName . '_between', 189 | 'description' => 'Filter between `from` and `to` values. Good substitute for DateTime Equals.', 190 | 'type' => new CriteriaTypeNS\Between(['fields' => [ 191 | 'from' => [ 192 | 'name' => 'from', 193 | 'type' => Type::nonNull($graphQLType), 194 | ], 195 | 'to' => [ 196 | 'name' => 'to', 197 | 'type' => Type::nonNull($graphQLType), 198 | ], 199 | ] 200 | ]), 201 | ]; 202 | } 203 | 204 | if ($filterManager->has('in')) { 205 | $fields[$fieldName . '_in'] = [ 206 | 'name' => $fieldName . '_in', 207 | 'type' => Type::listOf($graphQLType), 208 | 'description' => 'Filter for values in an array', 209 | ]; 210 | } 211 | 212 | if ($filterManager->has('notin')) { 213 | $fields[$fieldName . '_notin'] = [ 214 | 'name' => $fieldName . '_notin', 215 | 'type' => Type::listOf($graphQLType), 216 | 'description' => 'Filter for values not in an array', 217 | ]; 218 | } 219 | 220 | if ($graphQLType == Type::string()) { 221 | if ($filterManager->has('startswith')) { 222 | $fields[$fieldName . '_startswith'] = [ 223 | 'name' => $fieldName . '_startswith', 224 | 'type' => $graphQLType, 225 | 'documentation' => 'Strings only. ' 226 | . 'A like query from the beginning of the value `like \'value%\'`', 227 | ]; 228 | } 229 | 230 | if ($filterManager->has('endswith')) { 231 | $fields[$fieldName . '_endswith'] = [ 232 | 'name' => $fieldName . '_endswith', 233 | 'type' => $graphQLType, 234 | 'documentation' => 'Strings only. ' 235 | . 'A like query from the end of the value `like \'%value\'`', 236 | ]; 237 | } 238 | 239 | if ($filterManager->has('contains')) { 240 | $fields[$fieldName . '_contains'] = [ 241 | 'name' => $fieldName . '_contains', 242 | 'type' => $graphQLType, 243 | 'description' => 'Strings only. Similar to a Like query as `like \'%value%\'`', 244 | ]; 245 | } 246 | } 247 | 248 | if ($filterManager->has('memberof')) { 249 | $fields[$fieldName . '_memberof'] = [ 250 | 'name' => $fieldName . '_memberof', 251 | 'type' => $graphQLType, 252 | 'description' => 'Matches a value in an array field.', 253 | ]; 254 | } 255 | } 256 | 257 | $fields[$fieldName . '_distinct'] = [ 258 | 'name' => $fieldName . '_distinct', 259 | 'type' => Type::boolean(), 260 | 'description' => 'Return a unique list of fieldName. Only one distinct fieldName allowed per filter.', 261 | ]; 262 | } 263 | 264 | $fields['_skip'] = [ 265 | 'name' => '_skip', 266 | 'type' => Type::int(), 267 | 'documentation' => 'Skip forward x records from beginning of data set.', 268 | ]; 269 | $fields['_limit'] = [ 270 | 'name' => '_limit', 271 | 'type' => Type::int(), 272 | 'documentation' => 'Limit the number of results to x.', 273 | ]; 274 | 275 | $instance = new CriteriaType([ 276 | 'name' => str_replace('\\', '_', $requestedName) . '__CriteriaFilter', 277 | 'fields' => function () use ($fields) { 278 | return $fields; 279 | }, 280 | ]); 281 | 282 | return $this->cache($requestedName, $options, $instance); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/Criteria/Type/AbstractFilterType.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'name' => 'field', 16 | 'type' => Type::string(), 17 | ], 18 | 'where' => [ 19 | 'name' => 'where', 20 | 'type' => Type::string(), 21 | 'defaultValue' => 'and', 22 | ], 23 | 'format' => [ 24 | 'name' => 'format', 25 | 'type' => Type::string(), 26 | 'defaultValue' => 'Y-m-d\TH:i:sP', 27 | ], 28 | ]; 29 | 30 | $config['fields'] = array_merge($config['fields'], $defaultFieldConfig); 31 | 32 | parent::__construct($config); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Documentation/ApigilityDocumentationProvider.php: -------------------------------------------------------------------------------- 1 | config = $config; 16 | } 17 | 18 | public function getEntity($entityName, array $options) 19 | { 20 | // Documentation for entities is stored in the documentation.php config file. 21 | // Fetching all those files is outside the scope of work for this class for now. 22 | return 'Doctrine Entity ' . $entityName; 23 | } 24 | 25 | /** 26 | * Populate the field documentation based on teh input filter 27 | * for the first matching entity found in zf-rest configuration 28 | */ 29 | public function getField($entityName, $fieldName, array $options) 30 | { 31 | $inputFilter = null; 32 | $description = null; 33 | 34 | if (! isset($this->config['zf-rest'])) { 35 | return null; 36 | } 37 | 38 | foreach ($this->config['zf-rest'] as $controllerName => $restConfig) { 39 | if ($restConfig['entity_class'] == $entityName) { 40 | $inputFilter = $this->config['zf-content-validation'][$controllerName]['input_filter'] ?? null; 41 | break; 42 | } 43 | } 44 | 45 | if ($inputFilter 46 | && isset($this->config['input_filter_specs']) 47 | && isset($this->config['input_filter_specs'][$inputFilter])) { 48 | foreach ($this->config['input_filter_specs'][$inputFilter] as $fieldConfig) { 49 | if ($fieldConfig['name'] == $fieldName) { 50 | $description = $fieldConfig['description'] ?? null; 51 | break; 52 | } 53 | } 54 | } 55 | 56 | return $description; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Documentation/ApigilityDocumentationProviderFactory.php: -------------------------------------------------------------------------------- 1 | get('config'); 18 | 19 | return new ApigilityDocumentationProvider($config); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Documentation/DocumentationProviderInterface.php: -------------------------------------------------------------------------------- 1 | config = $config; 13 | } 14 | 15 | public function getEntity($entityClassName, array $options) 16 | { 17 | $hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $entityClassName); 18 | $config = $this->config['zf-doctrine-graphql-hydrator'][$hydratorAlias][$options['hydrator_section']] ?? null; 19 | 20 | return $config['documentation']['_entity'] ?? null; 21 | } 22 | 23 | public function getField($entityClassName, $fieldName, array $options) 24 | { 25 | $hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $entityClassName); 26 | $config = $this->config['zf-doctrine-graphql-hydrator'][$hydratorAlias][$options['hydrator_section']] ?? null; 27 | 28 | return $config['documentation'][$fieldName] ?? null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Documentation/HydratorConfigurationDocumentationProviderFactory.php: -------------------------------------------------------------------------------- 1 | get('config'); 15 | 16 | return new HydratorConfigurationDocumentationProvider($config); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Event.php: -------------------------------------------------------------------------------- 1 | hydratorExtractTool = $hydratorExtractTool; 35 | $this->hydratorManager = $hydratorManager; 36 | } 37 | 38 | public function __invoke($source, $args, Context $context, ResolveInfo $info) 39 | { 40 | if (is_array($source)) { 41 | return $source[$info->fieldName]; 42 | } 43 | 44 | $entityClassName = ClassUtils::getRealClass(get_class($source)); 45 | $splObjectHash = spl_object_hash($source); 46 | $hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $entityClassName); 47 | 48 | // If the hydrator does not exist pass handling to default Executor field resolver 49 | // @codeCoverageIgnoreStart 50 | if (! $this->hydratorManager->has($hydratorAlias)) { 51 | return Executor::defaultFieldResolver($source, $args, $context, $info); 52 | } 53 | // @codeCoverageIgnoreEnd 54 | 55 | /** 56 | * For disabled hydrator cache store only last hydrator result and reuse for consecutive calls 57 | * then drop the cache if it doesn't hit. 58 | */ 59 | if (! $context->getUseHydratorCache()) { 60 | if (isset($this->extractValues[$splObjectHash])) { 61 | return $this->extractValues[$splObjectHash][$info->fieldName] ?? null; 62 | } else { 63 | $this->extractValues = []; 64 | } 65 | 66 | $this->extractValues[$splObjectHash] 67 | = $this->hydratorExtractTool->extract($source, $hydratorAlias, $context); 68 | 69 | return $this->extractValues[$splObjectHash][$info->fieldName] ?? null; 70 | } 71 | 72 | // Use full hydrator cache 73 | if (isset($this->extractValues[$splObjectHash][$info->fieldName])) { 74 | return $this->extractValues[$splObjectHash][$info->fieldName] ?? null; 75 | } 76 | 77 | $this->extractValues[$splObjectHash] = $this->hydratorExtractTool->extract($source, $hydratorAlias, $context); 78 | 79 | return $this->extractValues[$splObjectHash][$info->fieldName] ?? null; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Field/FieldResolverFactory.php: -------------------------------------------------------------------------------- 1 | get('ZF\\Doctrine\\GraphQL\\Hydrator\\HydratorExtractTool'); 15 | $hydratorManager = $container->get('HydratorManager'); 16 | 17 | return new FieldResolver($hydratorExtractTool, $hydratorManager); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Filter/FilterManager.php: -------------------------------------------------------------------------------- 1 | instanceOf) { 28 | throw new Exception\InvalidServiceException(sprintf( 29 | '%s can only create instances of %s; %s is invalid', 30 | get_class($this), 31 | $this->instanceOf, 32 | is_object($instance) ? get_class($instance) : gettype($instance) 33 | )); 34 | } 35 | } 36 | 37 | /** 38 | * Validate the plugin is of the expected type (v2). 39 | * 40 | * Proxies to `validate()`. 41 | * 42 | * @param mixed $plugin 43 | * @return void 44 | * @throws Exception\InvalidArgumentException 45 | * @codeCoverageIgnore 46 | */ 47 | public function validatePlugin($plugin) 48 | { 49 | try { 50 | $this->validate($plugin); 51 | } catch (Exception\InvalidServiceException $e) { 52 | throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Filter/FilterManagerFactory.php: -------------------------------------------------------------------------------- 1 | canCreate($services, $requestedName); 27 | } 28 | 29 | /** 30 | * @codeCoverageIgnore 31 | */ 32 | public function createServiceWithName(ServiceLocatorInterface $services, $name, $requestedName) 33 | { 34 | return $this($services, $requestedName); 35 | } 36 | 37 | /** 38 | * Loop through all configured ORM managers and if the passed $requestedName 39 | * as entity name is managed by the ORM return true; 40 | */ 41 | public function canCreate(ContainerInterface $container, $requestedName) 42 | { 43 | $config = $container->get('config'); 44 | $hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $requestedName); 45 | 46 | return isset($config['zf-doctrine-graphql-hydrator'][$hydratorAlias]); 47 | } 48 | 49 | public function __invoke(ContainerInterface $container, $requestedName, array $options = null) : FilterType 50 | { 51 | // @codeCoverageIgnoreStart 52 | if ($this->isCached($requestedName, $options)) { 53 | return $this->getCache($requestedName, $options); 54 | } 55 | // @codeCoverageIgnoreEnd 56 | 57 | parent::__invoke($container, $requestedName, $options); 58 | 59 | $fields = []; 60 | $config = $container->get('config'); 61 | $typeManager = $container->get(TypeManager::class); 62 | $hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $requestedName); 63 | $hydratorExtractTool = $container->get('ZF\\Doctrine\\GraphQL\\Hydrator\\HydratorExtractTool'); 64 | $objectManager = $container 65 | ->get( 66 | $config['zf-doctrine-graphql-hydrator'][$hydratorAlias][$options['hydrator_section']]['object_manager'] 67 | ); 68 | $filterManager = $container->get(ORMFilterManager::class); 69 | $criteriaFilterManager = $container->get(CriteriaFilterManager::class); 70 | $orderByManager = $container->get(ORMOrderByManager::class); 71 | 72 | // Get an array of the hydrator fields 73 | $entityFields = $hydratorExtractTool->getFieldArray($requestedName, $hydratorAlias, $options); 74 | 75 | $references = []; 76 | 77 | $classMetadata = $objectManager->getClassMetadata($requestedName); 78 | 79 | foreach ($entityFields as $fieldName) { 80 | $graphQLType = null; 81 | try { 82 | $fieldMetadata = $classMetadata->getFieldMapping($fieldName); 83 | } catch (MappingException $e) { 84 | // For all related data you cannot query on them from the top level resource 85 | continue; 86 | } 87 | 88 | $graphQLType = $this->mapFieldType($fieldMetadata['type'], $typeManager); 89 | 90 | if ($graphQLType && $classMetadata->isIdentifier($fieldMetadata['fieldName'])) { 91 | $graphQLType = Type::id(); 92 | } 93 | 94 | // Send event to allow overriding a field type 95 | $results = $this->getEventManager()->trigger( 96 | Event::MAP_FIELD_TYPE, 97 | $this, 98 | [ 99 | 'fieldName' => $fieldName, 100 | 'graphQLType' => $graphQLType, 101 | 'classMetadata' => $classMetadata, 102 | 'fieldMetadata' => $fieldMetadata, 103 | 'hydratorAlias' => $hydratorAlias, 104 | 'options' => $options, 105 | ] 106 | ); 107 | if ($results->stopped()) { 108 | $graphQLType = $results->last(); 109 | } 110 | 111 | if ($graphQLType) { 112 | if ($orderByManager->has('field')) { 113 | $fields[$fieldName . '_sort'] = [ 114 | 'name' => $fieldName . '_sort', 115 | 'type' => Type::string(), 116 | 'description' => 'Sort the result either ASC or DESC', 117 | ]; 118 | } 119 | 120 | if ($filterManager->has('eq')) { 121 | $fields[$fieldName] = [ 122 | 'name' => $fieldName, 123 | 'type' => $graphQLType, 124 | 'description' => 'Equals; same as name: value. DateTime not supported.', 125 | ]; 126 | 127 | // Add filters 128 | $fields[$fieldName . '_eq'] = [ 129 | 'name' => $fieldName . '_eq', 130 | 'type' => $graphQLType, 131 | 'description' => 'Equals; same as name: value. DateTime not supported.', 132 | ]; 133 | } 134 | 135 | if ($filterManager->has('neq')) { 136 | $fields[$fieldName . '_neq'] = [ 137 | 'name' => $fieldName . '_neq', 138 | 'type' => $graphQLType, 139 | 'description' => 'Not Equals', 140 | ]; 141 | } 142 | 143 | if ($filterManager->has('lt')) { 144 | $fields[$fieldName . '_lt'] = [ 145 | 'name' => $fieldName . '_lt', 146 | 'type' => $graphQLType, 147 | 'description' => 'Less Than', 148 | ]; 149 | } 150 | 151 | if ($filterManager->has('lte')) { 152 | $fields[$fieldName . '_lte'] = [ 153 | 'name' => $fieldName . '_lte', 154 | 'type' => $graphQLType, 155 | 'description' => 'Less Than or Equal To', 156 | ]; 157 | } 158 | 159 | if ($filterManager->has('gt')) { 160 | $fields[$fieldName . '_gt'] = [ 161 | 'name' => $fieldName . '_gt', 162 | 'type' => $graphQLType, 163 | 'description' => 'Greater Than', 164 | ]; 165 | } 166 | 167 | if ($filterManager->has('gte')) { 168 | $fields[$fieldName . '_gte'] = [ 169 | 'name' => $fieldName . '_gte', 170 | 'type' => $graphQLType, 171 | 'description' => 'Greater Than or Equal To', 172 | ]; 173 | } 174 | 175 | if ($filterManager->has('isnull')) { 176 | $fields[$fieldName . '_isnull'] = [ 177 | 'name' => $fieldName . '_isnull', 178 | 'type' => Type::boolean(), 179 | 'description' => 'Takes a boolean. If TRUE return results where the field is null. ' 180 | . 'If FALSE returns results where the field is not null. ' 181 | . 'NOTE: acts as "isEmpty" for collection filters. A value of false will ' 182 | . 'be handled as though it were null.', 183 | ]; 184 | } 185 | 186 | if ($filterManager->has('in')) { 187 | $fields[$fieldName . '_in'] = [ 188 | 'name' => $fieldName . '_in', 189 | 'type' => Type::listOf(Type::nonNull($graphQLType)), 190 | 'description' => 'Filter for values in an array', 191 | ]; 192 | } 193 | 194 | if ($filterManager->has('notin')) { 195 | $fields[$fieldName . '_notin'] = [ 196 | 'name' => $fieldName . '_notin', 197 | 'type' => Type::listOf(Type::nonNull($graphQLType)), 198 | 'description' => 'Filter for values not in an array', 199 | ]; 200 | } 201 | 202 | if ($filterManager->has('between')) { 203 | $fields[$fieldName . '_between'] = [ 204 | 'name' => $fieldName . '_between', 205 | 'description' => 'Filter between `from` and `to` values. Good substitute for DateTime Equals.', 206 | 'type' => new FilterTypeNS\Between(['fields' => [ 207 | 'from' => [ 208 | 'name' => 'from', 209 | 'type' => Type::nonNull($graphQLType), 210 | ], 211 | 'to' => [ 212 | 'name' => 'to', 213 | 'type' => Type::nonNull($graphQLType), 214 | ], 215 | ] 216 | ]), 217 | ]; 218 | } 219 | 220 | if ($filterManager->has('like') && $graphQLType == Type::string()) { 221 | $fields[$fieldName . '_contains'] = [ 222 | 'name' => $fieldName . '_contains', 223 | 'type' => Type::string(), 224 | 'description' => 'Strings only. Similar to a Like query as `like \'%value%\'`', 225 | ]; 226 | 227 | $fields[$fieldName . '_startswith'] = [ 228 | 'name' => $fieldName . '_startswith', 229 | 'type' => Type::string(), 230 | 'documentation' => 'Strings only. ' 231 | . 'A like query from the beginning of the value `like \'value%\'`', 232 | ]; 233 | 234 | $fields[$fieldName . '_endswith'] = [ 235 | 'name' => $fieldName . '_endswith', 236 | 'type' => Type::string(), 237 | 'documentation' => 'Strings only. ' 238 | . 'A like query from the end of the value `like \'%value\'`', 239 | ]; 240 | } 241 | 242 | if ($criteriaFilterManager->has('memberof')) { 243 | $fields[$fieldName . '_memberof'] = [ 244 | 'name' => $fieldName . '_memberof', 245 | 'type' => Type::string(), 246 | 'description' => 'Matches a value in an array field.', 247 | ]; 248 | } 249 | } 250 | $fields[$fieldName . '_distinct'] = [ 251 | 'name' => $fieldName . '_distinct', 252 | 'type' => Type::boolean(), 253 | 'description' => 'Return a unique list of fieldName. Only one distinct fieldName allowed per filter.', 254 | ]; 255 | } 256 | 257 | $fields['_skip'] = [ 258 | 'name' => '_skip', 259 | 'type' => Type::int(), 260 | 'documentation' => 'Skip forward x records from beginning of data set.', 261 | ]; 262 | $fields['_limit'] = [ 263 | 'name' => '_limit', 264 | 'type' => Type::int(), 265 | 'documentation' => 'Limit the number of results to x.', 266 | ]; 267 | 268 | $instance = new FilterType([ 269 | 'name' => str_replace('\\', '_', $requestedName) . '__Filter', 270 | 'fields' => function () use ($fields, $references) { 271 | foreach ($references as $referenceName => $resolve) { 272 | // @codeCoverageIgnoreStart 273 | // This works fine but may need bigger unit tests 274 | $fields[$referenceName] = $resolve(); 275 | // @codeCoverageIgnoreEnd 276 | } 277 | 278 | return $fields; 279 | }, 280 | ]); 281 | 282 | return $this->cache($requestedName, $options, $instance); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/Filter/Loader.php: -------------------------------------------------------------------------------- 1 | filterManager = $filterManager; 14 | } 15 | 16 | public function __invoke(string $name, Context $context = null) : FilterType 17 | { 18 | $context = $context ?? new Context(); 19 | 20 | return $this->filterManager->build($name, $context->toArray()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Filter/LoaderFactory.php: -------------------------------------------------------------------------------- 1 | get(FilterManager::class); 15 | 16 | return new Loader($filterManager); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Filter/Type/AbstractFilterType.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'name' => 'field', 14 | 'type' => Type::string(), 15 | ], 16 | 'where' => [ 17 | 'name' => 'where', 18 | 'type' => Type::string(), 19 | 'defaultValue' => 'and', 20 | ], 21 | 'format' => [ 22 | 'name' => 'format', 23 | 'type' => Type::string(), 24 | 'defaultValue' => 'Y-m-d\TH:i:sP', 25 | ], 26 | ]; 27 | 28 | $config['fields'] = array_merge($config['fields'], $defaultFieldConfig); 29 | 30 | parent::__construct($config); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Hydrator/DoctrineHydrator.php: -------------------------------------------------------------------------------- 1 | extractService = $extractService; 29 | $this->hydrateService = $hydrateService; 30 | } 31 | 32 | /** 33 | * @return \Zend\Hydrator\HydratorInterface 34 | * @codeCoverageIgnore 35 | */ 36 | public function getExtractService() 37 | { 38 | return $this->extractService; 39 | } 40 | 41 | /** 42 | * @return \Zend\Hydrator\HydratorInterface 43 | * @codeCoverageIgnore 44 | */ 45 | public function getHydrateService() 46 | { 47 | return $this->hydrateService; 48 | } 49 | 50 | /** 51 | * Extract values from an object. 52 | * 53 | * @param object $object 54 | * 55 | * @return array 56 | */ 57 | public function extract($object) 58 | { 59 | return $this->extractService->extract($object); 60 | } 61 | 62 | /** 63 | * Hydrate $object with the provided $data. 64 | * 65 | * @param array $data 66 | * @param object $object 67 | * 68 | * @return object 69 | * @codeCoverageIgnore 70 | */ 71 | public function hydrate(array $data, $object) 72 | { 73 | // Zend hydrator: 74 | if ($this->hydrateService instanceof HydratorInterface) { 75 | return $this->hydrateService->hydrate($data, $object); 76 | } 77 | 78 | // Doctrine hydrator: (parameters switched) 79 | return $this->hydrateService->hydrate($object, $data); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Hydrator/DoctrineHydratorFactory.php: -------------------------------------------------------------------------------- 1 | lookupCache)) { 57 | return $this->lookupCache[$requestedName]; 58 | } 59 | 60 | // @codeCoverageIgnoreStart 61 | if (! $container->has('config')) { 62 | return false; 63 | } 64 | 65 | // Validate object is set 66 | $config = $container->get('config'); 67 | $namespace = self::FACTORY_NAMESPACE; 68 | if (! isset($config[$namespace]) 69 | || ! is_array($config[$namespace]) 70 | || ! isset($config[$namespace][$requestedName]) 71 | ) { 72 | $this->lookupCache[$requestedName] = false; 73 | 74 | return false; 75 | } 76 | // @codeCoverageIgnoreEnd 77 | 78 | $this->lookupCache[$requestedName] = true; 79 | 80 | return true; 81 | } 82 | 83 | /** 84 | * Determine if we can create a service with name. (v2) 85 | * 86 | * Provided for backwards compatiblity; proxies to canCreate(). 87 | * 88 | * @param ServiceLocatorInterface $hydratorManager 89 | * @param string $name 90 | * @param string $requestedName 91 | * 92 | * @return bool 93 | * 94 | * @throws ServiceNotFoundException 95 | * @codeCoverageIgnore 96 | */ 97 | public function canCreateServiceWithName(ServiceLocatorInterface $hydratorManager, $name, $requestedName) 98 | { 99 | if (! $hydratorManager instanceof HydratorPluginManager) { 100 | throw new Exception('Invalid hydrator manager'); 101 | } 102 | 103 | return $this->canCreate($hydratorManager->getServiceLocator(), $requestedName); 104 | } 105 | 106 | /** 107 | * Create and return the database-connected resource. 108 | * 109 | * @param ContainerInterface $container 110 | * @param string $requestedName 111 | * @param null|array $options 112 | * 113 | * @return DoctrineHydrator 114 | */ 115 | public function __invoke(ContainerInterface $container, $requestedName, array $options = null) 116 | { 117 | $config = $container->get('config'); 118 | $config = $config[self::FACTORY_NAMESPACE][$requestedName][$options['hydrator_section']]; 119 | 120 | $objectManager = $this->loadObjectManager($container, $config); 121 | 122 | $extractService = null; 123 | $hydrateService = null; 124 | 125 | $useEntityHydrator = (array_key_exists('use_generated_hydrator', $config) && $config['use_generated_hydrator']); 126 | $useCustomHydrator = (array_key_exists('hydrator', $config)); 127 | 128 | if ($useEntityHydrator && $config['use_generated_hydrator']) { 129 | $hydrateService = $this->loadEntityHydrator($container, $config, $objectManager); 130 | } 131 | 132 | // @codeCoverageIgnoreStart 133 | if ($useCustomHydrator && $config['hydrator']) { 134 | $extractService = $container->get($config['hydrator']); 135 | $hydrateService = $extractService; 136 | } 137 | // @codeCoverageIgnoreEnd 138 | 139 | # Use DoctrineModuleHydrator by default 140 | if (! isset($extractService, $hydrateService)) { 141 | $doctrineModuleHydrator = $this->loadDoctrineModuleHydrator($container, $config, $objectManager); 142 | $extractService = ($extractService ?: $doctrineModuleHydrator); 143 | $hydrateService = ($hydrateService ?: $doctrineModuleHydrator); 144 | } 145 | 146 | $this->configureHydrator($extractService, $container, $config, $objectManager); 147 | $this->configureHydrator($hydrateService, $container, $config, $objectManager); 148 | 149 | return new DoctrineHydrator($extractService, $hydrateService); 150 | } 151 | 152 | /** 153 | * Create and return the database-connected resource (v2). 154 | * 155 | * Provided for backwards compatibility; proxies to __invoke(). 156 | * 157 | * @param ServiceLocatorInterface $hydratorManager 158 | * @param string $name 159 | * @param string $requestedName 160 | * 161 | * @return DoctrineHydrator 162 | * @codeCoverageIgnore 163 | */ 164 | public function createServiceWithName(ServiceLocatorInterface $hydratorManager, $name, $requestedName) 165 | { 166 | if (! $hydratorManager instanceof HydratorPluginManager) { 167 | throw new Exception('Invalid hydrator manager'); 168 | } 169 | 170 | return $this($hydratorManager->getServiceLocator(), $requestedName); 171 | } 172 | 173 | protected function getObjectManagerType($objectManager) : string 174 | { 175 | if (class_exists(EntityManager::class) && $objectManager instanceof EntityManager) { 176 | return 'ORM'; 177 | } 178 | 179 | // @codeCoverageIgnoreStart 180 | throw new ServiceNotCreatedException('Unknown object manager type: ' . get_class($objectManager)); 181 | // @codeCoverageIgnoreEnd 182 | } 183 | 184 | /** 185 | * @param ContainerInterface $container 186 | * @param array $config 187 | * 188 | * @return ObjectManager 189 | * 190 | * @throws ServiceNotCreatedException 191 | */ 192 | protected function loadObjectManager(ContainerInterface $container, $config) 193 | { 194 | // @codeCoverageIgnoreStart 195 | if (! $container->has($config['object_manager'])) { 196 | throw new ServiceNotCreatedException('The object_manager could not be found.'); 197 | } 198 | // @codeCoverageIgnoreEnd 199 | 200 | return $container->get($config['object_manager']); 201 | } 202 | 203 | protected function loadEntityHydrator(ContainerInterface $container, $config, $objectManager) 204 | { 205 | $objectManagerType = $this->getObjectManagerType($objectManager); 206 | 207 | return; 208 | } 209 | 210 | /** 211 | * @param ContainerInterface $container 212 | * @param array $config 213 | * @param ObjectManager $objectManager 214 | * 215 | * @return HydratorInterface 216 | */ 217 | protected function loadDoctrineModuleHydrator(ContainerInterface $container, $config, $objectManager) 218 | { 219 | $objectManagerType = $this->getObjectManagerType($objectManager); 220 | 221 | $hydrator = new DoctrineObject($objectManager, $config['by_value']); 222 | 223 | return $hydrator; 224 | } 225 | 226 | /** 227 | * @param AbstractHydrator $hydrator 228 | * @param ContainerInterface $container 229 | * @param array $config 230 | * @param ObjectManager $objectManager 231 | * 232 | * @throws ServiceNotCreatedException 233 | */ 234 | public function configureHydrator($hydrator, ContainerInterface $container, $config, $objectManager) 235 | { 236 | $this->configureHydratorFilters($hydrator, $container, $config, $objectManager); 237 | $this->configureHydratorStrategies($hydrator, $container, $config, $objectManager); 238 | $this->configureHydratorNamingStrategy($hydrator, $container, $config, $objectManager); 239 | } 240 | 241 | /** 242 | * @param AbstractHydrator $hydrator 243 | * @param ContainerInterface $container 244 | * @param array $config 245 | * @param ObjectManager $objectManager 246 | * 247 | * @throws ServiceNotCreatedException 248 | * @codeCoverageIgnore 249 | */ 250 | public function configureHydratorNamingStrategy($hydrator, ContainerInterface $container, $config, $objectManager) 251 | { 252 | if (! ($hydrator instanceof NamingStrategyEnabledInterface) || ! isset($config['naming_strategy'])) { 253 | return; 254 | } 255 | 256 | $namingStrategyKey = $config['naming_strategy']; 257 | if (! $container->has($namingStrategyKey)) { 258 | throw new ServiceNotCreatedException(sprintf('Invalid naming strategy %s.', $namingStrategyKey)); 259 | } 260 | 261 | $namingStrategy = $container->get($namingStrategyKey); 262 | if (! $namingStrategy instanceof NamingStrategyInterface) { 263 | throw new ServiceNotCreatedException( 264 | sprintf('Invalid naming strategy class %s', get_class($namingStrategy)) 265 | ); 266 | } 267 | 268 | // Attach object manager: 269 | if ($namingStrategy instanceof ObjectManagerAwareInterface) { 270 | $namingStrategy->setObjectManager($objectManager); 271 | } 272 | 273 | $hydrator->setNamingStrategy($namingStrategy); 274 | } 275 | 276 | /** 277 | * @param AbstractHydrator $hydrator 278 | * @param ContainerInterface $container 279 | * @param array $config 280 | * @param ObjectManager $objectManager 281 | * 282 | * @throws ServiceNotCreatedException 283 | */ 284 | protected function configureHydratorStrategies($hydrator, ContainerInterface $container, $config, $objectManager) 285 | { 286 | // @codeCoverageIgnoreStart 287 | if (! $hydrator instanceof StrategyEnabledInterface 288 | || ! isset($config['strategies']) 289 | || ! is_array($config['strategies']) 290 | ) { 291 | return; 292 | } 293 | // @codeCoverageIgnoreEnd 294 | 295 | foreach ($config['strategies'] as $field => $strategyKey) { 296 | // @codeCoverageIgnoreStart 297 | if (! $container->has($strategyKey)) { 298 | throw new ServiceNotCreatedException(sprintf('Invalid strategy %s for field %s', $strategyKey, $field)); 299 | } 300 | 301 | $strategy = $container->get($strategyKey); 302 | if (! $strategy instanceof StrategyInterface) { 303 | throw new ServiceNotCreatedException( 304 | sprintf('Invalid strategy class %s for field %s', get_class($strategy), $field) 305 | ); 306 | } 307 | // @codeCoverageIgnoreEnd 308 | 309 | // Attach object manager: 310 | // @codeCoverageIgnoreStart 311 | if ($strategy instanceof ObjectManagerAwareInterface) { 312 | $strategy->setObjectManager($objectManager); 313 | } 314 | // @codeCoverageIgnoreEnd 315 | 316 | $hydrator->addStrategy($field, $strategy); 317 | } 318 | } 319 | 320 | /** 321 | * Add filters to the Hydrator based on a predefined configuration format, if specified. 322 | * 323 | * @param AbstractHydrator $hydrator 324 | * @param ContainerInterface $container 325 | * @param array $config 326 | * @param ObjectManager $objectManager 327 | * 328 | * @throws ServiceNotCreatedException 329 | */ 330 | protected function configureHydratorFilters($hydrator, ContainerInterface $container, $config, $objectManager) 331 | { 332 | // @codeCoverageIgnoreStart 333 | if (! $hydrator instanceof FilterEnabledInterface 334 | || ! isset($config['filters']) 335 | || ! is_array($config['filters']) 336 | ) { 337 | return; 338 | } 339 | // @codeCoverageIgnoreEnd 340 | 341 | foreach ($config['filters'] as $name => $filterConfig) { 342 | $conditionMap = [ 343 | 'and' => FilterComposite::CONDITION_AND, 344 | 'or' => FilterComposite::CONDITION_OR, 345 | ]; 346 | $condition = isset($filterConfig['condition']) ? 347 | $conditionMap[$filterConfig['condition']] : 348 | FilterComposite::CONDITION_OR; 349 | 350 | $filterService = $filterConfig['filter']; 351 | // @codeCoverageIgnoreStart 352 | if (! $container->has($filterService)) { 353 | throw new ServiceNotCreatedException( 354 | sprintf('Invalid filter %s for field %s: service does not exist', $filterService, $name) 355 | ); 356 | } 357 | 358 | $filterService = $container->get($filterService); 359 | if (! $filterService instanceof FilterInterface) { 360 | throw new ServiceNotCreatedException( 361 | sprintf('Filter service %s must implement FilterInterface', get_class($filterService)) 362 | ); 363 | } 364 | 365 | if ($filterService instanceof ObjectManagerAwareInterface) { 366 | $filterService->setObjectManager($objectManager); 367 | } 368 | // @codeCoverageIgnoreEnd 369 | 370 | $hydrator->addFilter($name, $filterService, $condition); 371 | } 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /src/Hydrator/Filter/FilterDefault.php: -------------------------------------------------------------------------------- 1 | hydratorManager = $hydratorManager; 23 | } 24 | 25 | // Extract an array of entities and return a collection 26 | public function extractToCollection($entityArray, string $hydratorAlias, $options) 27 | { 28 | $options = $this->optionsToArray($options); 29 | $hydrator = $this->hydratorManager->build($hydratorAlias, $options); 30 | 31 | $resultCollection = new ArrayCollection(); 32 | foreach ($entityArray as $value) { 33 | // @codeCoverageIgnoreStart 34 | if (is_array($value)) { 35 | $resultCollection->add($value); 36 | // @codeCoverageIgnoreEnd 37 | } else { 38 | $resultCollection->add($hydrator->extract($value)); 39 | } 40 | } 41 | 42 | return $resultCollection; 43 | } 44 | 45 | // Extract a single entity 46 | public function extract($entity, string $hydratorAlias, $options) 47 | { 48 | // @codeCoverageIgnoreStart 49 | if (is_array($entity)) { 50 | return $entity; 51 | } 52 | // @codeCoverageIgnoreEnd 53 | 54 | $options = $this->optionsToArray($options); 55 | $hydrator = $this->hydratorManager->build($hydratorAlias, $options); 56 | 57 | return $hydrator->extract($entity); 58 | } 59 | 60 | public function getFieldArray(string $entityClassName, string $hydratorAlias, $options) 61 | { 62 | $instantiator = new Instantiator(); 63 | $entity = $instantiator->instantiate($entityClassName); 64 | 65 | $options = $this->optionsToArray($options); 66 | $hydrator = $this->hydratorManager->build($hydratorAlias, $options); 67 | 68 | return array_keys($hydrator->extract($entity)); 69 | } 70 | 71 | private function optionsToArray($options) 72 | { 73 | if ($options instanceof Context) { 74 | $options = $options->toArray(); 75 | } 76 | 77 | return $options; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Hydrator/HydratorExtractToolDefaultFactory.php: -------------------------------------------------------------------------------- 1 | get('HydratorManager'); 15 | 16 | return new HydratorExtractToolDefault($hydratorManager); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Hydrator/HydratorExtractToolInterface.php: -------------------------------------------------------------------------------- 1 | Role relationship a user may have many roles. But 16 | * a role may have many users. So in a query where a user is fetched then their 17 | * roles are fetched you could then reverse the query to fetch all users with the 18 | * same role 19 | * 20 | * This query would return all user names with the same roles as the user who 21 | * created the artist. 22 | * { artist { user { role { user { name } } } } } 23 | * 24 | * This hydrator strategy is used to prevent the reverse lookup by nullifying 25 | * the response when queried from the owning side of a many to many relationship 26 | * 27 | * Ideally the developer will add the owning relation to a filter so the 28 | * field is not queryable at all. This strategy exists as a patch for generating 29 | * a configuration skeleton. 30 | */ 31 | class NullifyOwningAssociation extends AbstractCollectionStrategy implements 32 | StrategyInterface 33 | { 34 | public function extract($value) 35 | { 36 | return null; 37 | } 38 | 39 | /** 40 | * @codeCoverageIgnore 41 | */ 42 | public function hydrate($value) 43 | { 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Hydrator/Strategy/ToBoolean.php: -------------------------------------------------------------------------------- 1 | $configProvider->getDependencyConfig(), 30 | 'hydrators' => $configProvider->getHydratorConfig(), 31 | 'controllers' => $configProvider->getControllerConfig(), 32 | 'console' => [ 33 | 'router' => $configProvider->getConsoleRouterConfig(), 34 | ], 35 | 'zf-doctrine-graphql-type' => $configProvider->getDoctrineGraphQLTypeConfig(), 36 | 'zf-doctrine-graphql-filter' => $configProvider->getDoctrineGraphQLFilterConfig(), 37 | 'zf-doctrine-graphql-criteria' => $configProvider->getDoctrineGraphQLCriteriaConfig(), 38 | 'zf-doctrine-graphql-resolve' => $configProvider->getDoctrineGraphQLResolveConfig(), 39 | ]; 40 | } 41 | 42 | /** 43 | * @codeCoverageIgnore 44 | */ 45 | public function getConsoleUsage(Console $console) 46 | { 47 | return [ 48 | 'graphql:config-skeleton [--hydrator-sections=] [--object-manager=]' 49 | => 'Create GraphQL configuration skeleton', 50 | ['', 'A comma delimited list of sections to generate.'], 51 | ['', 'Defaults to doctrine.entitymanager.orm_default.'], 52 | ]; 53 | } 54 | 55 | public function init(ModuleManagerInterface $manager) 56 | { 57 | // @codeCoverageIgnoreStart 58 | if (! $manager instanceof ModuleManager) { 59 | throw new Exception('Invalid module manager'); 60 | } 61 | // @codeCoverageIgnoreEnd 62 | 63 | $sm = $manager->getEvent()->getParam('ServiceManager'); 64 | $serviceListener = $sm->get('ServiceListener'); 65 | 66 | $serviceListener->addServiceManager( 67 | Type\TypeManager::class, 68 | 'zf-doctrine-graphql-type', 69 | ObjectType::class, 70 | 'getZFDoctrineGraphQLTypeConfig' 71 | ); 72 | 73 | $serviceListener->addServiceManager( 74 | Filter\FilterManager::class, 75 | 'zf-doctrine-graphql-filter', 76 | InputObjectType::class, 77 | 'getZFDoctrineGraphQLFilterConfig' 78 | ); 79 | 80 | $serviceListener->addServiceManager( 81 | Criteria\CriteriaManager::class, 82 | 'zf-doctrine-graphql-criteria', 83 | InputObjectType::class, 84 | 'getZFDoctrineGraphQLCriteriaConfig' 85 | ); 86 | 87 | $serviceListener->addServiceManager( 88 | Resolve\ResolveManager::class, 89 | 'zf-doctrine-graphql-resolve', 90 | 'function', 91 | 'getZFDoctrineGraphQLResolveConfig' 92 | ); 93 | } 94 | 95 | public function onBootstrap(EventInterface $event) 96 | { 97 | $fieldResolver = $event->getParam('application') 98 | ->getServiceManager() 99 | ->get(Field\FieldResolver::class) 100 | ; 101 | 102 | GraphQL::setDefaultFieldResolver($fieldResolver); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Resolve/EntityResolveAbstractFactory.php: -------------------------------------------------------------------------------- 1 | canCreate($services, $requestedName); 27 | } 28 | 29 | /** 30 | * @codeCoverageIgnore 31 | */ 32 | public function createServiceWithName(ServiceLocatorInterface $services, $name, $requestedName) 33 | { 34 | return $this($services, $requestedName); 35 | } 36 | 37 | public function canCreate(ContainerInterface $container, $requestedName) 38 | { 39 | $hydratorManager = $container->get('HydratorManager'); 40 | $hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $requestedName); 41 | 42 | return $hydratorManager->has($hydratorAlias); 43 | } 44 | 45 | public function __invoke(ContainerInterface $container, $requestedName, array $options = null) : Closure 46 | { 47 | // @codeCoverageIgnoreStart 48 | if ($this->isCached($requestedName, $options)) { 49 | return $this->getCache($requestedName, $options); 50 | } 51 | // @codeCoverageIgnoreEnd 52 | 53 | parent::__invoke($container, $requestedName, $options); 54 | 55 | $config = $container->get('config'); 56 | $hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $requestedName); 57 | $hydratorExtractTool = $container->get('ZF\\Doctrine\\GraphQL\\Hydrator\\HydratorExtractTool'); 58 | $filterManager = $container->get(ORMFilterManager::class); 59 | $orderByManager = $container->get(ORMOrderByManager::class); 60 | $criteriaFilterManager = $container->get(CriteriaFilterManager::class); 61 | $criteriaBuilder = $container->get(CriteriaBuilder::class); 62 | $objectManager = $container 63 | ->get( 64 | $config['zf-doctrine-graphql-hydrator'][$hydratorAlias][$options['hydrator_section']]['object_manager'] 65 | ); 66 | 67 | $instance = function ( 68 | $obj, 69 | $args, 70 | $context 71 | ) use ( 72 | $options, 73 | $hydratorAlias, 74 | $hydratorExtractTool, 75 | $objectManager, 76 | $requestedName, 77 | $filterManager, 78 | $orderByManager, 79 | $criteriaBuilder 80 | ) { 81 | 82 | // Allow listener to resolve function 83 | $results = $this->getEventManager()->trigger( 84 | Event::RESOLVE, 85 | $this, 86 | [ 87 | 'object' => $obj, 88 | 'arguments' => $args, 89 | 'context' => $context, 90 | 'hydratorAlias' => $hydratorAlias, 91 | 'objectManager' => $objectManager, 92 | 'entityClassName' => $requestedName, 93 | ] 94 | ); 95 | if ($results->stopped()) { 96 | return $results->last(); 97 | } 98 | 99 | // Build query builder from Query Provider 100 | $queryBuilder = ($objectManager->createQueryBuilder()) 101 | ->select('row') 102 | ->from($requestedName, 'row') 103 | ; 104 | $this->getEventManager()->trigger( 105 | Event::FILTER_QUERY_BUILDER, 106 | $this, 107 | [ 108 | 'object' => $obj, 109 | 'arguments' => $args, 110 | 'context' => $context, 111 | 'objectManager' => $objectManager, 112 | 'queryBuilder' => $queryBuilder, 113 | 'entityClassName' => $requestedName, 114 | ] 115 | ); 116 | 117 | // Resolve top level filters 118 | $filter = $args['filter'] ?? []; 119 | $filterArray = []; 120 | $orderByArray = []; 121 | $criteriaArray = []; 122 | $distinctField = null; 123 | $skip = 0; 124 | $limit = $options['limit']; 125 | foreach ($filter as $field => $value) { 126 | // Command fields 127 | if ($field == '_skip') { 128 | $skip = $value; 129 | continue; 130 | } 131 | 132 | if ($field == '_limit') { 133 | if ($value <= $options['limit']) { 134 | $limit = $value; 135 | } 136 | continue; 137 | } 138 | 139 | // Handle most fields as $field_$type: $value 140 | // Get right-most _text 141 | $filter = substr($field, strrpos($field, '_') + 1); 142 | if (strpos($field, '_') === false || ! $this->isFilter($filter)) { 143 | // Handle field:value 144 | $filterArray[] = [ 145 | 'type' => 'eq', 146 | 'field' => $field, 147 | 'value' => $value, 148 | ]; 149 | } elseif (strpos($field, '_') !== false && $this->isFilter($filter)) { 150 | $field = substr($field, 0, (int)strrpos($field, '_')); 151 | 152 | switch ($filter) { 153 | case 'sort': 154 | $orderByArray[] = [ 155 | 'type' => 'field', 156 | 'field' => $field, 157 | 'direction' => $value, 158 | ]; 159 | break; 160 | case 'contains': 161 | $filterArray[] = [ 162 | 'type' => 'like', 163 | 'field' => $field, 164 | 'value' => '%' . $value . '%', 165 | ]; 166 | break; 167 | case 'startswith': 168 | $filterArray[] = [ 169 | 'type' => 'like', 170 | 'field' => $field, 171 | 'value' => $value . '%', 172 | ]; 173 | break; 174 | case 'endswith': 175 | $filterArray[] = [ 176 | 'type' => 'like', 177 | 'field' => $field, 178 | 'value' => '%' . $value, 179 | ]; 180 | break; 181 | case 'between': 182 | $value['type'] = $filter; 183 | $value['field'] = $field; 184 | $filterArray[] = $value; 185 | break; 186 | case 'in': 187 | $filterArray[] = [ 188 | 'type' => 'in', 189 | 'field' => $field, 190 | 'values' => $value, 191 | ]; 192 | break; 193 | case 'notin': 194 | $filterArray[] = [ 195 | 'type' => 'notin', 196 | 'field' => $field, 197 | 'values' => $value, 198 | ]; 199 | break; 200 | case 'isnull': 201 | if ($value === true) { 202 | $filterArray[] = [ 203 | 'type' => 'isnull', 204 | 'field' => $field, 205 | 'values' => null, 206 | ]; 207 | } else { 208 | $filterArray[] = [ 209 | 'type' => 'isnotnull', 210 | 'field' => $field, 211 | 'values' => null, 212 | ]; 213 | } 214 | break; 215 | case 'distinct': 216 | if (! $distinctField && $value) { 217 | $distinctField = $field; 218 | } 219 | break; 220 | case 'memberof': 221 | $criteriaArray[] = [ 222 | 'type' => 'memberof', 223 | 'field' => $field, 224 | 'value' => $value, 225 | ]; 226 | break; 227 | default: 228 | $filterArray[] = [ 229 | 'type' => $filter, 230 | 'field' => $field, 231 | 'value' => $value, 232 | ]; 233 | break; 234 | } 235 | } 236 | } 237 | 238 | // Process fitlers through filter manager 239 | $metadata = $objectManager->getClassMetadata($requestedName); 240 | if ($filterArray) { 241 | foreach ($filterArray as $key => $filter) { 242 | $filterArray[$key]['format'] = 'Y-m-d\TH:i:sP'; 243 | } 244 | 245 | $filterManager->filter( 246 | $queryBuilder, 247 | $metadata, 248 | $filterArray 249 | ); 250 | } 251 | if ($orderByArray) { 252 | $orderByManager->orderBy( 253 | $queryBuilder, 254 | $metadata, 255 | $orderByArray 256 | ); 257 | } 258 | if ($skip) { 259 | $queryBuilder->setFirstResult($skip); 260 | } 261 | if ($limit) { 262 | $queryBuilder->setMaxResults($limit); 263 | } 264 | 265 | // Fetch from Query Builder 266 | $results = $queryBuilder->getQuery()->getResult(); 267 | 268 | // Build hydrated result collection 269 | $resultCollection = $hydratorExtractTool->extractToCollection($results, $hydratorAlias, $options); 270 | 271 | // Criteria post filter 272 | if ($criteriaArray) { 273 | $criteria = $criteriaBuilder->create($metadata, $criteriaArray, []); 274 | $resultCollection = $resultCollection->matching($criteria); 275 | } 276 | 277 | // Distinct post filter 278 | if ($distinctField) { 279 | $distinctValueCollection = new ArrayCollection(); 280 | foreach ($resultCollection as $key => $value) { 281 | if (! $distinctValueCollection->contains($value[$distinctField])) { 282 | $distinctValueCollection->add($value[$distinctField]); 283 | } else { 284 | $resultCollection->remove($key); 285 | } 286 | } 287 | } 288 | 289 | // Allow listener to resolve post function 290 | $results = $this->getEventManager()->trigger( 291 | Event::RESOLVE_POST, 292 | $this, 293 | [ 294 | 'object' => $obj, 295 | 'arguments' => $args, 296 | 'context' => $context, 297 | 'resultCollection' => $resultCollection, 298 | 'hydratorAlias' => $hydratorAlias, 299 | 'objectManager' => $objectManager, 300 | 'queryBuilder' => $queryBuilder, 301 | 'entityClassName' => $requestedName, 302 | ] 303 | ); 304 | if ($results->stopped()) { 305 | return $results->last(); 306 | } 307 | 308 | return $resultCollection->toArray(); 309 | }; 310 | 311 | return $this->cache($requestedName, $options, $instance); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/Resolve/Loader.php: -------------------------------------------------------------------------------- 1 | resolveManager = $resolveManager; 14 | } 15 | 16 | public function __invoke(string $name, Context $context = null) 17 | { 18 | $context = $context ?? new Context(); 19 | 20 | return $this->resolveManager->build($name, $context->toArray()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Resolve/LoaderFactory.php: -------------------------------------------------------------------------------- 1 | get(ResolveManager::class); 15 | 16 | return new Loader($resolveManager); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Resolve/ResolveManager.php: -------------------------------------------------------------------------------- 1 | instanceOf) { 28 | throw new Exception\InvalidServiceException(sprintf( 29 | '%s can only create instances of %s; %s is invalid', 30 | get_class($this), 31 | $this->instanceOf, 32 | is_object($instance) ? get_class($instance) : gettype($instance) 33 | )); 34 | } 35 | } 36 | 37 | /** 38 | * Validate the plugin is of the expected type (v2). 39 | * 40 | * Proxies to `validate()`. 41 | * 42 | * @param mixed $plugin 43 | * @return void 44 | * @throws Exception\InvalidArgumentException 45 | * @codeCoverageIgnore 46 | */ 47 | public function validatePlugin($plugin) 48 | { 49 | try { 50 | $this->validate($plugin); 51 | } catch (Exception\InvalidServiceException $e) { 52 | throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Resolve/ResolveManagerFactory.php: -------------------------------------------------------------------------------- 1 | kind, [$valueNode]); 36 | } 37 | 38 | return $valueNode->value; 39 | } 40 | 41 | /** 42 | * @codeCoverageIgnore 43 | */ 44 | public function parseValue($value) 45 | { 46 | if (! is_string($value)) { 47 | $stringValue = print_r($value, true); 48 | throw new \UnexpectedValueException('Date is not a string: ' . $stringValue); 49 | } 50 | 51 | return DateTime::createFromFormat('Y-m-d\TH:i:sP', $value); 52 | } 53 | 54 | public function serialize($value) 55 | { 56 | if ($value instanceof DateTime) { 57 | $value = $value->format('c'); 58 | } 59 | 60 | return $value; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Type/EntityType.php: -------------------------------------------------------------------------------- 1 | typeManager = $typeManager; 14 | } 15 | 16 | public function __invoke(string $name, Context $context = null) : EntityType 17 | { 18 | $context = $context ?? new Context(); 19 | 20 | return $this->typeManager->build($name, $context->toArray()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Type/LoaderFactory.php: -------------------------------------------------------------------------------- 1 | get(TypeManager::class); 15 | 16 | return new Loader($typeManager); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Type/TypeManager.php: -------------------------------------------------------------------------------- 1 | instanceOf) { 28 | throw new Exception\InvalidServiceException(sprintf( 29 | '%s can only create instances of %s; %s is invalid', 30 | get_class($this), 31 | $this->instanceOf, 32 | is_object($instance) ? get_class($instance) : gettype($instance) 33 | )); 34 | } 35 | } 36 | 37 | /** 38 | * Validate the plugin is of the expected type (v2). 39 | * 40 | * Proxies to `validate()`. 41 | * 42 | * @param mixed $plugin 43 | * @return void 44 | * @throws Exception\InvalidArgumentException 45 | * @codeCoverageIgnore 46 | */ 47 | public function validatePlugin($plugin) 48 | { 49 | try { 50 | $this->validate($plugin); 51 | } catch (Exception\InvalidServiceException $e) { 52 | throw new Exception\InvalidArgumentException($e->getMessage(), $e->getCode(), $e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Type/TypeManagerFactory.php: -------------------------------------------------------------------------------- 1 | setApplicationConfig( 23 | include __DIR__ . '/config/test.config.php' 24 | ); 25 | parent::setUp(); 26 | 27 | $serviceManager = $this->getApplication()->getServiceManager(); 28 | $objectManager = $serviceManager->get('doctrine.entitymanager.orm_default'); 29 | $config = $serviceManager->get('config'); 30 | 31 | // Create Default Database 32 | $metadata = $objectManager->getMetadataFactory()->getAllMetadata(); 33 | $schemaTool = new SchemaTool($objectManager); 34 | $sql = $schemaTool->getCreateSchemaSql($metadata); 35 | 36 | foreach ($sql as $command) { 37 | $objectManager->getConnection()->exec($command); 38 | } 39 | 40 | // Add fixtures 41 | $artist1 = new Entity\Artist(); 42 | $artist1->name = 'artist1'; 43 | $artist1->createdAt = new DateTime('2010-02-01'); 44 | $artist1->alias = ['a1', 'a2', 'a3']; 45 | $objectManager->persist($artist1); 46 | 47 | $performance1 = new Entity\Performance(); 48 | $performance1->artist = $artist1; 49 | $performance1->performanceDate = '2011-01-01'; 50 | $performance1->venue = 'venue1'; 51 | $performance1->attendance = 1000; 52 | $performance1->isTradable = true; 53 | $performance1->ticketPrice = 10.01; 54 | $objectManager->persist($performance1); 55 | $artist1->performance->add($performance1); 56 | 57 | $performance2 = new Entity\Performance(); 58 | $performance2->artist = $artist1; 59 | $performance2->performanceDate = '2011-01-02'; 60 | $performance2->venue = 'venue2'; 61 | $performance2->attendance = 2000; 62 | $performance2->isTradable = null; 63 | $performance2->ticketPrice = 20.01; 64 | $objectManager->persist($performance2); 65 | $artist1->performance->add($performance2); 66 | 67 | $performance3 = new Entity\Performance(); 68 | $performance3->artist = $artist1; 69 | $performance3->performanceDate = '2011-01-03'; 70 | $performance3->venue = 'venue3'; 71 | $performance3->attendance = 2000; 72 | $performance3->isTradable = false; 73 | $performance3->ticketPrice = 30.01; 74 | $objectManager->persist($performance3); 75 | $artist1->performance->add($performance3); 76 | 77 | $performance4 = new Entity\Performance(); 78 | $performance4->artist = $artist1; 79 | $performance4->performanceDate = '2011-01-04'; 80 | $performance4->venue = 'venue4'; 81 | $performance4->attendance = 4000; 82 | $performance4->isTradable = false; 83 | $performance4->ticketPrice = 40.01; 84 | $objectManager->persist($performance4); 85 | $artist1->performance->add($performance4); 86 | 87 | $performance5 = new Entity\Performance(); 88 | $performance5->artist = $artist1; 89 | $performance5->performanceDate = '2011-01-05'; 90 | $performance5->venue = 'venue5'; 91 | $performance5->attendance = 5000; 92 | $performance5->isTradable = true; 93 | $performance5->ticketPrice = 50.01; 94 | $objectManager->persist($performance5); 95 | $artist1->performance->add($performance5); 96 | 97 | $artist2 = new Entity\Artist(); 98 | $artist2->name = 'artist2'; 99 | $artist2->createdAt = new DateTime('2010-02-02'); 100 | $artist2->alias = ['b1', 'b2', 'b3']; 101 | $objectManager->persist($artist2); 102 | 103 | $artist3 = new Entity\Artist(); 104 | $artist3->name = 'artist3'; 105 | $artist3->createdAt = new DateTime('2010-02-03'); 106 | $artist3->alias = ['c1', 'c2', 'c3']; 107 | $objectManager->persist($artist3); 108 | 109 | $artist4 = new Entity\Artist(); 110 | $artist4->name = 'artist4'; 111 | $artist4->createdAt = new DateTime('2010-02-04'); 112 | $artist4->alias = ['d1', 'd2', 'd3']; 113 | $objectManager->persist($artist4); 114 | 115 | $artist5 = new Entity\Artist(); 116 | $artist5->name = 'artist5'; 117 | $artist5->createdAt = new DateTime('2010-02-05'); 118 | $artist5->alias = ['e1', 'e2', 'e3']; 119 | $objectManager->persist($artist5); 120 | 121 | 122 | $user1 = new Entity\User(); 123 | $user1->name = 'test1'; 124 | $user1->password = 'secret'; 125 | $user1->createdAt = new DateTime('2010-01-01'); 126 | 127 | $address = new Entity\Address(); 128 | $address->user= $user1; 129 | $address->address = 'address1'; 130 | $user1->address = $address; 131 | 132 | $objectManager->persist($address); 133 | $objectManager->persist($user1); 134 | 135 | $user = new Entity\User(); 136 | $user->name = 'test2'; 137 | $user->password = 'secret'; 138 | $user->createdAt = new DateTime('2010-01-02'); 139 | 140 | $address = new Entity\Address(); 141 | $address->user= $user; 142 | $address->address = 'address2'; 143 | $user->address = $address; 144 | 145 | $objectManager->persist($address); 146 | $objectManager->persist($user); 147 | 148 | $user = new Entity\User(); 149 | $user->name = 'test3'; 150 | $user->password = 'secret'; 151 | $user->createdAt = new DateTime('2010-01-03'); 152 | 153 | $address = new Entity\Address(); 154 | $address->user= $user; 155 | $address->address = 'address3'; 156 | $user->address = $address; 157 | 158 | $objectManager->persist($address); 159 | $objectManager->persist($user); 160 | 161 | $user = new Entity\User(); 162 | $user->name = 'test4'; 163 | $user->password = 'secret'; 164 | $user->createdAt = new DateTime('2010-01-04'); 165 | 166 | $address = new Entity\Address(); 167 | $address->user= $user; 168 | $address->address = 'address4'; 169 | $user->address = $address; 170 | 171 | $objectManager->persist($address); 172 | $objectManager->persist($user); 173 | 174 | $user = new Entity\User(); 175 | $user->name = 'test5'; 176 | $user->password = 'secret'; 177 | $user->createdAt = new DateTime('2010-01-05'); 178 | 179 | $address = new Entity\Address(); 180 | $address->user= $user; 181 | $address->address = 'address5'; 182 | $user->address = $address; 183 | 184 | $objectManager->persist($address); 185 | $objectManager->persist($user); 186 | 187 | $user1->artist->add($artist1); 188 | $artist1->user->add($user1); 189 | $user1->artist->add($artist2); 190 | $artist2->user->add($user1); 191 | $user1->artist->add($artist3); 192 | $artist3->user->add($user1); 193 | $user1->artist->add($artist4); 194 | $artist4->user->add($user1); 195 | $user1->artist->add($artist5); 196 | $artist5->user->add($user1); 197 | 198 | $objectManager->flush(); 199 | $objectManager->clear(); 200 | } 201 | 202 | protected function getObjectManager() 203 | { 204 | return $this->getApplication() 205 | ->getServiceManager() 206 | ->get('doctrine.entitymanager.orm_default') 207 | ; 208 | } 209 | 210 | public function schemaDataProvider() { 211 | $testContext = new Context(); 212 | $testContext->setHydratorSection('test'); 213 | $testContext->setUseHydratorCache(true); 214 | $testContext->setLimit(1000); 215 | 216 | $providers = [ 217 | [ 218 | 'schemaName' => 'default', 219 | 'context' => new Context(), 220 | ], 221 | [ 222 | 'schemaName' => 'test', 223 | 'context' => $testContext, 224 | ], 225 | ]; 226 | 227 | return $providers; 228 | } 229 | 230 | public function eventDataProvider() { 231 | $eventContext = new Context(); 232 | $eventContext->setHydratorSection('event'); 233 | $eventContext->setUseHydratorCache(false); 234 | $eventContext->setLimit(1000); 235 | 236 | $providers = [ 237 | [ 238 | 'schemaName' => 'event', 239 | 'context' => $eventContext, 240 | ], 241 | ]; 242 | 243 | return $providers; 244 | } 245 | 246 | protected function getSchema($schemaName) 247 | { 248 | switch ($schemaName) { 249 | case 'default': 250 | return $this->getDefaultSchema(); 251 | case 'test': 252 | return $this->getTestSchema(); 253 | case 'event': 254 | return $this->getEventSchema(); 255 | } 256 | } 257 | 258 | protected function getDefaultSchema() 259 | { 260 | $serviceManager = $this->getApplication()->getServiceManager(); 261 | $typeLoader = $serviceManager->get(TypeLoader::class); 262 | $filterLoader = $serviceManager->get(FilterLoader::class); 263 | $resolveLoader = $serviceManager->get(ResolveLoader::class); 264 | 265 | $context = new Context(); 266 | 267 | $schema = new Schema([ 268 | 'query' => new ObjectType([ 269 | 'name' => 'query', 270 | 'fields' => [ 271 | 'artist' => [ 272 | 'type' => Type::listOf($typeLoader(Entity\Artist::class, $context)), 273 | 'args' => [ 274 | 'filter' => $filterLoader(Entity\Artist::class, $context), 275 | ], 276 | 'resolve' => $resolveLoader(Entity\Artist::class, $context), 277 | ], 278 | 'performance' => [ 279 | 'type' => Type::listOf($typeLoader(Entity\Performance::class, $context)), 280 | 'args' => [ 281 | 'filter' => $filterLoader(Entity\Performance::class, $context), 282 | ], 283 | 'resolve' => $resolveLoader(Entity\Performance::class, $context), 284 | ], 285 | 'user' => [ 286 | 'type' => Type::listOf($typeLoader(Entity\User::class, $context)), 287 | 'args' => [ 288 | 'filter' => $filterLoader(Entity\User::class, $context), 289 | ], 290 | 'resolve' => $resolveLoader(Entity\User::class, $context), 291 | ], 292 | 'address' => [ 293 | 'type' => Type::listOf($typeLoader(Entity\Address::class, $context)), 294 | 'args' => [ 295 | 'filter' => $filterLoader(Entity\Address::class, $context), 296 | ], 297 | 'resolve' => $resolveLoader(Entity\Address::class, $context), 298 | ], 299 | ], 300 | ]), 301 | ]); 302 | 303 | return $schema; 304 | } 305 | 306 | protected function getTestSchema() 307 | { 308 | $serviceManager = $this->getApplication()->getServiceManager(); 309 | $typeLoader = $serviceManager->get(TypeLoader::class); 310 | $filterLoader = $serviceManager->get(FilterLoader::class); 311 | $resolveLoader = $serviceManager->get(ResolveLoader::class); 312 | 313 | $context = new Context(); 314 | $context->setHydratorSection('test'); 315 | $context->setUseHydratorCache(false); 316 | 317 | $schema = new Schema([ 318 | 'query' => new ObjectType([ 319 | 'name' => 'query', 320 | 'fields' => [ 321 | 'artist' => [ 322 | 'type' => Type::listOf($typeLoader(Entity\Artist::class, $context)), 323 | 'args' => [ 324 | 'filter' => $filterLoader(Entity\Artist::class, $context), 325 | ], 326 | 'resolve' => $resolveLoader(Entity\Artist::class, $context), 327 | ], 328 | 'performance' => [ 329 | 'type' => Type::listOf($typeLoader(Entity\Performance::class, $context)), 330 | 'args' => [ 331 | 'filter' => $filterLoader(Entity\Performance::class, $context), 332 | ], 333 | 'resolve' => $resolveLoader(Entity\Performance::class, $context), 334 | ], 335 | 'user' => [ 336 | 'type' => Type::listOf($typeLoader(Entity\User::class, $context)), 337 | 'args' => [ 338 | 'filter' => $filterLoader(Entity\User::class, $context), 339 | ], 340 | 'resolve' => $resolveLoader(Entity\User::class, $context), 341 | ], 342 | 'address' => [ 343 | 'type' => Type::listOf($typeLoader(Entity\Address::class, $context)), 344 | 'args' => [ 345 | 'filter' => $filterLoader(Entity\Address::class, $context), 346 | ], 347 | 'resolve' => $resolveLoader(Entity\Address::class, $context), 348 | ], 349 | ], 350 | ]), 351 | ]); 352 | 353 | return $schema; 354 | } 355 | 356 | protected function getEventSchema() 357 | { 358 | $serviceManager = $this->getApplication()->getServiceManager(); 359 | $typeLoader = $serviceManager->get(TypeLoader::class); 360 | $filterLoader = $serviceManager->get(FilterLoader::class); 361 | $resolveLoader = $serviceManager->get(ResolveLoader::class); 362 | 363 | $context = new Context(); 364 | $context->setHydratorSection('event'); 365 | $context->setUseHydratorCache(false); 366 | 367 | $schema = new Schema([ 368 | 'query' => new ObjectType([ 369 | 'name' => 'query', 370 | 'fields' => [ 371 | 'artist' => [ 372 | 'type' => Type::listOf($typeLoader(Entity\Artist::class, $context)), 373 | 'args' => [ 374 | 'filter' => $filterLoader(Entity\Artist::class, $context), 375 | ], 376 | 'resolve' => $resolveLoader(Entity\Artist::class, $context), 377 | ], 378 | ], 379 | ]), 380 | ]); 381 | 382 | return $schema; 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /test/Bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 41 | 'autoregister_zf' => true, 42 | 'namespaces' => [ 43 | __NAMESPACE__ => __DIR__, 44 | ], 45 | ], 46 | ]); 47 | } 48 | 49 | protected static function findParentPath($path) 50 | { 51 | $dir = __DIR__; 52 | $previousDir = '.'; 53 | while (! is_dir($dir . '/' . $path)) { 54 | $dir = dirname($dir); 55 | if ($previousDir === $dir) { 56 | return false; 57 | } 58 | $previousDir = $dir; 59 | } 60 | return $dir . '/' . $path; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/GraphQL/AddressTest.php: -------------------------------------------------------------------------------- 1 | getSchema($schemaName); 17 | 18 | $query = "{ address { id address } }"; 19 | 20 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 21 | $output = $result->toArray(); 22 | 23 | $this->assertEquals(5, sizeof($output['data']['address'])); 24 | } 25 | 26 | /** 27 | * @dataProvider schemaDataProvider 28 | */ 29 | public function testAddressFilterId($schemaName, $context) 30 | { 31 | $schema = $this->getSchema($schemaName); 32 | 33 | $query = "{ address (filter: { id:1 }) { id address } }"; 34 | 35 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 36 | $output = $result->toArray(); 37 | 38 | $this->assertEquals(1, sizeof($output['data']['address'])); 39 | } 40 | 41 | /** 42 | * @dataProvider schemaDataProvider 43 | */ 44 | public function testAddressUser1to1($schemaName, $context) 45 | { 46 | $schema = $this->getSchema($schemaName); 47 | 48 | $query = "{ address (filter: { id:1 }) { id address user { id name } } }"; 49 | 50 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 51 | $output = $result->toArray(); 52 | 53 | $this->assertEquals('test1', $output['data']['address'][0]['user']['name']); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/GraphQL/ArtistTest.php: -------------------------------------------------------------------------------- 1 | getSchema($schemaName); 17 | 18 | $query = "{ artist { id name createdAt } }"; 19 | 20 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 21 | $output = $result->toArray(); 22 | 23 | $this->assertEquals(5, sizeof($output['data']['artist'])); 24 | } 25 | 26 | /** 27 | * @dataProvider schemaDataProvider 28 | */ 29 | public function testArtistPerformanceOneToMany($schemaName, $context) 30 | { 31 | $schema = $this->getSchema($schemaName); 32 | 33 | $query = "{ artist ( filter: { id: 1 } ) { id performance { id } } }"; 34 | 35 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 36 | $output = $result->toArray(); 37 | 38 | $this->assertEquals(5, sizeof($output['data']['artist'][0]['performance'])); 39 | } 40 | 41 | /** 42 | * @dataProvider schemaDataProvider 43 | */ 44 | public function testArtistUserManyToManyIsBlockedBecauseArtistIsOwner($schemaName, $context) 45 | { 46 | $schema = $this->getSchema($schemaName); 47 | 48 | $query = "{ artist { id user { id } } }"; 49 | 50 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 51 | $output = $result->toArray(); 52 | 53 | $this->assertEmpty($output['data']['artist'][0]['user']); 54 | } 55 | 56 | /** 57 | * @dataProvider schemaDataProvider 58 | */ 59 | public function testArtistAliasArrayField($schemaName, $context) 60 | { 61 | $schema = $this->getSchema($schemaName); 62 | 63 | $query = "{ artist ( filter: { name:\"artist1\" } ) { id alias } }"; 64 | 65 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 66 | $output = $result->toArray(); 67 | 68 | $this->assertEquals(['a1', 'a2', 'a3'], $output['data']['artist'][0]['alias']); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/GraphQL/ContextTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(1000, $context->getLimit()); 15 | $this->assertEquals('default', $context->getHydratorSection()); 16 | $this->assertEquals(false, $context->getUseHydratorCache()); 17 | } 18 | 19 | public function testContextObjectCustom() 20 | { 21 | $context = new Context(); 22 | $context->setHydratorSection('test'); 23 | $context->setUseHydratorCache(true); 24 | $context->setLimit(2000); 25 | 26 | $this->assertEquals(2000, $context->getLimit()); 27 | $this->assertEquals('test', $context->getHydratorSection()); 28 | $this->assertEquals(true, $context->getUseHydratorCache()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/GraphQL/CriteriaFiltersTest.php: -------------------------------------------------------------------------------- 1 | getSchema($schemaName); 17 | 18 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { id_eq: 1 } ) { id performanceDate } } }"; 19 | 20 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 21 | $output = $result->toArray(); 22 | 23 | $this->assertEquals(1, sizeof($output['data']['artist'][0]['performance'])); 24 | } 25 | 26 | /** 27 | * @dataProvider schemaDataProvider 28 | */ 29 | public function testNotEquals($schemaName, $context) 30 | { 31 | $schema = $this->getSchema($schemaName); 32 | 33 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { id_neq: 1 } ) { id performanceDate } } }"; 34 | 35 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 36 | $output = $result->toArray(); 37 | 38 | $this->assertEquals(4, sizeof($output['data']['artist'][0]['performance'])); 39 | } 40 | 41 | /** 42 | * @dataProvider schemaDataProvider 43 | */ 44 | public function testGreaterThan($schemaName, $context) 45 | { 46 | $schema = $this->getSchema($schemaName); 47 | 48 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { id_gt: 1 } ) { id performanceDate } } }"; 49 | 50 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 51 | $output = $result->toArray(); 52 | 53 | $this->assertEquals(4, sizeof($output['data']['artist'][0]['performance'])); 54 | } 55 | 56 | /** 57 | * @dataProvider schemaDataProvider 58 | */ 59 | public function testGreaterThanOrEquals($schemaName, $context) 60 | { 61 | $schema = $this->getSchema($schemaName); 62 | 63 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { id_gte: 2 } ) { id performanceDate } } }"; 64 | 65 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 66 | $output = $result->toArray(); 67 | 68 | $this->assertEquals(4, sizeof($output['data']['artist'][0]['performance'])); 69 | } 70 | 71 | /** 72 | * @dataProvider schemaDataProvider 73 | */ 74 | public function testLessThan($schemaName, $context) 75 | { 76 | $schema = $this->getSchema($schemaName); 77 | 78 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { id_lt: 2 } ) { id performanceDate } } }"; 79 | 80 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 81 | $output = $result->toArray(); 82 | 83 | $this->assertEquals(1, sizeof($output['data']['artist'][0]['performance'])); 84 | } 85 | 86 | /** 87 | * @dataProvider schemaDataProvider 88 | */ 89 | public function testLessThanOrEquals($schemaName, $context) 90 | { 91 | $schema = $this->getSchema($schemaName); 92 | 93 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { id_lte: 2 } ) { id performanceDate } } }"; 94 | 95 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 96 | $output = $result->toArray(); 97 | 98 | $this->assertEquals(2, sizeof($output['data']['artist'][0]['performance'])); 99 | } 100 | 101 | /** 102 | * @dataProvider schemaDataProvider 103 | */ 104 | public function testIsNull($schemaName, $context) 105 | { 106 | $schema = $this->getSchema($schemaName); 107 | 108 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { isTradable_isnull:true } ) { id performanceDate } } }"; 109 | 110 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 111 | $output = $result->toArray(); 112 | 113 | $this->assertEquals(2, sizeof($output['data']['artist'][0]['performance'])); 114 | } 115 | 116 | /** 117 | * @dataProvider schemaDataProvider 118 | */ 119 | public function testIsNotNull($schemaName, $context) 120 | { 121 | $schema = $this->getSchema($schemaName); 122 | 123 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { isTradable_isnull:false } ) { id performanceDate } } }"; 124 | 125 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 126 | $output = $result->toArray(); 127 | 128 | $this->assertEquals(2, sizeof($output['data']['artist'][0]['performance'])); 129 | } 130 | 131 | /** 132 | * @dataProvider schemaDataProvider 133 | */ 134 | public function testIn($schemaName, $context) 135 | { 136 | $schema = $this->getSchema($schemaName); 137 | 138 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { id_in: [3, 4] } ) { id performanceDate } } }"; 139 | 140 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 141 | $output = $result->toArray(); 142 | 143 | $this->assertEquals(2, sizeof($output['data']['artist'][0]['performance'])); 144 | } 145 | 146 | /** 147 | * @dataProvider schemaDataProvider 148 | */ 149 | public function testNotIn($schemaName, $context) 150 | { 151 | $schema = $this->getSchema($schemaName); 152 | 153 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { id_notin: [3, 4] } ) { id performanceDate } } }"; 154 | 155 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 156 | $output = $result->toArray(); 157 | 158 | $this->assertEquals(3, sizeof($output['data']['artist'][0]['performance'])); 159 | } 160 | 161 | /** 162 | * @dataProvider schemaDataProvider 163 | */ 164 | public function testBetween($schemaName, $context) 165 | { 166 | $schema = $this->getSchema($schemaName); 167 | 168 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { id_between: { from: 2 to: 4} } ) { id performanceDate } } }"; 169 | 170 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 171 | $output = $result->toArray(); 172 | 173 | $this->assertEquals(3, sizeof($output['data']['artist'][0]['performance'])); 174 | } 175 | 176 | /** 177 | * @dataProvider schemaDataProvider 178 | */ 179 | public function testContains($schemaName, $context) 180 | { 181 | $schema = $this->getSchema($schemaName); 182 | 183 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { venue_contains: \"enue\" } ) { id performanceDate } } }"; 184 | 185 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 186 | $output = $result->toArray(); 187 | 188 | $this->assertEquals(5, sizeof($output['data']['artist'][0]['performance'])); 189 | } 190 | 191 | /** 192 | * @dataProvider schemaDataProvider 193 | */ 194 | public function testStartsWith($schemaName, $context) 195 | { 196 | $schema = $this->getSchema($schemaName); 197 | 198 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { venue_startswith: \"v\" } ) { id performanceDate } } }"; 199 | 200 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 201 | $output = $result->toArray(); 202 | 203 | $this->assertEquals(5, sizeof($output['data']['artist'][0]['performance'])); 204 | } 205 | 206 | /** 207 | * @dataProvider schemaDataProvider 208 | */ 209 | public function testEndsWith($schemaName, $context) 210 | { 211 | $schema = $this->getSchema($schemaName); 212 | 213 | $query = "{ artist ( filter: { id:1 } ) { performance ( filter: { venue_endswith: \"5\" } ) { id performanceDate } } }"; 214 | 215 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 216 | $output = $result->toArray(); 217 | 218 | $this->assertEquals(1, sizeof($output['data']['artist'][0]['performance'])); 219 | } 220 | 221 | /** 222 | * @dataProvider schemaDataProvider 223 | */ 224 | public function testSortAsc($schemaName, $context) 225 | { 226 | $schema = $this->getSchema($schemaName); 227 | 228 | $query = '{ artist ( filter: { id:1 } ) { performance ( filter: { venue_sort:"asc" } ) { id venue performanceDate } } }'; 229 | 230 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 231 | $output = $result->toArray(); 232 | 233 | $this->assertEquals('venue1', $output['data']['artist'][0]['performance'][0]['venue']); 234 | } 235 | 236 | /** 237 | * @dataProvider schemaDataProvider 238 | */ 239 | public function testSortDesc($schemaName, $context) 240 | { 241 | $schema = $this->getSchema($schemaName); 242 | 243 | $query = '{ artist ( filter: { id:1 } ) { performance ( filter: { venue_sort:"desc" } ) { id venue performanceDate } } }'; 244 | 245 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 246 | $output = $result->toArray(); 247 | 248 | $this->assertEquals('venue5', $output['data']['artist'][0]['performance'][0]['venue']); 249 | } 250 | 251 | /** 252 | * @dataProvider schemaDataProvider 253 | */ 254 | public function testSkip($schemaName, $context) 255 | { 256 | $schema = $this->getSchema($schemaName); 257 | 258 | $query = '{ artist ( filter: { id:1 } ) { performance ( filter: { _skip: 3 } ) { id venue performanceDate } } }'; 259 | 260 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 261 | $output = $result->toArray(); 262 | 263 | $this->assertEquals(2, sizeof($output['data']['artist'][0]['performance'])); 264 | } 265 | 266 | /** 267 | * @dataProvider schemaDataProvider 268 | */ 269 | public function testLimit($schemaName, $context) 270 | { 271 | $schema = $this->getSchema($schemaName); 272 | 273 | $query = '{ artist ( filter: { id:1 } ) { performance ( filter: { _limit: 2 } ) { id venue performanceDate } } }'; 274 | 275 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 276 | $output = $result->toArray(); 277 | 278 | $this->assertEquals(2, sizeof($output['data']['artist'][0]['performance'])); 279 | } 280 | 281 | 282 | /** 283 | * @dataProvider schemaDataProvider 284 | */ 285 | public function testOverTheLimit($schemaName, $context) 286 | { 287 | $schema = $this->getSchema($schemaName); 288 | 289 | $query = '{ artist ( filter: { id:1 } ) { performance ( filter: { _limit: 20000 } ) { id venue performanceDate } } }'; 290 | 291 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 292 | $output = $result->toArray(); 293 | 294 | $this->assertEquals(5, sizeof($output['data']['artist'][0]['performance'])); 295 | } 296 | 297 | /** 298 | * @dataProvider schemaDataProvider 299 | */ 300 | public function testDistinct($schemaName, $context) 301 | { 302 | $schema = $this->getSchema($schemaName); 303 | 304 | $query = "{ user ( filter: { id:1 } ) { name artist ( filter: { name_distinct: true } ) { id alias } } }"; 305 | 306 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 307 | $output = $result->toArray(); 308 | 309 | $this->assertEquals(5, sizeof($output['data']['user'][0]['artist'])); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /test/GraphQL/EventTest.php: -------------------------------------------------------------------------------- 1 | getSchema($schemaName); 20 | 21 | $container = $this->getApplication()->getServiceManager(); 22 | $events = $container->get('SharedEventManager'); 23 | 24 | $events->attach( 25 | Event::class, 26 | Event::FILTER_QUERY_BUILDER, 27 | function(ZendEvent $event) 28 | { 29 | switch ($event->getParam('entityClassName')) { 30 | case 'DbTest\Entity\Performance': 31 | $event->getParam('queryBuilder') 32 | ->andWhere('row.id = 1') 33 | ; 34 | break; 35 | default: 36 | break; 37 | } 38 | }, 39 | 100 40 | ); 41 | 42 | $query = "{ performance { id } }"; 43 | 44 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 45 | $output = $result->toArray(); 46 | 47 | $this->assertEquals(1, sizeof($output['data']['performance'])); 48 | } 49 | 50 | /** 51 | * @dataProvider schemaDataProvider 52 | */ 53 | public function testResolveEvent($schemaName, $context) 54 | { 55 | $schema = $this->getSchema($schemaName); 56 | 57 | $container = $this->getApplication()->getServiceManager(); 58 | $events = $container->get('SharedEventManager'); 59 | $hydratorExtractTool = $container->get('ZF\\Doctrine\\GraphQL\\Hydrator\\HydratorExtractTool'); 60 | 61 | $events->attach( 62 | Event::class, 63 | Event::RESOLVE, 64 | function(ZendEvent $event) use ($hydratorExtractTool) 65 | { 66 | $object = $event->getParam('object'); 67 | $arguments = $event->getParam('arguments'); 68 | $context = $event->getParam('context'); 69 | $hydratorAlias = $event->getParam('hydratorAlias'); 70 | $objectManager = $event->getParam('objectManager'); 71 | $entityClassName = $event->getParam('entityClassName'); 72 | 73 | $results = $objectManager->getRepository($entityClassName)->findBy([ 74 | 'attendance' => 2000, 75 | ]); 76 | 77 | $resultCollection = $hydratorExtractTool->extractToCollection($results, $hydratorAlias, $context); 78 | 79 | $event->stopPropagation(true); 80 | 81 | return $resultCollection; 82 | }, 83 | 100 84 | ); 85 | 86 | $query = "{ performance { id attendance } }"; 87 | 88 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 89 | $output = $result->toArray(); 90 | 91 | $this->assertEquals(2, sizeof($output['data']['performance'])); 92 | } 93 | 94 | /** 95 | * @dataProvider schemaDataProvider 96 | */ 97 | public function testResolvePostEvent($schemaName, $context) 98 | { 99 | $schema = $this->getSchema($schemaName); 100 | 101 | $container = $this->getApplication()->getServiceManager(); 102 | $events = $container->get('SharedEventManager'); 103 | $hydratorExtractTool = $container->get('ZF\\Doctrine\\GraphQL\\Hydrator\\HydratorExtractTool'); 104 | 105 | $events->attach( 106 | Event::class, 107 | Event::RESOLVE_POST, 108 | function(ZendEvent $event) use ($hydratorExtractTool) 109 | { 110 | $objectManager = $event->getParam('objectManager'); 111 | $entityClassName = $event->getParam('entityClassName'); 112 | $resultCollection = $event->getParam('resultCollection'); 113 | $context = $event->getParam('context'); 114 | $hydratorAlias = $event->getParam('hydratorAlias'); 115 | 116 | $results = $objectManager->getRepository($entityClassName)->findBy([ 117 | 'attendance' => 2000, 118 | ]); 119 | 120 | $resultCollection->clear(); 121 | foreach ($results as $key => $value) { 122 | $resultCollection->add($hydratorExtractTool->extract($value, $hydratorAlias, $context)); 123 | } 124 | 125 | $event->stopPropagation(true); 126 | 127 | return $resultCollection; 128 | }, 129 | 100 130 | ); 131 | 132 | $query = "{ performance { id attendance } }"; 133 | 134 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 135 | $output = $result->toArray(); 136 | 137 | $this->assertEquals(2, sizeof($output['data']['performance'])); 138 | } 139 | 140 | 141 | /** 142 | * @dataProvider eventDataProvider 143 | */ 144 | public function testOverrideGraphQLTypeOnEntityTypeEvent($schemaName, $context) 145 | { 146 | $container = $this->getApplication()->getServiceManager(); 147 | $events = $container->get('SharedEventManager'); 148 | $config = $container->get('config'); 149 | 150 | $events->attach( 151 | Event::class, 152 | Event::MAP_FIELD_TYPE, 153 | function(ZendEvent $event) use ($container, $config) 154 | { 155 | $hydratorAlias = $event->getParam('hydratorAlias'); 156 | $options = $event->getParam('options'); 157 | $fieldName = $event->getParam('fieldName'); 158 | 159 | if ($hydratorAlias == 'ZF\\Doctrine\\GraphQL\\Hydrator\\DbTest_Entity_Artist') { 160 | if ($fieldName === 'alias') { 161 | // Update all Artist alias to a multidimentional array 162 | $hydratorConfig = $config['zf-doctrine-graphql-hydrator'][$hydratorAlias]; 163 | $objectManager = 164 | $container->get($hydratorConfig[$options['hydrator_section']]['object_manager']); 165 | 166 | $artist = $objectManager->getRepository(Entity\Artist::class) 167 | ->find(1); 168 | 169 | $artist->alias = ['multi' => ['dimentional' => 'array']]; 170 | $objectManager->flush(); 171 | 172 | $event->stopPropagation(); 173 | 174 | return Type::string(); 175 | } 176 | } 177 | }, 178 | 100 179 | ); 180 | 181 | $schema = $this->getSchema($schemaName); 182 | $query = "{ artist ( filter: { id:1 } ) { id alias } }"; 183 | 184 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 185 | $output = $result->toArray(); 186 | 187 | $this->assertEquals('{"multi":{"dimentional":"array"}}', $output['data']['artist'][0]['alias']); 188 | } 189 | 190 | /** 191 | * @dataProvider schemaDataProvider 192 | */ 193 | public function testOverrideGraphQLTypeOnFilterTypeAndCriteriaEvent($schemaName, $context) 194 | { 195 | $container = $this->getApplication()->getServiceManager(); 196 | $events = $container->get('SharedEventManager'); 197 | $config = $container->get('config'); 198 | 199 | $events->attach( 200 | Event::class, 201 | Event::MAP_FIELD_TYPE, 202 | function(ZendEvent $event) use ($container, $config) 203 | { 204 | $hydratorAlias = $event->getParam('hydratorAlias'); 205 | $options = $event->getParam('options'); 206 | $fieldName = $event->getParam('fieldName'); 207 | 208 | if ($hydratorAlias == 'ZF\\Doctrine\\GraphQL\\Hydrator\\DbTest_Entity_Performance') { 209 | if ($fieldName === 'attendance') { 210 | // Update all Artist alias to a multidimentional array 211 | $hydratorConfig = $config['zf-doctrine-graphql-hydrator'][$hydratorAlias]; 212 | $objectManager = 213 | $container->get($hydratorConfig[$options['hydrator_section']]['object_manager']); 214 | 215 | $artist = $objectManager->getRepository(Entity\Artist::class) 216 | ->find(1); 217 | 218 | $artist->alias = [1, 2, 3]; 219 | $objectManager->flush(); 220 | 221 | $event->stopPropagation(); 222 | 223 | return Type::listOf(Type::int()); 224 | } 225 | } 226 | }, 227 | 100 228 | ); 229 | 230 | $schema = $this->getSchema($schemaName); 231 | $query = "{ performance ( filter: { id:1 } ) { id artist { alias } } }"; 232 | 233 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 234 | $output = $result->toArray(); 235 | 236 | $this->assertEquals([1, 2, 3], $output['data']['performance'][0]['artist']['alias']); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /test/GraphQL/PerformanceTest.php: -------------------------------------------------------------------------------- 1 | getSchema($schemaName); 17 | 18 | $query = "{ performance { id performanceDate } }"; 19 | 20 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 21 | $output = $result->toArray(); 22 | 23 | $this->assertEquals(5, sizeof($output['data']['performance'])); 24 | } 25 | 26 | /** 27 | * @dataProvider schemaDataProvider 28 | */ 29 | public function testArtistManyToOne($schemaName, $context) 30 | { 31 | $schema = $this->getSchema($schemaName); 32 | 33 | $query = "{ performance { id artist { name } } }"; 34 | 35 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 36 | $output = $result->toArray(); 37 | 38 | $this->assertEquals('artist1', $output['data']['performance'][0]['artist']['name']); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/GraphQL/QueryBuilderFiltersTest.php: -------------------------------------------------------------------------------- 1 | getSchema($schemaName); 17 | 18 | $query = "{ performance ( filter: { id_eq: 1 } ) { id performanceDate } }"; 19 | 20 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 21 | $output = $result->toArray(); 22 | 23 | $this->assertEquals(1, sizeof($output['data']['performance'])); 24 | } 25 | 26 | /** 27 | * @dataProvider schemaDataProvider 28 | */ 29 | public function testNotEquals($schemaName, $context) 30 | { 31 | $schema = $this->getSchema($schemaName); 32 | 33 | $query = "{ performance ( filter: { id_neq: 1 } ) { id performanceDate } }"; 34 | 35 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 36 | $output = $result->toArray(); 37 | 38 | $this->assertEquals(4, sizeof($output['data']['performance'])); 39 | } 40 | 41 | /** 42 | * @dataProvider schemaDataProvider 43 | */ 44 | public function testGreaterThan($schemaName, $context) 45 | { 46 | $schema = $this->getSchema($schemaName); 47 | 48 | $query = "{ performance ( filter: { id_gt: 1 } ) { id performanceDate } }"; 49 | 50 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 51 | $output = $result->toArray(); 52 | 53 | $this->assertEquals(4, sizeof($output['data']['performance'])); 54 | } 55 | 56 | /** 57 | * @dataProvider schemaDataProvider 58 | */ 59 | public function testGreaterThanOrEquals($schemaName, $context) 60 | { 61 | $schema = $this->getSchema($schemaName); 62 | 63 | $query = "{ performance ( filter: { id_gte: 2 } ) { id performanceDate } }"; 64 | 65 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 66 | $output = $result->toArray(); 67 | 68 | $this->assertEquals(4, sizeof($output['data']['performance'])); 69 | } 70 | 71 | /** 72 | * @dataProvider schemaDataProvider 73 | */ 74 | public function testLessThan($schemaName, $context) 75 | { 76 | $schema = $this->getSchema($schemaName); 77 | 78 | $query = "{ performance ( filter: { id_lt: 2 } ) { id performanceDate } }"; 79 | 80 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 81 | $output = $result->toArray(); 82 | 83 | $this->assertEquals(1, sizeof($output['data']['performance'])); 84 | } 85 | 86 | /** 87 | * @dataProvider schemaDataProvider 88 | */ 89 | public function testLessThanOrEquals($schemaName, $context) 90 | { 91 | $schema = $this->getSchema($schemaName); 92 | 93 | $query = "{ performance ( filter: { id_lte: 2 } ) { id performanceDate } }"; 94 | 95 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 96 | $output = $result->toArray(); 97 | 98 | $this->assertEquals(2, sizeof($output['data']['performance'])); 99 | } 100 | 101 | /** 102 | * @dataProvider schemaDataProvider 103 | */ 104 | public function testIsNull($schemaName, $context) 105 | { 106 | $schema = $this->getSchema($schemaName); 107 | 108 | $query = "{ performance ( filter: { isTradable_isnull:true } ) { id performanceDate } }"; 109 | 110 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 111 | $output = $result->toArray(); 112 | 113 | $this->assertEquals(1, sizeof($output['data']['performance'])); 114 | } 115 | 116 | /** 117 | * @dataProvider schemaDataProvider 118 | */ 119 | public function testIsNotNull($schemaName, $context) 120 | { 121 | $schema = $this->getSchema($schemaName); 122 | 123 | $query = "{ performance ( filter: { isTradable_isnull:false } ) { id performanceDate } }"; 124 | 125 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 126 | $output = $result->toArray(); 127 | 128 | $this->assertEquals(4, sizeof($output['data']['performance'])); 129 | } 130 | 131 | /** 132 | * @dataProvider schemaDataProvider 133 | */ 134 | public function testIn($schemaName, $context) 135 | { 136 | $schema = $this->getSchema($schemaName); 137 | 138 | $query = "{ performance ( filter: { id_in: [3, 4] } ) { id performanceDate } }"; 139 | 140 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 141 | $output = $result->toArray(); 142 | 143 | $this->assertEquals(2, sizeof($output['data']['performance'])); 144 | } 145 | 146 | /** 147 | * @dataProvider schemaDataProvider 148 | */ 149 | public function testNotIn($schemaName, $context) 150 | { 151 | $schema = $this->getSchema($schemaName); 152 | 153 | $query = "{ performance ( filter: { id_notin: [3, 4] } ) { id performanceDate } }"; 154 | 155 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 156 | $output = $result->toArray(); 157 | 158 | $this->assertEquals(3, sizeof($output['data']['performance'])); 159 | } 160 | 161 | /** 162 | * @dataProvider schemaDataProvider 163 | */ 164 | public function testBetween($schemaName, $context) 165 | { 166 | $schema = $this->getSchema($schemaName); 167 | 168 | $query = "{ performance ( filter: { id_between: { from: 2 to: 4} } ) { id performanceDate } }"; 169 | 170 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 171 | $output = $result->toArray(); 172 | 173 | $this->assertEquals(3, sizeof($output['data']['performance'])); 174 | } 175 | 176 | /** 177 | * @dataProvider schemaDataProvider 178 | */ 179 | public function testContains($schemaName, $context) 180 | { 181 | $schema = $this->getSchema($schemaName); 182 | 183 | $query = "{ performance ( filter: { venue_contains: \"enue\" } ) { id performanceDate } }"; 184 | 185 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 186 | $output = $result->toArray(); 187 | 188 | $this->assertEquals(5, sizeof($output['data']['performance'])); 189 | } 190 | 191 | /** 192 | * @dataProvider schemaDataProvider 193 | */ 194 | public function testStartsWith($schemaName, $context) 195 | { 196 | $schema = $this->getSchema($schemaName); 197 | 198 | $query = "{ performance ( filter: { venue_startswith: \"v\" } ) { id performanceDate } }"; 199 | 200 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 201 | $output = $result->toArray(); 202 | 203 | $this->assertEquals(5, sizeof($output['data']['performance'])); 204 | } 205 | 206 | /** 207 | * @dataProvider schemaDataProvider 208 | */ 209 | public function testEndsWith($schemaName, $context) 210 | { 211 | $schema = $this->getSchema($schemaName); 212 | 213 | $query = "{ performance ( filter: { venue_endswith: \"5\" } ) { id performanceDate } }"; 214 | 215 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 216 | $output = $result->toArray(); 217 | 218 | $this->assertEquals(1, sizeof($output['data']['performance'])); 219 | } 220 | 221 | /** 222 | * @dataProvider schemaDataProvider 223 | */ 224 | public function testSortAsc($schemaName, $context) 225 | { 226 | $schema = $this->getSchema($schemaName); 227 | 228 | $query = '{ performance ( filter: { venue_sort:"asc" } ) { id venue performanceDate } }'; 229 | 230 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 231 | $output = $result->toArray(); 232 | 233 | $this->assertEquals('venue1', $output['data']['performance'][0]['venue']); 234 | } 235 | 236 | /** 237 | * @dataProvider schemaDataProvider 238 | */ 239 | public function testSortDesc($schemaName, $context) 240 | { 241 | $schema = $this->getSchema($schemaName); 242 | 243 | $query = '{ performance ( filter: { venue_sort:"desc" } ) { id venue performanceDate } }'; 244 | 245 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 246 | $output = $result->toArray(); 247 | 248 | $this->assertEquals('venue5', $output['data']['performance'][0]['venue']); 249 | } 250 | 251 | /** 252 | * @dataProvider schemaDataProvider 253 | */ 254 | public function testSkip($schemaName, $context) 255 | { 256 | $schema = $this->getSchema($schemaName); 257 | 258 | $query = "{ performance ( filter: { _skip: 3 } ) { id performanceDate } }"; 259 | 260 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 261 | $output = $result->toArray(); 262 | 263 | $this->assertEquals(2, sizeof($output['data']['performance'])); 264 | } 265 | 266 | /** 267 | * @dataProvider schemaDataProvider 268 | */ 269 | public function testLimit($schemaName, $context) 270 | { 271 | $schema = $this->getSchema($schemaName); 272 | 273 | $query = "{ performance ( filter: { _limit: 3 } ) { id performanceDate } }"; 274 | 275 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 276 | $output = $result->toArray(); 277 | 278 | $this->assertEquals(3, sizeof($output['data']['performance'])); 279 | } 280 | 281 | /** 282 | * @dataProvider schemaDataProvider 283 | */ 284 | public function testOverTheLimit($schemaName, $context) 285 | { 286 | $schema = $this->getSchema($schemaName); 287 | 288 | $query = "{ performance ( filter: { _limit: 10000 } ) { id performanceDate } }"; 289 | 290 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 291 | $output = $result->toArray(); 292 | 293 | $this->assertEquals(5, sizeof($output['data']['performance'])); 294 | } 295 | 296 | /** 297 | * @dataProvider schemaDataProvider 298 | */ 299 | public function testDistinct($schemaName, $context) 300 | { 301 | $schema = $this->getSchema($schemaName); 302 | 303 | $query = "{ artist ( filter: { name_distinct: true } ) { name } }"; 304 | 305 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 306 | $output = $result->toArray(); 307 | 308 | $this->assertEquals(5, sizeof($output['data']['artist'])); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /test/GraphQL/UserTest.php: -------------------------------------------------------------------------------- 1 | getSchema($schemaName); 17 | 18 | $query = "{ user { id name } }"; 19 | 20 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 21 | $output = $result->toArray(); 22 | 23 | $this->assertEquals(5, sizeof($output['data']['user'])); 24 | } 25 | 26 | /** 27 | * @dataProvider schemaDataProvider 28 | */ 29 | public function testUserFilterId($schemaName, $context) 30 | { 31 | $schema = $this->getSchema($schemaName); 32 | $query = "{ user (filter: { id:1 }) { id name } }"; 33 | 34 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 35 | $output = $result->toArray(); 36 | 37 | $this->assertEquals(1, sizeof($output['data']['user'])); 38 | } 39 | 40 | /** 41 | * @dataProvider schemaDataProvider 42 | */ 43 | public function testUserAddress($schemaName, $context) 44 | { 45 | $schema = $this->getSchema($schemaName); 46 | 47 | $query = "{ user (filter: { id:1 }) { id name address { id address } } }"; 48 | 49 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 50 | $output = $result->toArray(); 51 | 52 | $this->assertEquals('address1', $output['data']['user'][0]['address']['address']); 53 | } 54 | 55 | /** 56 | * @dataProvider schemaDataProvider 57 | */ 58 | public function testPasswordFilter($schemaName, $context) 59 | { 60 | $schema = $this->getSchema($schemaName); 61 | 62 | $query = "{ user (filter: { id:1 }) { password } }"; 63 | 64 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 65 | $output = $result->toArray(); 66 | 67 | $this->assertEquals( 68 | 'Cannot query field "password" on type "DbTest_Entity_User__' . $schemaName . '".', 69 | $output['errors'][0]['message'] 70 | ); 71 | } 72 | 73 | /** 74 | * @dataProvider schemaDataProvider 75 | */ 76 | public function testUserArtistManyToManyWorksBecauseArtistIsOwner($schemaName, $context) 77 | { 78 | $schema = $this->getSchema($schemaName); 79 | 80 | $query = "{ user( filter: { id:1 } ) { id artist { id } } }"; 81 | 82 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 83 | $output = $result->toArray(); 84 | 85 | $this->assertEquals(5, sizeof($output['data']['user'][0]['artist'])); 86 | } 87 | 88 | /** 89 | * @dataProvider schemaDataProvider 90 | */ 91 | public function testFetchCriteriaForRelation($schemaName, $context) 92 | { 93 | $schema = $this->getSchema($schemaName); 94 | 95 | $query = "{ user( filter: { id:1 } ) { id artist { id performance ( filter: { id: 3 } ) { id } } } }"; 96 | 97 | $result = GraphQL::executeQuery($schema, $query, $rootValue = null, $context, $variableValues = null); 98 | $output = $result->toArray(); 99 | 100 | $this->assertEquals(5, sizeof($output['data']['user'][0]['artist'])); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/config/autoload/local.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'connection' => [ 6 | 'orm_default' => [ 7 | 'driverClass' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver', 8 | 'params' => [ 9 | 'memory' => 'true', 10 | ], 11 | ], 12 | ], 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /test/config/autoload/zf-doctrine-criteria.global.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'aliases' => [ 10 | 'field' => OrderBy\Field::class, 11 | ], 12 | 'factories' => [ 13 | OrderBy\Field::class => InvokableFactory::class, 14 | ], 15 | ], 16 | 'zf-doctrine-criteria-filter' => [ 17 | 'aliases' => [ 18 | 'contains' => Filter\Contains::class, 19 | 'endswith' => Filter\EndsWith::class, 20 | 'eq' => Filter\Equals::class, 21 | 'gt' => Filter\GreaterThan::class, 22 | 'gte' => Filter\GreaterThanOrEquals::class, 23 | 'in' => Filter\In::class, 24 | 'lt' => Filter\LessThan::class, 25 | 'lte' => Filter\LessThanOrEquals::class, 26 | 'memberof' => Filter\MemberOf::class, 27 | 'neq' => Filter\NotEquals::class, 28 | 'notin' => Filter\NotIn::class, 29 | 'startswith' => Filter\StartsWith::class, 30 | ], 31 | 'factories' => [ 32 | Filter\Contains::class => InvokableFactory::class, 33 | Filter\EndsWith::class => InvokableFactory::class, 34 | Filter\Equals::class => InvokableFactory::class, 35 | Filter\GreaterThan::class => InvokableFactory::class, 36 | Filter\GreaterThanOrEquals::class => InvokableFactory::class, 37 | Filter\In::class => InvokableFactory::class, 38 | Filter\LessThan::class => InvokableFactory::class, 39 | Filter\LessThanOrEquals::class => InvokableFactory::class, 40 | Filter\MemberOf::class => InvokableFactory::class, 41 | Filter\NotEquals::class => InvokableFactory::class, 42 | Filter\NotIn::class => InvokableFactory::class, 43 | Filter\StartsWith::class => InvokableFactory::class, 44 | ], 45 | ], 46 | ]; 47 | -------------------------------------------------------------------------------- /test/config/autoload/zf-doctrine-graphql.global.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'ZF\\Doctrine\\GraphQL\\Hydrator\\DbTest_Entity_Artist' => [ 5 | 'default' => [ 6 | 'entity_class' => \DbTest\Entity\Artist::class, 7 | 'object_manager' => 'doctrine.entitymanager.orm_default', 8 | 'by_value' => false, 9 | 'use_generated_hydrator' => true, 10 | 'naming_strategy' => null, 11 | 'hydrator' => null, 12 | 'strategies' => [ 13 | 'id' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 14 | 'name' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 15 | 'alias' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 16 | 'createdAt' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 17 | 'performance' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 18 | 'user' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\NullifyOwningAssociation::class, 19 | ], 20 | 'filters' => [ 21 | 'default' => [ 22 | 'condition' => 'and', 23 | 'filter' => \ZF\Doctrine\GraphQL\Hydrator\Filter\FilterDefault::class, 24 | ], 25 | ], 26 | ], 27 | 'test' => [ 28 | 'entity_class' => \DbTest\Entity\Artist::class, 29 | 'object_manager' => 'doctrine.entitymanager.orm_default', 30 | 'by_value' => false, 31 | 'use_generated_hydrator' => true, 32 | 'naming_strategy' => null, 33 | 'hydrator' => null, 34 | 'strategies' => [ 35 | 'id' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 36 | 'alias' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 37 | 'createdAt' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 38 | 'name' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 39 | 'performance' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 40 | 'user' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\NullifyOwningAssociation::class, 41 | ], 42 | 'filters' => [ 43 | 'default' => [ 44 | 'condition' => 'and', 45 | 'filter' => \ZF\Doctrine\GraphQL\Hydrator\Filter\FilterDefault::class, 46 | ], 47 | ], 48 | ], 49 | 'event' => [ 50 | 'entity_class' => \DbTest\Entity\Artist::class, 51 | 'object_manager' => 'doctrine.entitymanager.orm_default', 52 | 'by_value' => false, 53 | 'use_generated_hydrator' => true, 54 | 'naming_strategy' => null, 55 | 'hydrator' => null, 56 | 'strategies' => [ 57 | 'id' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 58 | 'alias' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToJson::class, 59 | 'createdAt' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 60 | 'name' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 61 | 'performance' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 62 | 'user' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\NullifyOwningAssociation::class, 63 | ], 64 | 'filters' => [ 65 | 'eventTest' => [ 66 | 'condition' => 'and', 67 | 'filter' => 'DbTest\Hydrator\Filter\EventTestFilter', 68 | ], 69 | ], 70 | ], 71 | ], 72 | 'ZF\\Doctrine\\GraphQL\\Hydrator\\DbTest_Entity_User' => [ 73 | 'default' => [ 74 | 'entity_class' => \DbTest\Entity\User::class, 75 | 'object_manager' => 'doctrine.entitymanager.orm_default', 76 | 'by_value' => false, 77 | 'use_generated_hydrator' => true, 78 | 'naming_strategy' => null, 79 | 'hydrator' => null, 80 | 'strategies' => [ 81 | 'id' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 82 | 'name' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 83 | 'artist' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 84 | 'address' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 85 | ], 86 | 'filters' => [ 87 | 'default' => [ 88 | 'condition' => 'and', 89 | 'filter' => \ZF\Doctrine\GraphQL\Hydrator\Filter\FilterDefault::class, 90 | ], 91 | 'password' => [ 92 | 'condition' => 'and', 93 | 'filter' => \ZF\Doctrine\GraphQL\Hydrator\Filter\Password::class, 94 | ], 95 | ], 96 | ], 97 | 'test' => [ 98 | 'entity_class' => \DbTest\Entity\User::class, 99 | 'object_manager' => 'doctrine.entitymanager.orm_default', 100 | 'by_value' => false, 101 | 'use_generated_hydrator' => true, 102 | 'naming_strategy' => null, 103 | 'hydrator' => null, 104 | 'strategies' => [ 105 | 'id' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 106 | 'name' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 107 | 'artist' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 108 | 'address' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 109 | ], 110 | 'filters' => [ 111 | 'default' => [ 112 | 'condition' => 'and', 113 | 'filter' => \ZF\Doctrine\GraphQL\Hydrator\Filter\FilterDefault::class, 114 | ], 115 | 'password' => [ 116 | 'condition' => 'and', 117 | 'filter' => \ZF\Doctrine\GraphQL\Hydrator\Filter\Password::class, 118 | ], 119 | ], 120 | ], 121 | ], 122 | 'ZF\\Doctrine\\GraphQL\\Hydrator\\DbTest_Entity_Address' => [ 123 | 'default' => [ 124 | 'entity_class' => \DbTest\Entity\Address::class, 125 | 'object_manager' => 'doctrine.entitymanager.orm_default', 126 | 'by_value' => false, 127 | 'use_generated_hydrator' => true, 128 | 'naming_strategy' => null, 129 | 'hydrator' => null, 130 | 'strategies' => [ 131 | 'id' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 132 | 'address' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 133 | 'user' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 134 | ], 135 | 'filters' => [ 136 | 'default' => [ 137 | 'condition' => 'and', 138 | 'filter' => \ZF\Doctrine\GraphQL\Hydrator\Filter\FilterDefault::class, 139 | ], 140 | ], 141 | ], 142 | 'test' => [ 143 | 'entity_class' => \DbTest\Entity\Address::class, 144 | 'object_manager' => 'doctrine.entitymanager.orm_default', 145 | 'by_value' => false, 146 | 'use_generated_hydrator' => true, 147 | 'naming_strategy' => null, 148 | 'hydrator' => null, 149 | 'strategies' => [ 150 | 'id' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 151 | 'address' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 152 | 'user' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 153 | ], 154 | 'filters' => [ 155 | 'default' => [ 156 | 'condition' => 'and', 157 | 'filter' => \ZF\Doctrine\GraphQL\Hydrator\Filter\FilterDefault::class, 158 | ], 159 | ], 160 | ], 161 | ], 162 | 'ZF\\Doctrine\\GraphQL\\Hydrator\\DbTest_Entity_Performance' => [ 163 | 'default' => [ 164 | 'entity_class' => \DbTest\Entity\Performance::class, 165 | 'object_manager' => 'doctrine.entitymanager.orm_default', 166 | 'by_value' => false, 167 | 'use_generated_hydrator' => true, 168 | 'naming_strategy' => null, 169 | 'hydrator' => null, 170 | 'strategies' => [ 171 | 'id' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 172 | 'performanceDate' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 173 | 'venue' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 174 | 'attendance' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 175 | 'isTradable' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToBoolean::class, 176 | 'ticketPrice' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToFloat::class, 177 | 'artist' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 178 | ], 179 | 'filters' => [ 180 | 'default' => [ 181 | 'condition' => 'and', 182 | 'filter' => \ZF\Doctrine\GraphQL\Hydrator\Filter\FilterDefault::class, 183 | ], 184 | ], 185 | ], 186 | 'test' => [ 187 | 'entity_class' => \DbTest\Entity\Performance::class, 188 | 'object_manager' => 'doctrine.entitymanager.orm_default', 189 | 'by_value' => false, 190 | 'use_generated_hydrator' => true, 191 | 'naming_strategy' => null, 192 | 'hydrator' => null, 193 | 'strategies' => [ 194 | 'id' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 195 | 'performanceDate' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 196 | 'venue' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\FieldDefault::class, 197 | 'attendance' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToInteger::class, 198 | 'isTradable' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToBoolean::class, 199 | 'ticketPrice' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\ToFloat::class, 200 | 'artist' => \ZF\Doctrine\GraphQL\Hydrator\Strategy\AssociationDefault::class, 201 | ], 202 | 'filters' => [ 203 | 'default' => [ 204 | 'condition' => 'and', 205 | 'filter' => \ZF\Doctrine\GraphQL\Hydrator\Filter\FilterDefault::class, 206 | ], 207 | ], 208 | ], 209 | ], 210 | ], 211 | ]; 212 | -------------------------------------------------------------------------------- /test/config/autoload/zf-doctrine-querybuilder.global.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'filter_key' => 'filter', 14 | 'order_by_key' => 'order-by', 15 | ], 16 | 'zf-doctrine-querybuilder-orderby-orm' => [ 17 | 'aliases' => [ 18 | 'field' => OrderBy\ORM\Field::class, 19 | ], 20 | 'factories' => [ 21 | OrderBy\ORM\Field::class => InvokableFactory::class, 22 | ], 23 | ], 24 | 'zf-doctrine-querybuilder-orderby-odm' => [ 25 | 'aliases' => [ 26 | 'field' => OrderBy\ODM\Field::class, 27 | ], 28 | 'factories' => [ 29 | OrderBy\ODM\Field::class => InvokableFactory::class, 30 | ], 31 | ], 32 | 'zf-doctrine-querybuilder-filter-orm' => [ 33 | 'aliases' => [ 34 | 'eq' => Filter\ORM\Equals::class, 35 | 'neq' => Filter\ORM\NotEquals::class, 36 | 'lt' => Filter\ORM\LessThan::class, 37 | 'lte' => Filter\ORM\LessThanOrEquals::class, 38 | 'gt' => Filter\ORM\GreaterThan::class, 39 | 'gte' => Filter\ORM\GreaterThanOrEquals::class, 40 | 'isnull' => Filter\ORM\IsNull::class, 41 | 'isnotnull' => Filter\ORM\IsNotNull::class, 42 | 'in' => Filter\ORM\In::class, 43 | 'notin' => Filter\ORM\NotIn::class, 44 | 'between' => Filter\ORM\Between::class, 45 | 'like' => Filter\ORM\Like::class, 46 | 'notlike' => Filter\ORM\NotLike::class, 47 | 'ismemberof' => Filter\ORM\IsMemberOf::class, 48 | 'orx' => Filter\ORM\OrX::class, 49 | 'andx' => Filter\ORM\AndX::class, 50 | // 'innerjoin' => Filter\ORM\InnerJoin::class, 51 | // 'leftjoin' => Filter\ORM\LeftJoin::class, 52 | ], 53 | 'factories' => [ 54 | Filter\ORM\Equals::class => InvokableFactory::class, 55 | Filter\ORM\NotEquals::class => InvokableFactory::class, 56 | Filter\ORM\LessThan::class => InvokableFactory::class, 57 | Filter\ORM\LessThanOrEquals::class => InvokableFactory::class, 58 | Filter\ORM\GreaterThan::class => InvokableFactory::class, 59 | Filter\ORM\GreaterThanOrEquals::class => InvokableFactory::class, 60 | Filter\ORM\IsNull::class => InvokableFactory::class, 61 | Filter\ORM\IsNotNull::class => InvokableFactory::class, 62 | Filter\ORM\In::class => InvokableFactory::class, 63 | Filter\ORM\NotIn::class => InvokableFactory::class, 64 | Filter\ORM\Between::class => InvokableFactory::class, 65 | Filter\ORM\Like::class => InvokableFactory::class, 66 | Filter\ORM\NotLike::class => InvokableFactory::class, 67 | Filter\ORM\IsMemberOf::class => InvokableFactory::class, 68 | Filter\ORM\OrX::class => InvokableFactory::class, 69 | Filter\ORM\AndX::class => InvokableFactory::class, 70 | // Filter\ORM\InnerJoin::class => InvokableFactory::class, 71 | // Filter\ORM\LeftJoin::class => InvokableFactory::class, 72 | ], 73 | ], 74 | 'zf-doctrine-querybuilder-filter-odm' => [ 75 | 'aliases' => [ 76 | 'eq' => Filter\ODM\Equals::class, 77 | 'neq' => Filter\ODM\NotEquals::class, 78 | 'lt' => Filter\ODM\LessThan::class, 79 | 'lte' => Filter\ODM\LessThanOrEquals::class, 80 | 'gt' => Filter\ODM\GreaterThan::class, 81 | 'gte' => Filter\ODM\GreaterThanOrEquals::class, 82 | 'isnull' => Filter\ODM\IsNull::class, 83 | 'isnotnull' => Filter\ODM\IsNotNull::class, 84 | 'in' => Filter\ODM\In::class, 85 | 'notin' => Filter\ODM\NotIn::class, 86 | 'between' => Filter\ODM\Between::class, 87 | 'like' => Filter\ODM\Like::class, 88 | 'regex' => Filter\ODM\Regex::class, 89 | ], 90 | 'factories' => [ 91 | Filter\ODM\Equals::class => InvokableFactory::class, 92 | Filter\ODM\NotEquals::class => InvokableFactory::class, 93 | Filter\ODM\LessThan::class => InvokableFactory::class, 94 | Filter\ODM\LessThanOrEquals::class => InvokableFactory::class, 95 | Filter\ODM\GreaterThan::class => InvokableFactory::class, 96 | Filter\ODM\GreaterThanOrEquals::class => InvokableFactory::class, 97 | Filter\ODM\IsNull::class => InvokableFactory::class, 98 | Filter\ODM\IsNotNull::class => InvokableFactory::class, 99 | Filter\ODM\In::class => InvokableFactory::class, 100 | Filter\ODM\NotIn::class => InvokableFactory::class, 101 | Filter\ODM\Between::class => InvokableFactory::class, 102 | Filter\ODM\Like::class => InvokableFactory::class, 103 | Filter\ODM\Regex::class => InvokableFactory::class, 104 | ], 105 | ], 106 | ]; 107 | -------------------------------------------------------------------------------- /test/config/test.config.php: -------------------------------------------------------------------------------- 1 | $modules, 17 | 18 | // These are various options for the listeners attached to the ModuleManager 19 | 'module_listener_options' => [ 20 | // This should be an array of paths in which modules reside. 21 | // If a string key is provided, the listener will consider that a module 22 | // namespace, the value of that key the specific path to that module's 23 | // Module class. 24 | 'module_paths' => [ 25 | __DIR__ . '/../../vendor', 26 | __DIR__ . '/../../src', 27 | __DIR__ . '/../module/GraphQLApiTest/src', 28 | 'DbTest' => __DIR__ . '/../module/DbTest/src', 29 | 30 | ], 31 | 32 | // An array of paths from which to glob configuration files after 33 | // modules are loaded. These effectively override configuration 34 | // provided by modules themselves. Paths may use GLOB_BRACE notation. 35 | 'config_glob_paths' => [ 36 | __DIR__ . '/autoload/{,*.}{global,local}.php', 37 | ], 38 | ], 39 | ]; 40 | -------------------------------------------------------------------------------- /test/module/DbTest/config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'driver' => [ 6 | 'db_driver' => [ 7 | 'class' => 'Doctrine\\ORM\\Mapping\\Driver\\XmlDriver', 8 | 'paths' => [ 9 | __DIR__ . '/orm', 10 | ], 11 | ], 12 | 'orm_default' => [ 13 | 'class' => 'Doctrine\\ORM\\Mapping\\Driver\\DriverChain', 14 | 'drivers' => [ 15 | 'DbTest\\Entity' => 'db_driver', 16 | ], 17 | ], 18 | ], 19 | ], 20 | 'service_manager' => [ 21 | 'invokables' => [ 22 | 'DbTest\Hydrator\Filter\EventTestFilter' => 'DbTest\Hydrator\Filter\EventTestFilter', 23 | ], 24 | ], 25 | ]; 26 | -------------------------------------------------------------------------------- /test/module/DbTest/config/orm/DbTest.Entity.Address.dcm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/module/DbTest/config/orm/DbTest.Entity.Artist.dcm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/module/DbTest/config/orm/DbTest.Entity.Performance.dcm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/module/DbTest/config/orm/DbTest.Entity.User.dcm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/module/DbTest/media/GraphQLTest.skipper: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /test/module/DbTest/src/Entity/Address.php: -------------------------------------------------------------------------------- 1 | performance = new ArrayCollection(); 19 | $this->user = new ArrayCollection(); 20 | $this->alias = []; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/module/DbTest/src/Entity/Performance.php: -------------------------------------------------------------------------------- 1 | artist = new ArrayCollection(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/module/DbTest/src/Hydrator/Filter/EventTestFilter.php: -------------------------------------------------------------------------------- 1 | ['namespaces' => [ 17 | __NAMESPACE__ => __DIR__, 18 | ] 19 | ] 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/start.php: -------------------------------------------------------------------------------- 1 |