From 2795d4b2977e73e07b47a6d5c23c25c4fb4ae6e6 Mon Sep 17 00:00:00 2001 From: Bertrand Dunogier Date: Mon, 12 Nov 2018 09:04:05 +0100 Subject: [PATCH 01/18] Fixed syntax of fieldsDefaultPublic in example This field goes under `config`, not at the root. --- docs/security/fields-public-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security/fields-public-control.md b/docs/security/fields-public-control.md index 98338b8e2..78624a65b 100644 --- a/docs/security/fields-public-control.md +++ b/docs/security/fields-public-control.md @@ -25,8 +25,8 @@ You can also use `config.fieldsDefaultPublic` to handle the setting globally on ```yaml AnObject: type: object - fieldsDefaultPublic: "@=service('my_service').isGranted(typeName, fieldName)" config: + fieldsDefaultPublic: "@=service('my_service').isGranted(typeName, fieldName)" fields: id: type: "String!" From 9b5a90f25e1e4c961297331bd3a87d396966c28a Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 15 Nov 2018 11:40:02 +0100 Subject: [PATCH 02/18] Fix variable name, fixes #418 --- src/DependencyInjection/Compiler/GlobalVariablesPass.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DependencyInjection/Compiler/GlobalVariablesPass.php b/src/DependencyInjection/Compiler/GlobalVariablesPass.php index 0ed28d234..fbb5535ce 100644 --- a/src/DependencyInjection/Compiler/GlobalVariablesPass.php +++ b/src/DependencyInjection/Compiler/GlobalVariablesPass.php @@ -32,7 +32,7 @@ public function process(ContainerBuilder $container) $expressionLanguageDefinition->addMethodCall( 'addGlobalName', [ - \sprintf('globalVariables->get(\'%s\')', $attributes['alias']), + \sprintf('globalVariable->get(\'%s\')', $attributes['alias']), $attributes['alias'], ] ); From def521ad705b873657990716987edbdb81f361b4 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Sat, 17 Nov 2018 10:19:01 +0100 Subject: [PATCH 03/18] Add Symfony 4.2 to travis build --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 31b6a9144..c9ae138f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,9 @@ jobs: - php: 7.2 env: SYMFONY_VERSION=3.4.* - php: 7.2 - env: SYMFONY_VERSION=4.1.* STABILITY=beta + env: SYMFONY_VERSION=4.1.* + - php: 7.2 + env: SYMFONY_VERSION=4.2.* STABILITY=beta - php: nightly env: COMPOSER_UPDATE_FLAGS=--ignore-platform-reqs - php: 7.2 From db675467c47343e8eaaa4fa1e8a130ba7822e4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20David?= Date: Wed, 21 Nov 2018 19:17:11 +0100 Subject: [PATCH 04/18] Fix using custom names on a relay-mutation Input and Payload --- src/Relay/Mutation/InputDefinition.php | 3 +- src/Relay/Mutation/PayloadDefinition.php | 3 +- .../App/config/multipleSchema/config.yml | 3 ++ .../internal/InternalAddUserPayload.yaml | 7 ++++ .../mapping/internal/InternalMutation.yaml | 12 +++++++ .../mapping/public/PublicAddUserInput.yaml | 7 ++++ .../mapping/public/PublicAddUserPayload.yaml | 7 ++++ .../mapping/public/PublicMutation.yaml | 12 +++++++ .../MultipleSchema/MultipleSchemaTest.php | 35 +++++++++++++++++++ 9 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tests/Functional/App/config/multipleSchema/mapping/internal/InternalAddUserPayload.yaml create mode 100644 tests/Functional/App/config/multipleSchema/mapping/internal/InternalMutation.yaml create mode 100644 tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserInput.yaml create mode 100644 tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserPayload.yaml create mode 100644 tests/Functional/App/config/multipleSchema/mapping/public/PublicMutation.yaml diff --git a/src/Relay/Mutation/InputDefinition.php b/src/Relay/Mutation/InputDefinition.php index a664239fd..fb2d85b23 100644 --- a/src/Relay/Mutation/InputDefinition.php +++ b/src/Relay/Mutation/InputDefinition.php @@ -8,13 +8,14 @@ final class InputDefinition implements MappingInterface { public function toMappingDefinition(array $config) { + $alias = \preg_replace('/(.*)?Type$/', '$1', $config['class_name']); $name = $config['name']; $name = \preg_replace('/(.*)?Input$/', '$1', $name).'Input'; $inputFields = empty($config['fields']) || !\is_array($config['fields']) ? [] : $config['fields']; return [ - $name => [ + $alias => [ 'type' => 'input-object', 'config' => [ 'name' => $name, diff --git a/src/Relay/Mutation/PayloadDefinition.php b/src/Relay/Mutation/PayloadDefinition.php index 5f65c3ff9..a71b75b5e 100644 --- a/src/Relay/Mutation/PayloadDefinition.php +++ b/src/Relay/Mutation/PayloadDefinition.php @@ -8,12 +8,13 @@ final class PayloadDefinition implements MappingInterface { public function toMappingDefinition(array $config) { + $alias = \preg_replace('/(.*)?Type$/', '$1', $config['class_name']); $name = $config['name']; $name = \preg_replace('/(.*)?Payload$/', '$1', $name).'Payload'; $outputFields = empty($config['fields']) || !\is_array($config['fields']) ? [] : $config['fields']; return [ - $name => [ + $alias => [ 'type' => 'object', 'config' => [ 'name' => $name, diff --git a/tests/Functional/App/config/multipleSchema/config.yml b/tests/Functional/App/config/multipleSchema/config.yml index fecf08445..c4a26026c 100644 --- a/tests/Functional/App/config/multipleSchema/config.yml +++ b/tests/Functional/App/config/multipleSchema/config.yml @@ -8,8 +8,11 @@ overblog_graphql: schema: public: query: PublicQuery + mutation: PublicMutation internal: query: InternalQuery + mutation: InternalMutation + mappings: types: - diff --git a/tests/Functional/App/config/multipleSchema/mapping/internal/InternalAddUserPayload.yaml b/tests/Functional/App/config/multipleSchema/mapping/internal/InternalAddUserPayload.yaml new file mode 100644 index 000000000..c2630a684 --- /dev/null +++ b/tests/Functional/App/config/multipleSchema/mapping/internal/InternalAddUserPayload.yaml @@ -0,0 +1,7 @@ +InternalAddUserPayload: + type: relay-mutation-payload + config: + name: AddUserPayload + fields: + user: + type: 'InternalUser' diff --git a/tests/Functional/App/config/multipleSchema/mapping/internal/InternalMutation.yaml b/tests/Functional/App/config/multipleSchema/mapping/internal/InternalMutation.yaml new file mode 100644 index 000000000..06b0f9971 --- /dev/null +++ b/tests/Functional/App/config/multipleSchema/mapping/internal/InternalMutation.yaml @@ -0,0 +1,12 @@ +InternalMutation: + type: object + config: + name: Mutation + description: 'Root of the schema.' + fields: + addUser: + builder: 'Relay::Mutation' + builderConfig: + inputType: PublicAddUserInput + payloadType: InternalAddUserPayload + mutateAndGetPayload: {"user":{"username":"user1","email":"email1"}} \ No newline at end of file diff --git a/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserInput.yaml b/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserInput.yaml new file mode 100644 index 000000000..07f03e517 --- /dev/null +++ b/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserInput.yaml @@ -0,0 +1,7 @@ +PublicAddUserInput: + type: relay-mutation-input + config: + name: AddUserInput + fields: + username: + type: 'String!' diff --git a/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserPayload.yaml b/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserPayload.yaml new file mode 100644 index 000000000..3487c8cac --- /dev/null +++ b/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserPayload.yaml @@ -0,0 +1,7 @@ +PublicAddUserPayload: + type: relay-mutation-payload + config: + name: AddUserPayload + fields: + user: + type: 'PublicUser' diff --git a/tests/Functional/App/config/multipleSchema/mapping/public/PublicMutation.yaml b/tests/Functional/App/config/multipleSchema/mapping/public/PublicMutation.yaml new file mode 100644 index 000000000..317aca88b --- /dev/null +++ b/tests/Functional/App/config/multipleSchema/mapping/public/PublicMutation.yaml @@ -0,0 +1,12 @@ +PublicMutation: + type: object + config: + name: Mutation + description: 'Root of the schema.' + fields: + addUser: + builder: 'Relay::Mutation' + builderConfig: + inputType: PublicAddUserInput + payloadType: PublicAddUserPayload + mutateAndGetPayload: {"user":{"username":"user1"}} \ No newline at end of file diff --git a/tests/Functional/MultipleSchema/MultipleSchemaTest.php b/tests/Functional/MultipleSchema/MultipleSchemaTest.php index 160946652..d99da1142 100644 --- a/tests/Functional/MultipleSchema/MultipleSchemaTest.php +++ b/tests/Functional/MultipleSchema/MultipleSchemaTest.php @@ -22,6 +22,23 @@ public function testPublicSchema() $result = $this->executeGraphQLRequest('{users{edges{node{username}}}}', [], 'public'); $this->assertSame([['node' => ['username' => 'user1']]], $result['data']['users']['edges']); + + $query = <<<'EOF' +mutation M { + addUser(input: {username: "user1"}) { + user { + username + } + } +} +EOF; + $expectedData = [ + 'addUser' => [ + 'user' => ['username' => 'user1'], + ], + ]; + + $this->assertGraphQL($query, $expectedData, null, [], 'public'); } public function testInternalSchema() @@ -33,6 +50,24 @@ public function testInternalSchema() $result = $this->executeGraphQLRequest('{users{edges{node{username email}}}}', [], 'internal'); $this->assertSame([['node' => ['username' => 'user1', 'email' => 'topsecret']]], $result['data']['users']['edges']); + + $query = <<<'EOF' +mutation M { + addUser(input: {username: "user1"}) { + user { + username + email + } + } +} +EOF; + $expectedData = [ + 'addUser' => [ + 'user' => ['username' => 'user1', 'email' => 'email1'], + ], + ]; + + $this->assertGraphQL($query, $expectedData, null, [], 'internal'); } public function testUnknownTypeShouldNotInfinityLoop() From b9d04eca9798a1fe2791074bc0c1c18e6f4d17ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20David?= Date: Wed, 21 Nov 2018 19:17:11 +0100 Subject: [PATCH 05/18] Fix using custom names on a relay-mutation Input and Payload --- src/Relay/Mutation/InputDefinition.php | 3 +- src/Relay/Mutation/PayloadDefinition.php | 3 +- .../App/config/multipleSchema/config.yml | 3 ++ .../internal/InternalAddUserPayload.yaml | 7 ++++ .../mapping/internal/InternalMutation.yaml | 12 +++++++ .../mapping/public/PublicAddUserInput.yaml | 7 ++++ .../mapping/public/PublicAddUserPayload.yaml | 7 ++++ .../mapping/public/PublicMutation.yaml | 12 +++++++ .../MultipleSchema/MultipleSchemaTest.php | 35 +++++++++++++++++++ 9 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tests/Functional/App/config/multipleSchema/mapping/internal/InternalAddUserPayload.yaml create mode 100644 tests/Functional/App/config/multipleSchema/mapping/internal/InternalMutation.yaml create mode 100644 tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserInput.yaml create mode 100644 tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserPayload.yaml create mode 100644 tests/Functional/App/config/multipleSchema/mapping/public/PublicMutation.yaml diff --git a/src/Relay/Mutation/InputDefinition.php b/src/Relay/Mutation/InputDefinition.php index a664239fd..fb2d85b23 100644 --- a/src/Relay/Mutation/InputDefinition.php +++ b/src/Relay/Mutation/InputDefinition.php @@ -8,13 +8,14 @@ final class InputDefinition implements MappingInterface { public function toMappingDefinition(array $config) { + $alias = \preg_replace('/(.*)?Type$/', '$1', $config['class_name']); $name = $config['name']; $name = \preg_replace('/(.*)?Input$/', '$1', $name).'Input'; $inputFields = empty($config['fields']) || !\is_array($config['fields']) ? [] : $config['fields']; return [ - $name => [ + $alias => [ 'type' => 'input-object', 'config' => [ 'name' => $name, diff --git a/src/Relay/Mutation/PayloadDefinition.php b/src/Relay/Mutation/PayloadDefinition.php index 5f65c3ff9..a71b75b5e 100644 --- a/src/Relay/Mutation/PayloadDefinition.php +++ b/src/Relay/Mutation/PayloadDefinition.php @@ -8,12 +8,13 @@ final class PayloadDefinition implements MappingInterface { public function toMappingDefinition(array $config) { + $alias = \preg_replace('/(.*)?Type$/', '$1', $config['class_name']); $name = $config['name']; $name = \preg_replace('/(.*)?Payload$/', '$1', $name).'Payload'; $outputFields = empty($config['fields']) || !\is_array($config['fields']) ? [] : $config['fields']; return [ - $name => [ + $alias => [ 'type' => 'object', 'config' => [ 'name' => $name, diff --git a/tests/Functional/App/config/multipleSchema/config.yml b/tests/Functional/App/config/multipleSchema/config.yml index fecf08445..c4a26026c 100644 --- a/tests/Functional/App/config/multipleSchema/config.yml +++ b/tests/Functional/App/config/multipleSchema/config.yml @@ -8,8 +8,11 @@ overblog_graphql: schema: public: query: PublicQuery + mutation: PublicMutation internal: query: InternalQuery + mutation: InternalMutation + mappings: types: - diff --git a/tests/Functional/App/config/multipleSchema/mapping/internal/InternalAddUserPayload.yaml b/tests/Functional/App/config/multipleSchema/mapping/internal/InternalAddUserPayload.yaml new file mode 100644 index 000000000..c2630a684 --- /dev/null +++ b/tests/Functional/App/config/multipleSchema/mapping/internal/InternalAddUserPayload.yaml @@ -0,0 +1,7 @@ +InternalAddUserPayload: + type: relay-mutation-payload + config: + name: AddUserPayload + fields: + user: + type: 'InternalUser' diff --git a/tests/Functional/App/config/multipleSchema/mapping/internal/InternalMutation.yaml b/tests/Functional/App/config/multipleSchema/mapping/internal/InternalMutation.yaml new file mode 100644 index 000000000..06b0f9971 --- /dev/null +++ b/tests/Functional/App/config/multipleSchema/mapping/internal/InternalMutation.yaml @@ -0,0 +1,12 @@ +InternalMutation: + type: object + config: + name: Mutation + description: 'Root of the schema.' + fields: + addUser: + builder: 'Relay::Mutation' + builderConfig: + inputType: PublicAddUserInput + payloadType: InternalAddUserPayload + mutateAndGetPayload: {"user":{"username":"user1","email":"email1"}} \ No newline at end of file diff --git a/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserInput.yaml b/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserInput.yaml new file mode 100644 index 000000000..07f03e517 --- /dev/null +++ b/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserInput.yaml @@ -0,0 +1,7 @@ +PublicAddUserInput: + type: relay-mutation-input + config: + name: AddUserInput + fields: + username: + type: 'String!' diff --git a/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserPayload.yaml b/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserPayload.yaml new file mode 100644 index 000000000..3487c8cac --- /dev/null +++ b/tests/Functional/App/config/multipleSchema/mapping/public/PublicAddUserPayload.yaml @@ -0,0 +1,7 @@ +PublicAddUserPayload: + type: relay-mutation-payload + config: + name: AddUserPayload + fields: + user: + type: 'PublicUser' diff --git a/tests/Functional/App/config/multipleSchema/mapping/public/PublicMutation.yaml b/tests/Functional/App/config/multipleSchema/mapping/public/PublicMutation.yaml new file mode 100644 index 000000000..317aca88b --- /dev/null +++ b/tests/Functional/App/config/multipleSchema/mapping/public/PublicMutation.yaml @@ -0,0 +1,12 @@ +PublicMutation: + type: object + config: + name: Mutation + description: 'Root of the schema.' + fields: + addUser: + builder: 'Relay::Mutation' + builderConfig: + inputType: PublicAddUserInput + payloadType: PublicAddUserPayload + mutateAndGetPayload: {"user":{"username":"user1"}} \ No newline at end of file diff --git a/tests/Functional/MultipleSchema/MultipleSchemaTest.php b/tests/Functional/MultipleSchema/MultipleSchemaTest.php index 160946652..d99da1142 100644 --- a/tests/Functional/MultipleSchema/MultipleSchemaTest.php +++ b/tests/Functional/MultipleSchema/MultipleSchemaTest.php @@ -22,6 +22,23 @@ public function testPublicSchema() $result = $this->executeGraphQLRequest('{users{edges{node{username}}}}', [], 'public'); $this->assertSame([['node' => ['username' => 'user1']]], $result['data']['users']['edges']); + + $query = <<<'EOF' +mutation M { + addUser(input: {username: "user1"}) { + user { + username + } + } +} +EOF; + $expectedData = [ + 'addUser' => [ + 'user' => ['username' => 'user1'], + ], + ]; + + $this->assertGraphQL($query, $expectedData, null, [], 'public'); } public function testInternalSchema() @@ -33,6 +50,24 @@ public function testInternalSchema() $result = $this->executeGraphQLRequest('{users{edges{node{username email}}}}', [], 'internal'); $this->assertSame([['node' => ['username' => 'user1', 'email' => 'topsecret']]], $result['data']['users']['edges']); + + $query = <<<'EOF' +mutation M { + addUser(input: {username: "user1"}) { + user { + username + email + } + } +} +EOF; + $expectedData = [ + 'addUser' => [ + 'user' => ['username' => 'user1', 'email' => 'email1'], + ], + ]; + + $this->assertGraphQL($query, $expectedData, null, [], 'internal'); } public function testUnknownTypeShouldNotInfinityLoop() From a04ac1f94d32cbedbbb49ad5b5de25000d7b7231 Mon Sep 17 00:00:00 2001 From: Bertrand Dunogier Date: Sat, 1 Dec 2018 01:00:41 +0100 Subject: [PATCH 06/18] Doc grammar improvements --- docs/events/index.md | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/docs/events/index.md b/docs/events/index.md index 9f13c68bf..f124ff643 100644 --- a/docs/events/index.md +++ b/docs/events/index.md @@ -1,19 +1,14 @@ Events ========= -[Events](http://symfony.com/doc/master/event_dispatcher.html) are a way to hook -into the execution flow. This bundle provides various events from executor -and context value initialization, executor result formatting (data, errors, extensions), -errors/warnings formatting. - -With this in mind we now turn to explain each one of them. +[Events](http://symfony.com/doc/master/event_dispatcher.html) are a way to hook into the execution flow. This bundle provides various events around the executor's context value initialization and result formatting (data, errors, extensions). Executor context value ---------------------- *Event:* `graphql.executor.context` -This event can be listen to initialize executor `contextValue` argument. +It is used to initialize the executor's `contextValue` argument. Executor initialisation @@ -21,12 +16,11 @@ Executor initialisation *Event:* `graphql.pre_executor` -This event can be listen to initialize executor execute arguments -(`schema`, `requestString`, `rootValue`, `contextValue`, `variableValue`, `operationName`). +Used to initialize the executor's execute arguments: `schema`, `requestString`, `rootValue`, `contextValue`, `variableValue`, `operationName`. -For example: +Example: -* Initializing `rootValue` with the current user (we assume that user is fully authenticated) +* Initialize the `rootValue` with the current user (we assume that user is fully authenticated) ```yaml App\EventListener\RootValueInitialization: @@ -62,12 +56,12 @@ Executor result *Event:* `graphql.post_executor` -This event can be listen to set custom error handling and formatting, it can also be use to -add information to `extensions` section. +Used to set custom error handling and formatting. It can also be used to +add information to the `extensions` section. -For example: +Example: -* How to add `credits` entry in `extensions` section +* Add a `credits` entry in the `extensions` section ```yaml App\EventListener\Credits: @@ -106,11 +100,11 @@ Error formatting *Event:* `graphql.error_formatting` -This event can be listen to add or remove fields from result `errors` and `extensions.warnings` -sections, it can also be use to log errors or exception. Each single error or warning will trigger +Used to add or remove fields from the result's `errors` and `extensions.warnings` +sections. It can also be used to log errors or exception. Each single error or warning will trigger an event. -For example: +Example: * How to add error code: @@ -158,17 +152,17 @@ For example: ``` *Note:* -- This event is dispatch by this bundle default error handler, that the reason why, disabling -error handler will also disable this event. +- This event is dispatched by this bundle's default error handler. Disabling it +will also disable this event. Type loaded ---------------- *Event:* `graphql.type_loaded` -This event can be listen to modified type before schema registration. +Used to modify types before schema registration. -For example: +Example: ```yaml App\EventListener\TypeDecorator: @@ -188,7 +182,7 @@ class TypeDecorator public function onTypeLoaded(TypeLoadedEvent $event) { $type = $event->getType(); - // here we can modified type + // modify the type } } ``` From e59a9b6e54e18d2e0781522799e96075261d2a17 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Mon, 3 Dec 2018 13:57:14 +0100 Subject: [PATCH 07/18] Fix deprecation on symfony 4.2 --- src/DependencyInjection/Configuration.php | 46 ++++++++++++----------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 13fa9630a..15c0d6611 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -37,8 +37,10 @@ public function __construct($debug, $cacheDir = null) public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root(self::NAME); + $treeBuilder = new TreeBuilder(self::NAME); + + // BC layer for symfony/config 4.1 and older + $rootNode = \method_exists($treeBuilder, 'getRootNode') ? $treeBuilder->getRootNode() : $treeBuilder->root(self::NAME); $rootNode ->children() @@ -54,9 +56,9 @@ public function getConfigTreeBuilder() private function batchingMethodSection() { - $builder = new TreeBuilder(); + $builder = new TreeBuilder('batching_method', 'enum'); /** @var EnumNodeDefinition $node */ - $node = $builder->root('batching_method', 'enum'); + $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('batching_method', 'enum'); $node ->values(['relay', 'apollo']) @@ -68,9 +70,9 @@ private function batchingMethodSection() private function errorsHandlerSection() { - $builder = new TreeBuilder(); + $builder = new TreeBuilder('errors_handler'); /** @var ArrayNodeDefinition $node */ - $node = $builder->root('errors_handler'); + $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('errors_handler'); $node ->treatFalseLike(['enabled' => false]) ->treatTrueLike(['enabled' => true]) @@ -103,9 +105,9 @@ private function errorsHandlerSection() private function definitionsSection() { - $builder = new TreeBuilder(); + $builder = new TreeBuilder('definitions'); /** @var ArrayNodeDefinition $node */ - $node = $builder->root('definitions'); + $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('definitions'); $node ->addDefaultsIfNotSet() ->children() @@ -134,9 +136,9 @@ private function definitionsSection() private function servicesSection() { - $builder = new TreeBuilder(); + $builder = new TreeBuilder('services'); /** @var ArrayNodeDefinition $node */ - $node = $builder->root('services'); + $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('services'); $node ->addDefaultsIfNotSet() ->children() @@ -158,9 +160,9 @@ private function servicesSection() private function securitySection() { - $builder = new TreeBuilder(); + $builder = new TreeBuilder('security'); /** @var ArrayNodeDefinition $node */ - $node = $builder->root('security'); + $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('security'); $node ->addDefaultsIfNotSet() ->children() @@ -176,9 +178,9 @@ private function securitySection() private function definitionsSchemaSection() { - $builder = new TreeBuilder(); + $builder = new TreeBuilder('schema'); /** @var ArrayNodeDefinition $node */ - $node = $builder->root('schema'); + $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('schema'); $node ->beforeNormalization() ->ifTrue(function ($v) { @@ -212,9 +214,9 @@ private function definitionsSchemaSection() private function definitionsAutoMappingSection() { - $builder = new TreeBuilder(); + $builder = new TreeBuilder('auto_mapping'); /** @var ArrayNodeDefinition $node */ - $node = $builder->root('auto_mapping'); + $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('auto_mapping'); $node ->treatFalseLike(['enabled' => false]) ->treatTrueLike(['enabled' => true]) @@ -234,8 +236,8 @@ private function definitionsAutoMappingSection() private function definitionsMappingsSection() { - $builder = new TreeBuilder(); - $node = $builder->root('mappings'); + $builder = new TreeBuilder('mappings'); + $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('mappings'); $node ->children() ->arrayNode('auto_discover') @@ -288,9 +290,9 @@ private function definitionsMappingsSection() */ private function builderSection($name) { - $builder = new TreeBuilder(); + $builder = new TreeBuilder($name); /** @var ArrayNodeDefinition $node */ - $node = $builder->root($name); + $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root($name); $node->beforeNormalization() ->ifTrue(function ($v) { return \is_array($v) && !empty($v); @@ -328,9 +330,9 @@ private function builderSection($name) */ private function securityQuerySection($name, $disabledValue) { - $builder = new TreeBuilder(); + $builder = new TreeBuilder($name, 'scalar'); /** @var ScalarNodeDefinition $node */ - $node = $builder->root($name, 'scalar'); + $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root($name, 'scalar'); $node->beforeNormalization() ->ifTrue(function ($v) { return \is_string($v) && \is_numeric($v); From 4f00cb4e5318be0641e9ff0c2d3cd47fe0a3e78d Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Mon, 3 Dec 2018 16:15:54 +0100 Subject: [PATCH 08/18] Remove useless travis cache --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c9ae138f9..366d40c24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,6 @@ cache: directories: - $HOME/.composer/cache - $HOME/.php_cs.cache - - vendor/ before_install: - if [ "${STABILITY}" != "" ]; then perl -pi -e 's/^}$/,"minimum-stability":"'"${STABILITY}"'"}/' composer.json; fi; From 4d852864b97ec2cacc4d7ece921e1ac531205079 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Mon, 3 Dec 2018 16:16:15 +0100 Subject: [PATCH 09/18] Use sf 4.2 stable version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 366d40c24..1227c6537 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,7 +45,7 @@ jobs: - php: 7.2 env: SYMFONY_VERSION=4.1.* - php: 7.2 - env: SYMFONY_VERSION=4.2.* STABILITY=beta + env: SYMFONY_VERSION=4.2.* - php: nightly env: COMPOSER_UPDATE_FLAGS=--ignore-platform-reqs - php: 7.2 From 0354a51e4a068e7039ea662b889b929eb428239e Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Tue, 4 Dec 2018 17:24:53 +0100 Subject: [PATCH 10/18] Remove deprecation messages for symfony 4.2 --- src/Config/CustomScalarTypeDefinition.php | 5 +-- src/Config/EnumTypeDefinition.php | 5 +-- src/Config/InputObjectTypeDefinition.php | 5 +-- src/Config/InterfaceTypeDefinition.php | 5 +-- src/Config/ObjectTypeDefinition.php | 4 +-- src/Config/TypeDefinition.php | 32 ++++++++++------- src/Config/TypeWithOutputFieldsDefinition.php | 4 +-- src/Config/UnionTypeDefinition.php | 5 +-- src/DependencyInjection/Configuration.php | 34 +++++++++++++------ .../TypesConfiguration.php | 4 +-- 10 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/Config/CustomScalarTypeDefinition.php b/src/Config/CustomScalarTypeDefinition.php index 094f4e8f3..c8b1cb3e4 100644 --- a/src/Config/CustomScalarTypeDefinition.php +++ b/src/Config/CustomScalarTypeDefinition.php @@ -2,14 +2,11 @@ namespace Overblog\GraphQLBundle\Config; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; - class CustomScalarTypeDefinition extends TypeDefinition { public function getDefinition() { - $builder = new TreeBuilder(); - $node = $builder->root('_custom_scalar_config'); + $node = self::createNode('_custom_scalar_config'); $node ->children() diff --git a/src/Config/EnumTypeDefinition.php b/src/Config/EnumTypeDefinition.php index e3a255c35..9117b30a0 100644 --- a/src/Config/EnumTypeDefinition.php +++ b/src/Config/EnumTypeDefinition.php @@ -2,14 +2,11 @@ namespace Overblog\GraphQLBundle\Config; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; - class EnumTypeDefinition extends TypeDefinition { public function getDefinition() { - $builder = new TreeBuilder(); - $node = $builder->root('_enum_config'); + $node = self::createNode('_enum_config'); $node ->children() diff --git a/src/Config/InputObjectTypeDefinition.php b/src/Config/InputObjectTypeDefinition.php index 58bfaef92..d8198b3dd 100644 --- a/src/Config/InputObjectTypeDefinition.php +++ b/src/Config/InputObjectTypeDefinition.php @@ -2,14 +2,11 @@ namespace Overblog\GraphQLBundle\Config; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; - class InputObjectTypeDefinition extends TypeDefinition { public function getDefinition() { - $builder = new TreeBuilder(); - $node = $builder->root('_input_object_config'); + $node = self::createNode('_input_object_config'); $node ->children() diff --git a/src/Config/InterfaceTypeDefinition.php b/src/Config/InterfaceTypeDefinition.php index ba95dc90c..8a9c7909f 100644 --- a/src/Config/InterfaceTypeDefinition.php +++ b/src/Config/InterfaceTypeDefinition.php @@ -2,14 +2,11 @@ namespace Overblog\GraphQLBundle\Config; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; - class InterfaceTypeDefinition extends TypeWithOutputFieldsDefinition { public function getDefinition() { - $builder = new TreeBuilder(); - $node = $builder->root('_interface_config'); + $node = self::createNode('_interface_config'); $node ->children() diff --git a/src/Config/ObjectTypeDefinition.php b/src/Config/ObjectTypeDefinition.php index 65547c65e..be3de254e 100644 --- a/src/Config/ObjectTypeDefinition.php +++ b/src/Config/ObjectTypeDefinition.php @@ -3,14 +3,12 @@ namespace Overblog\GraphQLBundle\Config; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; class ObjectTypeDefinition extends TypeWithOutputFieldsDefinition { public function getDefinition() { - $builder = new TreeBuilder(); - $node = $builder->root('_object_config'); + $node = self::createNode('_object_config'); $node ->children() diff --git a/src/Config/TypeDefinition.php b/src/Config/TypeDefinition.php index 4a61a9b47..aef186046 100644 --- a/src/Config/TypeDefinition.php +++ b/src/Config/TypeDefinition.php @@ -2,6 +2,7 @@ namespace Overblog\GraphQLBundle\Config; +use Overblog\GraphQLBundle\DependencyInjection\Configuration; use Symfony\Component\Config\Definition\Builder\TreeBuilder; abstract class TypeDefinition @@ -22,16 +23,14 @@ public static function create() protected function resolveTypeSection() { - $builder = new TreeBuilder(); - $node = $builder->root('resolveType', 'variable'); + $node = self::createNode('resolveType', 'variable'); return $node; } protected function nameSection() { - $builder = new TreeBuilder(); - $node = $builder->root('name', 'scalar'); + $node = self::createNode('name', 'scalar'); $node->isRequired(); $node->validate() ->ifTrue(function ($name) { @@ -45,24 +44,21 @@ protected function nameSection() protected function defaultValueSection() { - $builder = new TreeBuilder(); - $node = $builder->root('defaultValue', 'variable'); + $node = self::createNode('defaultValue', 'variable'); return $node; } protected function descriptionSection() { - $builder = new TreeBuilder(); - $node = $builder->root('description', 'scalar'); + $node = self::createNode('description', 'scalar'); return $node; } protected function deprecationReasonSelection() { - $builder = new TreeBuilder(); - $node = $builder->root('deprecationReason', 'scalar'); + $node = self::createNode('deprecationReason', 'scalar'); $node->info('Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced)'); @@ -71,8 +67,7 @@ protected function deprecationReasonSelection() protected function typeSelection($isRequired = false) { - $builder = new TreeBuilder(); - $node = $builder->root('type', 'scalar'); + $node = self::createNode('type', 'scalar'); $node->info('One of internal or custom types.'); @@ -82,4 +77,17 @@ protected function typeSelection($isRequired = false) return $node; } + + /** + * @internal + * + * @param string $name + * @param string $type + * + * @return \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition + */ + protected static function createNode($name, $type = 'array') + { + return Configuration::getRootNodeWithoutDeprecation(new TreeBuilder($name, $type), $name, $type); + } } diff --git a/src/Config/TypeWithOutputFieldsDefinition.php b/src/Config/TypeWithOutputFieldsDefinition.php index 7321181bc..224ccf0bd 100644 --- a/src/Config/TypeWithOutputFieldsDefinition.php +++ b/src/Config/TypeWithOutputFieldsDefinition.php @@ -3,7 +3,6 @@ namespace Overblog\GraphQLBundle\Config; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; abstract class TypeWithOutputFieldsDefinition extends TypeDefinition { @@ -14,8 +13,7 @@ abstract class TypeWithOutputFieldsDefinition extends TypeDefinition */ protected function outputFieldsSelection($name = 'fields') { - $builder = new TreeBuilder(); - $node = $builder->root($name); + $node = self::createNode($name); $node ->isRequired() ->requiresAtLeastOneElement(); diff --git a/src/Config/UnionTypeDefinition.php b/src/Config/UnionTypeDefinition.php index a5ddf1410..b4ab29c48 100644 --- a/src/Config/UnionTypeDefinition.php +++ b/src/Config/UnionTypeDefinition.php @@ -2,14 +2,11 @@ namespace Overblog\GraphQLBundle\Config; -use Symfony\Component\Config\Definition\Builder\TreeBuilder; - class UnionTypeDefinition extends TypeDefinition { public function getDefinition() { - $builder = new TreeBuilder(); - $node = $builder->root('_union_config'); + $node = self::createNode('_union_config'); $node ->children() diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 15c0d6611..fdfb5f13f 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -39,8 +39,7 @@ public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(self::NAME); - // BC layer for symfony/config 4.1 and older - $rootNode = \method_exists($treeBuilder, 'getRootNode') ? $treeBuilder->getRootNode() : $treeBuilder->root(self::NAME); + $rootNode = self::getRootNodeWithoutDeprecation($treeBuilder, self::NAME); $rootNode ->children() @@ -58,7 +57,7 @@ private function batchingMethodSection() { $builder = new TreeBuilder('batching_method', 'enum'); /** @var EnumNodeDefinition $node */ - $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('batching_method', 'enum'); + $node = self::getRootNodeWithoutDeprecation($builder, 'batching_method', 'enum'); $node ->values(['relay', 'apollo']) @@ -72,7 +71,7 @@ private function errorsHandlerSection() { $builder = new TreeBuilder('errors_handler'); /** @var ArrayNodeDefinition $node */ - $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('errors_handler'); + $node = self::getRootNodeWithoutDeprecation($builder, 'errors_handler'); $node ->treatFalseLike(['enabled' => false]) ->treatTrueLike(['enabled' => true]) @@ -138,7 +137,7 @@ private function servicesSection() { $builder = new TreeBuilder('services'); /** @var ArrayNodeDefinition $node */ - $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('services'); + $node = self::getRootNodeWithoutDeprecation($builder, 'services'); $node ->addDefaultsIfNotSet() ->children() @@ -180,7 +179,7 @@ private function definitionsSchemaSection() { $builder = new TreeBuilder('schema'); /** @var ArrayNodeDefinition $node */ - $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('schema'); + $node = self::getRootNodeWithoutDeprecation($builder, 'schema'); $node ->beforeNormalization() ->ifTrue(function ($v) { @@ -216,7 +215,7 @@ private function definitionsAutoMappingSection() { $builder = new TreeBuilder('auto_mapping'); /** @var ArrayNodeDefinition $node */ - $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('auto_mapping'); + $node = self::getRootNodeWithoutDeprecation($builder, 'auto_mapping'); $node ->treatFalseLike(['enabled' => false]) ->treatTrueLike(['enabled' => true]) @@ -237,7 +236,7 @@ private function definitionsAutoMappingSection() private function definitionsMappingsSection() { $builder = new TreeBuilder('mappings'); - $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root('mappings'); + $node = self::getRootNodeWithoutDeprecation($builder, 'mappings'); $node ->children() ->arrayNode('auto_discover') @@ -292,7 +291,7 @@ private function builderSection($name) { $builder = new TreeBuilder($name); /** @var ArrayNodeDefinition $node */ - $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root($name); + $node = self::getRootNodeWithoutDeprecation($builder, $name); $node->beforeNormalization() ->ifTrue(function ($v) { return \is_array($v) && !empty($v); @@ -332,7 +331,7 @@ private function securityQuerySection($name, $disabledValue) { $builder = new TreeBuilder($name, 'scalar'); /** @var ScalarNodeDefinition $node */ - $node = \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root($name, 'scalar'); + $node = self::getRootNodeWithoutDeprecation($builder, $name, 'scalar'); $node->beforeNormalization() ->ifTrue(function ($v) { return \is_string($v) && \is_numeric($v); @@ -363,4 +362,19 @@ private function securityQuerySection($name, $disabledValue) return $node; } + + /** + * @internal + * + * @param TreeBuilder $builder + * @param string|null $name + * @param string $type + * + * @return ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition + */ + public static function getRootNodeWithoutDeprecation(TreeBuilder $builder, $name, $type = 'array') + { + // BC layer for symfony/config 4.1 and older + return \method_exists($builder, 'getRootNode') ? $builder->getRootNode() : $builder->root($name, $type); + } } diff --git a/src/DependencyInjection/TypesConfiguration.php b/src/DependencyInjection/TypesConfiguration.php index cae2924a8..09578a22a 100644 --- a/src/DependencyInjection/TypesConfiguration.php +++ b/src/DependencyInjection/TypesConfiguration.php @@ -21,8 +21,8 @@ class TypesConfiguration implements ConfigurationInterface public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('overblog_graphql_types'); + $treeBuilder = new TreeBuilder('overblog_graphql_types'); + $rootNode = Configuration::getRootNodeWithoutDeprecation($treeBuilder, 'overblog_graphql_types'); $configTypeKeys = \array_map( function ($type) { From baf8dffcf98e30fd471a6f908c81d9b4d1cc40b2 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Wed, 5 Dec 2018 07:30:13 +0100 Subject: [PATCH 11/18] Move travis coverage job to code qa stage --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1227c6537..ea48ee5fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,9 @@ jobs: env: SYMFONY_VERSION=4.2.* - php: nightly env: COMPOSER_UPDATE_FLAGS=--ignore-platform-reqs - - php: 7.2 + + - stage: Code Quality + php: 7.2 env: COVERAGE before_script: - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,} From 63252699cbf2fed548cfc01470ae3056831404c1 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Wed, 5 Dec 2018 07:45:11 +0100 Subject: [PATCH 12/18] Add a hack to fix travis build --- tests/Functional/Inheritance/InheritanceTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Functional/Inheritance/InheritanceTest.php b/tests/Functional/Inheritance/InheritanceTest.php index ae118ca74..694d7f9d3 100644 --- a/tests/Functional/Inheritance/InheritanceTest.php +++ b/tests/Functional/Inheritance/InheritanceTest.php @@ -21,7 +21,8 @@ protected function setUp() public function testObjectInheritance() { $this->assertArrayHasKey('Query', $this->config); - $this->assertSame( + // TODO(mcg-web): understand why travis fields order diffed from local test + $this->assertEquals( [ 'type' => 'object', InheritanceProcessor::INHERITS_KEY => ['QueryFoo', 'QueryBar', 'QueryHelloWord'], From c3f17bddbe733843834308a6740a8c6454dd555c Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Fri, 7 Dec 2018 08:17:16 +0100 Subject: [PATCH 13/18] Add travis php 7.3 build --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ea48ee5fd..dba2c72cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,9 +42,9 @@ jobs: env: SYMFONY_VERSION=3.3.* PHPUNIT_VERSION=^6.0 - php: 7.2 env: SYMFONY_VERSION=3.4.* - - php: 7.2 + - php: 7.3 env: SYMFONY_VERSION=4.1.* - - php: 7.2 + - php: 7.3 env: SYMFONY_VERSION=4.2.* - php: nightly env: COMPOSER_UPDATE_FLAGS=--ignore-platform-reqs From ecfac7df4fd3b7e429f4e3886bc060dedce0b33d Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Tue, 11 Dec 2018 08:03:56 +0100 Subject: [PATCH 14/18] Allow promise for AccessResolver::checkAccessForStrictMode --- src/Resolver/AccessResolver.php | 61 ++++++++++++++----- .../App/Resolver/ConnectionResolver.php | 5 ++ .../config/access/mapping/access.types.yml | 4 ++ .../App/config/connection/services.yml | 1 + tests/Functional/Security/AccessTest.php | 40 +++++++++++- 5 files changed, 94 insertions(+), 17 deletions(-) diff --git a/src/Resolver/AccessResolver.php b/src/Resolver/AccessResolver.php index 5be94d965..ce702e68d 100644 --- a/src/Resolver/AccessResolver.php +++ b/src/Resolver/AccessResolver.php @@ -28,22 +28,18 @@ public function resolve(callable $accessChecker, callable $resolveCallback, arra return $this->checkAccessForStrictMode($accessChecker, $resolveCallback, $resolveArgs); } - $result = \call_user_func_array($resolveCallback, $resolveArgs); + $resultOrPromise = \call_user_func_array($resolveCallback, $resolveArgs); - if ($result instanceof Promise) { - $result = $result->adoptedPromise; - } - - if ($this->promiseAdapter->isThenable($result) || $result instanceof SyncPromise) { - return $this->promiseAdapter->then( - new Promise($result, $this->promiseAdapter), + if ($this->isThenable($resultOrPromise)) { + return $this->createPromise( + $resultOrPromise, function ($result) use ($accessChecker, $resolveArgs) { return $this->processFilter($result, $accessChecker, $resolveArgs); } ); } - return $this->processFilter($result, $accessChecker, $resolveArgs); + return $this->processFilter($resultOrPromise, $accessChecker, $resolveArgs); } private static function isMutationRootField(ResolveInfo $info) @@ -53,12 +49,21 @@ private static function isMutationRootField(ResolveInfo $info) private function checkAccessForStrictMode(callable $accessChecker, callable $resolveCallback, array $resolveArgs = []) { - if (!$this->hasAccess($accessChecker, $resolveArgs)) { - $exceptionClassName = self::isMutationRootField($resolveArgs[3]) ? UserError::class : UserWarning::class; - throw new $exceptionClassName('Access denied to this field.'); - } + $promiseOrHasAccess = $this->hasAccess($accessChecker, $resolveArgs); + $callback = function ($hasAccess) use ($resolveArgs, $resolveCallback) { + if (!$hasAccess) { + $exceptionClassName = self::isMutationRootField($resolveArgs[3]) ? UserError::class : UserWarning::class; + throw new $exceptionClassName('Access denied to this field.'); + } + + return \call_user_func_array($resolveCallback, $resolveArgs); + }; - return \call_user_func_array($resolveCallback, $resolveArgs); + if ($this->isThenable($promiseOrHasAccess)) { + return $this->createPromise($promiseOrHasAccess, $callback); + } else { + return $callback($promiseOrHasAccess); + } } private function processFilter($result, $accessChecker, $resolveArgs) @@ -89,9 +94,33 @@ function (Edge $edge) use ($accessChecker, $resolveArgs) { private function hasAccess(callable $accessChecker, array $resolveArgs = [], $object = null) { $resolveArgs[] = $object; - $access = (bool) \call_user_func_array($accessChecker, $resolveArgs); + $accessOrPromise = \call_user_func_array($accessChecker, $resolveArgs); - return $access; + return $accessOrPromise; + } + + private function isThenable($object) + { + $object = $this->extractAdoptedPromise($object); + + return $this->promiseAdapter->isThenable($object) || $object instanceof SyncPromise; + } + + private function extractAdoptedPromise($object) + { + if ($object instanceof Promise) { + $object = $object->adoptedPromise; + } + + return $object; + } + + private function createPromise($promise, callable $onFulfilled = null) + { + return $this->promiseAdapter->then( + new Promise($this->extractAdoptedPromise($promise), $this->promiseAdapter), + $onFulfilled + ); } /** diff --git a/tests/Functional/App/Resolver/ConnectionResolver.php b/tests/Functional/App/Resolver/ConnectionResolver.php index 84c2c0ca8..8418229c1 100644 --- a/tests/Functional/App/Resolver/ConnectionResolver.php +++ b/tests/Functional/App/Resolver/ConnectionResolver.php @@ -80,4 +80,9 @@ public function resolveQuery() return $this->allUsers[0]; } + + public function resolvePromiseFullFilled($value) + { + return $this->promiseAdapter->createFulfilled($value); + } } diff --git a/tests/Functional/App/config/access/mapping/access.types.yml b/tests/Functional/App/config/access/mapping/access.types.yml index 30e0589c4..2c4b2b206 100644 --- a/tests/Functional/App/config/access/mapping/access.types.yml +++ b/tests/Functional/App/config/access/mapping/access.types.yml @@ -38,6 +38,10 @@ User: access: "@=hasRole('ROLE_ADMIN')" resolve: ['ROLE_USER'] isEnabled: + # access as a promise + access: "@=resolver('promise', [args['hasAccess']])" + args: + hasAccess: {type: Boolean!} type: Boolean resolve: true friends: diff --git a/tests/Functional/App/config/connection/services.yml b/tests/Functional/App/config/connection/services.yml index 2e8809ac0..4af041356 100644 --- a/tests/Functional/App/config/connection/services.yml +++ b/tests/Functional/App/config/connection/services.yml @@ -8,3 +8,4 @@ services: - { name: "overblog_graphql.resolver", alias: "node", method: "resolveNode" } - { name: "overblog_graphql.resolver", alias: "query", method: "resolveQuery" } - { name: "overblog_graphql.resolver", alias: "connection", method: "resolveConnection" } + - { name: "overblog_graphql.resolver", alias: "promise", method: "resolvePromiseFullFilled" } diff --git a/tests/Functional/Security/AccessTest.php b/tests/Functional/Security/AccessTest.php index f484d18cc..202a05e4e 100644 --- a/tests/Functional/Security/AccessTest.php +++ b/tests/Functional/Security/AccessTest.php @@ -15,7 +15,7 @@ class AccessTest extends TestCase private $userRolesQuery = 'query { user { roles } }'; - private $userIsEnabledQuery = 'query { user { isEnabled } }'; + private $userIsEnabledQuery = 'query ($hasAccess: Boolean = true) { user { isEnabled(hasAccess: $hasAccess) } }'; private $userFriendsQuery = <<<'EOF' query { @@ -66,6 +66,44 @@ public function testCustomClassLoaderNotRegister() $this->assertResponse($this->userNameQuery, [], static::ANONYMOUS_USER, 'access'); } + public function testNotAuthenticatedUserAccessAsPromisedFulfilledTrue() + { + $this->assertResponse( + $this->userIsEnabledQuery, + ['data' => ['user' => ['isEnabled' => true]]], + static::ANONYMOUS_USER, + 'access' + ); + } + + public function testNotAuthenticatedUserAccessAsPromisedFulfilledFalse() + { + $this->assertResponse( + $this->userIsEnabledQuery, + [ + 'data' => [ + 'user' => [ + 'isEnabled' => null, + ], + ], + 'extensions' => [ + 'warnings' => [ + [ + 'message' => 'Access denied to this field.', + 'category' => 'user', + 'locations' => [['line' => 1, 'column' => 45]], + 'path' => ['user', 'isEnabled'], + ], + ], + ], + ], + static::ANONYMOUS_USER, + 'access', + '', + ['hasAccess' => false] + ); + } + public function testNotAuthenticatedUserAccessToUserName() { $expected = [ From 7a119fc523eb9b1fcc9ffee87dd045216f79929b Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Wed, 19 Dec 2018 08:17:34 +0100 Subject: [PATCH 15/18] Add private service doc section in expression language --- docs/definitions/expression-language.md | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/definitions/expression-language.md b/docs/definitions/expression-language.md index f1c918779..0eb3397ee 100644 --- a/docs/definitions/expression-language.md +++ b/docs/definitions/expression-language.md @@ -40,6 +40,42 @@ Expression | Description | Scope [For more details on expression syntax](http://symfony.com/doc/current/components/expression_language/syntax.html) +Private services +---------------- + +It is not possible to use private services with `service` or `serv` functions since this is equivalent to call +`get` method on the container. Private services must be tag as global variable to be accessible. + +Yaml example: + +```yaml +App\MyPrivateService: + public: false + tags: + - { name: overblog_graphql.global_variable, alias: my_private_service } +``` + +To use a vendor private services: + +```php +findDefinition(\Vendor\PrivateService::class); +$vendorPrivateServiceDef->addTag('overblog_graphql.global_variable', ['alias' => 'vendor_private_service']); +``` + +Usage: + +```yaml +MyType: + type: object + config: + fields: + name: + type: String! + resolve: '@=my_private_service.formatName(value)' +``` + Custom expression function -------------------------- From 2d0949dbda2e2e4d21406bbcf6f3c7758d5c8000 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Thu, 20 Dec 2018 08:34:25 +0100 Subject: [PATCH 16/18] Fix global variables in access --- src/ExpressionLanguage/ExpressionLanguage.php | 7 +++++++ src/Generator/TypeGenerator.php | 10 ++++++++-- tests/Functional/App/Service/PrivateService.php | 11 +++++++++++ tests/Functional/App/config/access/config.yml | 7 +++++++ .../App/config/access/mapping/access.types.yml | 1 + 5 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/Functional/App/Service/PrivateService.php diff --git a/src/ExpressionLanguage/ExpressionLanguage.php b/src/ExpressionLanguage/ExpressionLanguage.php index e6a98ac0d..d41522b7d 100644 --- a/src/ExpressionLanguage/ExpressionLanguage.php +++ b/src/ExpressionLanguage/ExpressionLanguage.php @@ -6,6 +6,8 @@ class ExpressionLanguage extends BaseExpressionLanguage { + const KNOWN_NAMES = ['value', 'args', 'context', 'info', 'object']; + private $globalNames = []; /** @@ -17,6 +19,11 @@ public function addGlobalName($index, $name) $this->globalNames[$index] = $name; } + public function getGlobalNames() + { + return $this->globalNames; + } + public function compile($expression, $names = []) { return parent::compile($expression, \array_merge($names, $this->globalNames)); diff --git a/src/Generator/TypeGenerator.php b/src/Generator/TypeGenerator.php index bdc5ba1b4..447723466 100644 --- a/src/Generator/TypeGenerator.php +++ b/src/Generator/TypeGenerator.php @@ -6,6 +6,7 @@ use Overblog\GraphQLBundle\Config\Processor; use Overblog\GraphQLBundle\Definition\Argument; use Overblog\GraphQLBundle\Definition\Type\CustomScalarType; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage; use Overblog\GraphQLGenerator\Generator\TypeGenerator as BaseTypeGenerator; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\Filesystem\Filesystem; @@ -195,9 +196,14 @@ protected function generateParentClassName(array $config) protected function generateUseStrictAccess(array $value) { + $expressionLanguage = $this->getExpressionLanguage(); $useStrictAccess = 'true'; - if (null !== $this->getExpressionLanguage() && $this->arrayKeyExistsAndIsNotNull($value, 'access') && $value['access'] instanceof Expression) { - $parsedExpression = $this->getExpressionLanguage()->parse($value['access'], ['value', 'args', 'context', 'info', 'object']); + if (null !== $expressionLanguage && $this->arrayKeyExistsAndIsNotNull($value, 'access') && $value['access'] instanceof Expression) { + $names = ExpressionLanguage::KNOWN_NAMES; + if ($expressionLanguage instanceof ExpressionLanguage) { + $names = \array_merge($names, $expressionLanguage->getGlobalNames()); + } + $parsedExpression = $expressionLanguage->parse($value['access'], $names); $serializedNode = \str_replace("\n", '//', (string) $parsedExpression->getNodes()); $useStrictAccess = false === \strpos($serializedNode, 'NameNode(name: \'object\')') ? 'true' : 'false'; } diff --git a/tests/Functional/App/Service/PrivateService.php b/tests/Functional/App/Service/PrivateService.php new file mode 100644 index 000000000..d1b41997e --- /dev/null +++ b/tests/Functional/App/Service/PrivateService.php @@ -0,0 +1,11 @@ + Date: Thu, 20 Dec 2018 08:35:14 +0100 Subject: [PATCH 17/18] Add a test case for global variables in resolve --- tests/Functional/App/Resolver/NodeResolver.php | 12 ++---------- tests/Functional/App/config/node/config.yml | 2 -- .../Functional/App/config/node/mapping/node_type.yml | 2 +- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/tests/Functional/App/Resolver/NodeResolver.php b/tests/Functional/App/Resolver/NodeResolver.php index c4fb83db5..6d4d7d99b 100644 --- a/tests/Functional/App/Resolver/NodeResolver.php +++ b/tests/Functional/App/Resolver/NodeResolver.php @@ -11,9 +11,6 @@ class NodeResolver implements ContainerAwareInterface { use ContainerAwareTrait; - /** @var TypeResolver */ - private $typeResolver; - private $userData = [ '1' => [ 'id' => 1, @@ -36,11 +33,6 @@ class NodeResolver implements ContainerAwareInterface ], ]; - public function __construct(TypeResolver $typeResolver) - { - $this->typeResolver = $typeResolver; - } - public function resolvePhotoField($value, ResolveInfo $info) { switch ($info->fieldName) { @@ -67,9 +59,9 @@ public function idFetcher($id) public function typeResolver($value) { if (isset($value['name'])) { - return $this->typeResolver->resolve('User'); + return 'User'; } else { - return $this->typeResolver->resolve('Photo'); + return 'Photo'; } } } diff --git a/tests/Functional/App/config/node/config.yml b/tests/Functional/App/config/node/config.yml index 677da302d..92a84773d 100644 --- a/tests/Functional/App/config/node/config.yml +++ b/tests/Functional/App/config/node/config.yml @@ -4,8 +4,6 @@ imports: services: node_resolver: class: Overblog\GraphQLBundle\Tests\Functional\App\Resolver\NodeResolver - arguments: - - "@overblog_graphql.type_resolver" tags: - { name: "overblog_graphql.resolver", method: "typeResolver" } - { name: "overblog_graphql.resolver", alias: "node_id_fetcher", method: "idFetcher" } diff --git a/tests/Functional/App/config/node/mapping/node_type.yml b/tests/Functional/App/config/node/mapping/node_type.yml index fa5aee5ce..e50ac5a98 100644 --- a/tests/Functional/App/config/node/mapping/node_type.yml +++ b/tests/Functional/App/config/node/mapping/node_type.yml @@ -1,4 +1,4 @@ Node: type: relay-node config: - resolveType: '@=resolver("node_resolver::typeResolver", [value])' + resolveType: '@=typeResolver.resolve(resolver("node_resolver::typeResolver", [value]))' From 4e441b35eb9741595a713aefed13064b5ff62ae7 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Thu, 20 Dec 2018 09:36:31 +0100 Subject: [PATCH 18/18] Give some love to php cs --- src/ExpressionLanguage/ExpressionLanguage.php | 2 +- tests/Functional/App/Service/PrivateService.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ExpressionLanguage/ExpressionLanguage.php b/src/ExpressionLanguage/ExpressionLanguage.php index b3855c728..ebaee286f 100644 --- a/src/ExpressionLanguage/ExpressionLanguage.php +++ b/src/ExpressionLanguage/ExpressionLanguage.php @@ -8,7 +8,7 @@ class ExpressionLanguage extends BaseExpressionLanguage { - const KNOWN_NAMES = ['value', 'args', 'context', 'info', 'object']; + public const KNOWN_NAMES = ['value', 'args', 'context', 'info', 'object']; private $globalNames = []; diff --git a/tests/Functional/App/Service/PrivateService.php b/tests/Functional/App/Service/PrivateService.php index d1b41997e..323c9cc31 100644 --- a/tests/Functional/App/Service/PrivateService.php +++ b/tests/Functional/App/Service/PrivateService.php @@ -1,5 +1,7 @@