From d7cbb60f55b94754f2714a35d000b47aee2f3cc7 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Tue, 13 Mar 2018 07:34:48 +0100 Subject: [PATCH 01/36] Init 0.12-dev --- .travis.yml | 9 --------- README.md | 9 +++++---- composer.json | 40 ++++++++++++++++++++-------------------- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/.travis.yml b/.travis.yml index 958a5c769..1b080de67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,17 +14,8 @@ branches: matrix: fast_finish: true include: - - php: 5.6 - env: COMPOSER_UPDATE_FLAGS=--prefer-lowest SYMFONY_DEPRECATIONS_HELPER=disabled - - php: 5.6 - env: SYMFONY_VERSION=3.1.* SYMFONY_DEPRECATIONS_HELPER=disabled - - php: 7.0 - env: SYMFONY_VERSION=3.2.* - - php: 7.1 - php: 7.2 env: PHP_CS_FIXER=true - - php: 7.2 - env: SYMFONY_VERSION=3.3.* - php: 7.2 env: SYMFONY_VERSION=3.4.* - php: 7.2 diff --git a/README.md b/README.md index 518b629a6..2181fe017 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,11 @@ It also supports: Browse your version documentation: -* [0.8](https://github.com/overblog/GraphQLBundle/blob/0.8/README.md) -* [0.9](https://github.com/overblog/GraphQLBundle/blob/0.9/README.md) -* [0.10](https://github.com/overblog/GraphQLBundle/blob/0.10/README.md) -* [0.11](https://github.com/overblog/GraphQLBundle/blob/0.11/README.md) +* [0.8 (DEPRECATED)](https://github.com/overblog/GraphQLBundle/blob/0.8/README.md) +* [0.9 (DEPRECATED)](https://github.com/overblog/GraphQLBundle/blob/0.9/README.md) +* [0.10 (DEPRECATED)](https://github.com/overblog/GraphQLBundle/blob/0.10/README.md) +* [0.11 (STABLE)](https://github.com/overblog/GraphQLBundle/blob/0.11/README.md) +* [0.12 (DEV)](https://github.com/overblog/GraphQLBundle/blob/master/README.md) Documentation ------------- diff --git a/composer.json b/composer.json index ec8825850..15ff3ddd4 100644 --- a/composer.json +++ b/composer.json @@ -29,16 +29,16 @@ "sort-packages": true }, "require": { - "php": ">=5.6", + "php": ">=7.1", "overblog/graphql-php-generator": "^0.7.0", "psr/log": "^1.0", - "symfony/config": "^3.1 || ^4.0", - "symfony/dependency-injection": "^3.1 || ^4.0", - "symfony/event-dispatcher": "^3.1 || ^4.0", - "symfony/expression-language": "^3.1 || ^4.0", - "symfony/framework-bundle": "^3.1 || ^4.0", - "symfony/options-resolver": "^3.1 || ^4.0", - "symfony/property-access": "^3.1 || ^4.0", + "symfony/config": "^3.4 || ^4.0", + "symfony/dependency-injection": "^3.4 || ^4.0", + "symfony/event-dispatcher": "^3.4 || ^4.0", + "symfony/expression-language": "^3.4 || ^4.0", + "symfony/framework-bundle": "^3.4 || ^4.0", + "symfony/options-resolver": "^3.4 || ^4.0", + "symfony/property-access": "^3.4 || ^4.0", "webonyx/graphql-php": "^0.11.2" }, "suggest": { @@ -50,21 +50,21 @@ "phpunit/phpunit": "^5.7.26 || ^6.0", "react/promise": "^2.5", "sensio/framework-extra-bundle": "^3.0", - "symfony/asset": "^3.1 || ^4.0", - "symfony/browser-kit": "^3.1 || ^4.0", - "symfony/console": "^3.1 || ^4.0", - "symfony/css-selector": "^3.1 || ^4.0", - "symfony/phpunit-bridge": "^3.1 || ^4.0", - "symfony/process": "^3.1 || ^4.0", - "symfony/security-bundle": "^3.1 || ^4.0", - "symfony/templating": "^3.1 || ^4.0", - "symfony/twig-bundle": "^3.1 || ^4.0", - "symfony/web-profiler-bundle": "^3.1 || ^4.0", - "symfony/yaml": "^3.1 || ^4.0" + "symfony/asset": "^3.4 || ^4.0", + "symfony/browser-kit": "^3.4 || ^4.0", + "symfony/console": "^3.4 || ^4.0", + "symfony/css-selector": "^3.4 || ^4.0", + "symfony/phpunit-bridge": "^3.4 || ^4.0", + "symfony/process": "^3.4 || ^4.0", + "symfony/security-bundle": "^3.4 || ^4.0", + "symfony/templating": "^3.4 || ^4.0", + "symfony/twig-bundle": "^3.4 || ^4.0", + "symfony/web-profiler-bundle": "^3.4 || ^4.0", + "symfony/yaml": "^3.4 || ^4.0" }, "extra": { "branch-alias": { - "dev-master": "0.11-dev" + "dev-master": "0.12-dev" } } } From b25b5e25d817f809e8763fd5f1bccdd42234fc52 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Tue, 13 Mar 2018 08:08:34 +0100 Subject: [PATCH 02/36] Fix version requirements --- README.md | 8 +++++--- Resources/doc/index.md | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2181fe017..f951b0bf1 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,14 @@ It also supports: Browse your version documentation: -* [0.8 (DEPRECATED)](https://github.com/overblog/GraphQLBundle/blob/0.8/README.md) -* [0.9 (DEPRECATED)](https://github.com/overblog/GraphQLBundle/blob/0.9/README.md) -* [0.10 (DEPRECATED)](https://github.com/overblog/GraphQLBundle/blob/0.10/README.md) +* [0.8 (OBSOLETE)](https://github.com/overblog/GraphQLBundle/blob/0.8/README.md) +* [0.9 (OBSOLETE)](https://github.com/overblog/GraphQLBundle/blob/0.9/README.md) +* [0.10 (STABLE)](https://github.com/overblog/GraphQLBundle/blob/0.10/README.md) * [0.11 (STABLE)](https://github.com/overblog/GraphQLBundle/blob/0.11/README.md) * [0.12 (DEV)](https://github.com/overblog/GraphQLBundle/blob/master/README.md) +[Versions requirements](Resources/doc/index.md#versions-requirements) + Documentation ------------- diff --git a/Resources/doc/index.md b/Resources/doc/index.md index a37967eca..8a1bcab34 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -5,14 +5,16 @@ This Symfony bundle provides integration of [GraphQL](https://facebook.github.io and [GraphQL Relay](https://facebook.github.io/relay/docs/graphql-relay-specification.html). It also supports batching using libs like [ReactRelayNetworkLayer](https://github.com/nodkz/react-relay-network-layer) or [Apollo GraphQL](http://dev.apollodata.com/core/network.html#query-batching). -Requirements ------------- - -| Version | PHP | Symfony | -|------------------------------------------------------------:|------------:|------------------:| -| `>= 0.10` | `>= 5.6` | `>= 3.1` | -| [`0.9`](https://github.com/overblog/GraphQLBundle/tree/0.9) | `>= 5.5.9` | `>= 2.8, <= 3.1` | -| [`0.8`](https://github.com/overblog/GraphQLBundle/tree/0.8) | `>= 5.4 ` | `>= 2.7, <= 3.1` | +Versions requirements +---------------------- + +| Version | PHP | Symfony | Support | +|----------------------------------------------------------------:|------------:|------------------:|--------------------:| +| [`0.12`](https://github.com/overblog/GraphQLBundle/tree/master) | `>= 7.1` | `>= 3.1` | DEV | +| [`0.11`](https://github.com/overblog/GraphQLBundle/tree/0.11) | `>= 5.6` | `>= 3.1, <= 4.0` | Active support | +| [`0.10`](https://github.com/overblog/GraphQLBundle/tree/0.10) | `>= 5.5.9` | `>= 2.8, <= 3.1` | Active support | +| [`0.9`](https://github.com/overblog/GraphQLBundle/tree/0.9) | `>= 5.5.9` | `>= 2.8, <= 3.1` | End of life | +| [`0.8`](https://github.com/overblog/GraphQLBundle/tree/0.8) | `>= 5.4 ` | `>= 2.7, <= 3.1` | End of life | After installation ------------ From a2cacad12821e94edb5f812f93456eb3ce7c3111 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 22 Mar 2018 14:51:21 +0100 Subject: [PATCH 03/36] Add a NAME constant in generated type classes --- Generator/TypeGenerator.php | 5 +++++ Resources/skeleton/TypeSystem.php.skeleton | 1 + 2 files changed, 6 insertions(+) diff --git a/Generator/TypeGenerator.php b/Generator/TypeGenerator.php index b44ddd7a2..20e39fe1e 100644 --- a/Generator/TypeGenerator.php +++ b/Generator/TypeGenerator.php @@ -161,6 +161,11 @@ protected function generateParentClassName(array $config) } } + protected function generateTypeName(array $config) + { + return $this->varExport($config['config']['name']); + } + public function compile($mode) { $cacheDir = $this->getCacheDir(); diff --git a/Resources/skeleton/TypeSystem.php.skeleton b/Resources/skeleton/TypeSystem.php.skeleton index e6c0f6f0e..ba436aac9 100644 --- a/Resources/skeleton/TypeSystem.php.skeleton +++ b/Resources/skeleton/TypeSystem.php.skeleton @@ -4,6 +4,7 @@ class extends { +const NAME = ; public function __construct(ConfigProcessor $configProcessor, GlobalVariables $globalVariables = null) { From 6fec0049e4e74443c72e8909768da35555560302 Mon Sep 17 00:00:00 2001 From: Adrian Philipp Date: Mon, 9 Apr 2018 12:37:04 +0200 Subject: [PATCH 04/36] Dump schema: Adds option to export schema with descriptions --- Command/GraphQLDumpSchemaCommand.php | 9 +- .../Command/GraphDumpSchemaCommandTest.php | 14 + .../Command/fixtures/schema.descriptions.json | 1391 +++++++++++++++++ 3 files changed, 1413 insertions(+), 1 deletion(-) create mode 100644 Tests/Functional/Command/fixtures/schema.descriptions.json diff --git a/Command/GraphQLDumpSchemaCommand.php b/Command/GraphQLDumpSchemaCommand.php index 3372d0e9a..e84c63d33 100644 --- a/Command/GraphQLDumpSchemaCommand.php +++ b/Command/GraphQLDumpSchemaCommand.php @@ -60,6 +60,12 @@ protected function configure() InputOption::VALUE_NONE, 'Enabled classic json format: { "__schema": {...} }.' ) + ->addOption( + 'with-descriptions', + null, + InputOption::VALUE_NONE, + 'Dump schema including descriptions.' + ) ; } @@ -74,13 +80,14 @@ private function createFile(InputInterface $input) { $format = strtolower($input->getOption('format')); $schemaName = $input->getOption('schema'); + $includeDescription = $input->getOption('with-descriptions'); $file = $input->getOption('file') ?: $this->baseExportPath.sprintf('/../var/schema%s.%s', $schemaName ? '.'.$schemaName : '', $format); switch ($format) { case 'json': $request = [ - 'query' => Introspection::getIntrospectionQuery(false), + 'query' => Introspection::getIntrospectionQuery($includeDescription), 'variables' => [], 'operationName' => null, ]; diff --git a/Tests/Functional/Command/GraphDumpSchemaCommandTest.php b/Tests/Functional/Command/GraphDumpSchemaCommandTest.php index 0d597b09c..1bb3933b7 100644 --- a/Tests/Functional/Command/GraphDumpSchemaCommandTest.php +++ b/Tests/Functional/Command/GraphDumpSchemaCommandTest.php @@ -51,6 +51,20 @@ public function testDump($format, $withFormatOption = true) ); } + public function testDumpWithDescriptions() + { + $file = $this->cacheDir.'/schema.json'; + $this->assertCommandExecution( + [ + '--file' => $file, + '--with-descriptions' => true, + ], + __DIR__.'/fixtures/schema.descriptions.json', + $file, + 'json' + ); + } + public function testClassicJsonFormat() { $file = $this->cacheDir.'/schema.json'; diff --git a/Tests/Functional/Command/fixtures/schema.descriptions.json b/Tests/Functional/Command/fixtures/schema.descriptions.json new file mode 100644 index 000000000..63d265259 --- /dev/null +++ b/Tests/Functional/Command/fixtures/schema.descriptions.json @@ -0,0 +1,1391 @@ +{ + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [ + { + "name": "user", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "User", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "User", + "description": null, + "fields": [ + { + "name": "name", + "description": "the user name", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "friends", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "friendConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "friendsForward", + "description": null, + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "userConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "friendsBackward", + "description": null, + "args": [ + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "userConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric\nvalues. Int can represent values between -(2^31) and 2^31 - 1. ", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "friendConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "totalCount", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "friendEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PageInfo", + "description": "Information about pagination in a connection.", + "fields": [ + { + "name": "hasNextPage", + "description": "When paginating forwards, are there more items?", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hasPreviousPage", + "description": "When paginating backwards, are there more items?", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "startCursor", + "description": "When paginating backwards, the cursor to continue.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "endCursor", + "description": "When paginating forwards, the cursor to continue.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "friendEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "friendshipTime", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "node", + "description": "The item at the end of the edge.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "User", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "userConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "userEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "userEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "User", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to\nrefetch an object or as key for a cache. The ID type appears in a JSON\nresponse as a String; however, it is not intended to be human-readable.\nWhen expected as an input type, any string (such as `\"4\"`) or integer\n(such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Float", + "description": "The `Float` scalar type represents signed double-precision fractional\nvalues as specified by\n[IEEE 754](http:\/\/en.wikipedia.org\/wiki\/IEEE_floating_point). ", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onOperation", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onFragment", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onField", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + } + ], + "directives": [ + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "deprecated", + "description": "Marks an element of a GraphQL schema as no longer supported.", + "locations": [ + "FIELD_DEFINITION", + "ENUM_VALUE" + ], + "args": [ + { + "name": "reason", + "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https:\/\/daringfireball.net\/projects\/markdown\/).", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"No longer supported\"" + } + ] + } + ] + } +} From 50f6b06833af4aa298a595160da3cf1e339edd0d Mon Sep 17 00:00:00 2001 From: Maxime COLIN Date: Tue, 10 Apr 2018 15:08:09 +0200 Subject: [PATCH 05/36] Update custom exception mapping doc --- Resources/doc/error-handling/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/doc/error-handling/index.md b/Resources/doc/error-handling/index.md index a6159fbba..67ef8023e 100644 --- a/Resources/doc/error-handling/index.md +++ b/Resources/doc/error-handling/index.md @@ -95,7 +95,7 @@ define a custom exception mapping: ```yaml overblog_graphql: #... - definitions: + errors_handler: #... # change to true to try to map an exception to a parent exception if the exact exception is not in # the mapping From b18e0b75156bbc7de2e120e452b0f0fef89d1182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Tainon?= Date: Sun, 15 Apr 2018 15:01:59 +0200 Subject: [PATCH 06/36] Fix minor typo in documentation --- Resources/doc/definitions/resolver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/doc/definitions/resolver.md b/Resources/doc/definitions/resolver.md index d926ebde4..271337c66 100644 --- a/Resources/doc/definitions/resolver.md +++ b/Resources/doc/definitions/resolver.md @@ -135,7 +135,7 @@ Resolvers can be define 2 different ways **Note:** * When using service id as FQCN in yaml definition, backslashes must be correctly escaped, - here an example `'@=resolver("App\\GraphQL\\Resolver\\Greetings", [args['name']])'`. + here an example `'@=resolver("App\\GraphQL\\Resolver\\Greetings", [args["name"]])'`. * You can also see the more straight forward way using [resolver map](resolver-map.md) 2. **The service way** From 60eec0cb9a103fd4561a0c75e58c369175e4e955 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 17 Apr 2018 20:05:19 +0200 Subject: [PATCH 07/36] Enum type --- Resources/doc/definitions/type-system/enum.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Resources/doc/definitions/type-system/enum.md b/Resources/doc/definitions/type-system/enum.md index fc089a688..2225c889d 100644 --- a/Resources/doc/definitions/type-system/enum.md +++ b/Resources/doc/definitions/type-system/enum.md @@ -20,7 +20,6 @@ Episode: value: 5 description: "Released in 1980." JEDI: 6 # using the short syntax (JEDI value equal to 6) -# in this case FORCEAWAKENS value = FORCEAWAKENS -# FORCEAWAKENS: -# description: "Released in 2015." + FORCEAWAKENS: # in this case FORCEAWAKENS value = FORCEAWAKENS + description: "Released in 2015." ``` From 9e8fdb9af681431cd3518df7d63a884d6837f4f4 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 17 Apr 2018 20:05:33 +0200 Subject: [PATCH 08/36] Update enum.md --- Resources/doc/definitions/type-system/enum.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/doc/definitions/type-system/enum.md b/Resources/doc/definitions/type-system/enum.md index 2225c889d..ae8f1986d 100644 --- a/Resources/doc/definitions/type-system/enum.md +++ b/Resources/doc/definitions/type-system/enum.md @@ -5,7 +5,7 @@ Enum # MyBundle/Resources/config/graphql/Episode.types.yml # The original trilogy consists of three movies. # This implements the following type system shorthand: -# enum Episode { NEWHOPE, EMPIRE, JEDI } +# enum Episode { NEWHOPE, EMPIRE, JEDI, FORCEAWAKENS } Episode: type: enum config: From 2d8393a4aabfaed4cefc7d8fc9f9b4ccfc40b549 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 19 Apr 2018 19:06:17 +0200 Subject: [PATCH 09/36] resolver.md --- Resources/doc/definitions/resolver.md | 380 ++++++++++++++------------ 1 file changed, 204 insertions(+), 176 deletions(-) diff --git a/Resources/doc/definitions/resolver.md b/Resources/doc/definitions/resolver.md index 271337c66..2818d1a2c 100644 --- a/Resources/doc/definitions/resolver.md +++ b/Resources/doc/definitions/resolver.md @@ -1,3 +1,5 @@ + + # Resolver To ease development we named 2 types of resolver: @@ -7,181 +9,207 @@ To ease development we named 2 types of resolver: This is just a recommendation. -Resolvers can be define 2 different ways - -1. **The PHP way** - - You can declare a resolver (any class that implements `Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface` - or `Overblog\GraphQLBundle\Definition\Resolver\MutationInterface`) - in `src/*Bundle/GraphQL` or `app/GraphQL` and they will be auto discovered. - Auto map classes method are accessible by: - * double-colon (::) to separate service id (class name) and the method names - (example: `AppBunble\GraphQL\CustomResolver::myMethod`) - * for callable classes you can use the service id (example: `AppBunble\GraphQL\InvokeResolver` for a resolver implementing the `__invoke` method) - you can also alias a type by implementing `Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface` - which returns a map of method/alias. The service created will autowire the `__construct` - and `Symfony\Component\DependencyInjection\ContainerAwareInterface::setContainer` methods. - - Here an example: - ```php - 'say_hello']; - } - } - ``` - - `SayHello` resolver can be access by using `App\GraphQL\Resolver\Greetings::sayHello` or - `say_hello` alias. - - we can also use class invoker: - ```php - value += $number; - } - - /** - * {@inheritdoc} - */ - public static function getAliases() - { - return ['addition' => 'add']; - } - } - ``` - `addition` mutation can be access by using `App\GraphQL\Mutation\CalcMutation::addition` or - `add` alias. - - You can also define custom dirs using the config (Symfony <3.3): - ```yaml - overblog_graphql: - definitions: - auto_mapping: - directories: - - "%kernel.root_dir%/src/*Bundle/CustomDir" - - "%kernel.root_dir%/src/AppBundle/{foo,bar}" - ``` - - If using Symfony 3.3+ disabling auto mapping can be a solution to leave place to native - DI `autoconfigure`: - - ```yaml - overblog_graphql: - definitions: - auto_mapping: false - ``` - - Here an example of how this can be done with DI `autoconfigure`: - - ```yaml - services: - App\Mutation\: - resource: '../src/Mutation' - tags: ['overblog_graphql.mutation'] - - App\Resolver\: - resource: '../src/Resolver' - tags: ['overblog_graphql.resolver'] - ``` - - **Note:** - * When using service id as FQCN in yaml definition, backslashes must be correctly escaped, - here an example `'@=resolver("App\\GraphQL\\Resolver\\Greetings", [args["name"]])'`. - * You can also see the more straight forward way using [resolver map](resolver-map.md) - -2. **The service way** - - Creating a service tagged `overblog_graphql.resolver` for resolvers - or `overblog_graphql.mutation` for mutations. - - Using the php way examples: - - ```yaml - services: - App\GraphQL\Resolver\Greetings: - # only for sf < 3.3 - #class: App\GraphQL\Resolver\Greetings - tags: - - { name: overblog_graphql.resolver, method: sayHello, alias: say_hello } # add alias say_hello - - { name: overblog_graphql.resolver, method: sayHello } # add service id "App\GraphQL\Resolver\Greetings" - ``` - - `SayHello` resolver can be access by using `App\GraphQL\Resolver\Greetings::sayHello` or - `say_hello` alias. - - for invokable classes no need to use `alias` and `method` attributes: - - ```yaml - services: - App\GraphQL\Resolver\Greetings: - # only for sf < 3.3 - #class: App\GraphQL\Resolver\Greetings - tags: - - { name: overblog_graphql.resolver } - ``` - - This way resolver can be accessed with service id `App\GraphQL\Resolver\Greetings`. - - for mutation: - - ```yaml - services: - App\GraphQL\Mutation\CalcMutation: - # only for sf < 3.3 - #class: App\GraphQL\Mutation\CalcMutation - tags: - - { name: overblog_graphql.mutation, method: addition, alias: add } - ``` - `addition` mutation can be access by using `App\GraphQL\Mutation\CalcMutation::addition` or - `add` alias. +Resolvers can be define 2 different ways: + +## The PHP way + + +You can declare a resolver (any class that implements `Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface` or `Overblog\GraphQLBundle\Definition\Resolver\MutationInterface`) in `src/*Bundle/GraphQL` or `app/GraphQL` and they will be auto discovered. +Auto map classes method are accessible by: +* double-colon (::) to separate service id (class name) and the method names +(example: `AppBunble\GraphQL\CustomResolver::myMethod`) +* for callable classes you can use the service id (example: `AppBunble\GraphQL\InvokeResolver` for a resolver implementing the `__invoke` method) you can also alias a type by implementing `Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface` which returns a map of method/alias. The service created will autowire the `__construct` and `Symfony\Component\DependencyInjection\ContainerAwareInterface::setContainer` methods. + +**Note:** +* When using service id as FQCN in yaml definition, backslashes must be correctly escaped, here an example: +`'@=resolver("App\\GraphQL\\Resolver\\Greetings", [args["name"]])'`. +* You can also see the more straight forward way using [resolver map](resolver-map.md). + +### Resolver + +Example using an alias: +````yaml +resolve: '@=resolver("say_hello", [args["name"]])' +```` + +```php + 'say_hello']; + } +} +```` + +Example using a fully qualified method name: +````yaml +resolve: '@=resolver("App\\GraphQL\\Resolver\\Greetings::sayHello", [args["name"]])' +```` + +Note: backslashes must be correctly escaped and respect the use of single and double quotes. + +```php +value += $number; + } + + /** + * {@inheritdoc} + */ + public static function getAliases() + { + return ['addition' => 'add']; + } +} +``` +`addition` mutation can be access by using `App\GraphQL\Mutation\CalcMutation::addition` or +`add` alias. + +You can also define custom dirs using the config (Symfony <3.3): +```yaml +overblog_graphql: +definitions: + auto_mapping: + directories: + - "%kernel.root_dir%/src/*Bundle/CustomDir" + - "%kernel.root_dir%/src/AppBundle/{foo,bar}" +``` + +If using Symfony 3.3+ disabling auto mapping can be a solution to leave place to native +DI `autoconfigure`: + +```yaml +overblog_graphql: +definitions: + auto_mapping: false +``` + +Here an example of how this can be done with DI `autoconfigure`: + +```yaml +services: +App\Mutation\: + resource: '../src/Mutation' + tags: ['overblog_graphql.mutation'] + +App\Resolver\: + resource: '../src/Resolver' + tags: ['overblog_graphql.resolver'] +``` + +## The service way + +Creating a service tagged `overblog_graphql.resolver` for resolvers +or `overblog_graphql.mutation` for mutations. + +Using the php way examples: + +```yaml +services: +App\GraphQL\Resolver\Greetings: + # only for sf < 3.3 + #class: App\GraphQL\Resolver\Greetings + tags: + - { name: overblog_graphql.resolver, method: sayHello, alias: say_hello } # add alias say_hello + - { name: overblog_graphql.resolver, method: sayHello } # add service id "App\GraphQL\Resolver\Greetings" +``` + +`SayHello` resolver can be access by using `App\GraphQL\Resolver\Greetings::sayHello` or +`say_hello` alias. + +for invokable classes no need to use `alias` and `method` attributes: + +```yaml +services: +App\GraphQL\Resolver\Greetings: + # only for sf < 3.3 + #class: App\GraphQL\Resolver\Greetings + tags: + - { name: overblog_graphql.resolver } +``` + +This way resolver can be accessed with service id `App\GraphQL\Resolver\Greetings`. + +for mutation: + +```yaml +services: +App\GraphQL\Mutation\CalcMutation: + # only for sf < 3.3 + #class: App\GraphQL\Mutation\CalcMutation + tags: + - { name: overblog_graphql.mutation, method: addition, alias: add } +``` +`addition` mutation can be access by using `App\GraphQL\Mutation\CalcMutation::addition` or +`add` alias. Next step [solving N+1 problem](solving-n-plus-1-problem.md) From c977abd5115c7ad4471a2567cae4458861df139b Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 19 Apr 2018 19:06:42 +0200 Subject: [PATCH 10/36] resolver.md --- Resources/doc/definitions/resolver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/doc/definitions/resolver.md b/Resources/doc/definitions/resolver.md index 2818d1a2c..289256955 100644 --- a/Resources/doc/definitions/resolver.md +++ b/Resources/doc/definitions/resolver.md @@ -44,7 +44,7 @@ class Greetings implements ResolverInterface, AliasedInterface { public function sayHello($name) { - return sprintf('hello %s!!!', $name); + return sprintf('hello %s!!!', $name); } /** From b1e3f819670a0988a671b0c93efb42f95657b43f Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 19 Apr 2018 19:09:13 +0200 Subject: [PATCH 11/36] resolver.md --- Resources/doc/definitions/resolver.md | 44 +++++++++++++-------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Resources/doc/definitions/resolver.md b/Resources/doc/definitions/resolver.md index 289256955..95d61e4d7 100644 --- a/Resources/doc/definitions/resolver.md +++ b/Resources/doc/definitions/resolver.md @@ -138,10 +138,10 @@ You can also define custom dirs using the config (Symfony <3.3): ```yaml overblog_graphql: definitions: - auto_mapping: - directories: - - "%kernel.root_dir%/src/*Bundle/CustomDir" - - "%kernel.root_dir%/src/AppBundle/{foo,bar}" + auto_mapping: + directories: + - "%kernel.root_dir%/src/*Bundle/CustomDir" + - "%kernel.root_dir%/src/AppBundle/{foo,bar}" ``` If using Symfony 3.3+ disabling auto mapping can be a solution to leave place to native @@ -150,7 +150,7 @@ DI `autoconfigure`: ```yaml overblog_graphql: definitions: - auto_mapping: false + auto_mapping: false ``` Here an example of how this can be done with DI `autoconfigure`: @@ -158,12 +158,12 @@ Here an example of how this can be done with DI `autoconfigure`: ```yaml services: App\Mutation\: - resource: '../src/Mutation' - tags: ['overblog_graphql.mutation'] + resource: '../src/Mutation' + tags: ['overblog_graphql.mutation'] App\Resolver\: - resource: '../src/Resolver' - tags: ['overblog_graphql.resolver'] + resource: '../src/Resolver' + tags: ['overblog_graphql.resolver'] ``` ## The service way @@ -176,11 +176,11 @@ Using the php way examples: ```yaml services: App\GraphQL\Resolver\Greetings: - # only for sf < 3.3 - #class: App\GraphQL\Resolver\Greetings - tags: - - { name: overblog_graphql.resolver, method: sayHello, alias: say_hello } # add alias say_hello - - { name: overblog_graphql.resolver, method: sayHello } # add service id "App\GraphQL\Resolver\Greetings" + # only for sf < 3.3 + #class: App\GraphQL\Resolver\Greetings + tags: + - { name: overblog_graphql.resolver, method: sayHello, alias: say_hello } # add alias say_hello + - { name: overblog_graphql.resolver, method: sayHello } # add service id "App\GraphQL\Resolver\Greetings" ``` `SayHello` resolver can be access by using `App\GraphQL\Resolver\Greetings::sayHello` or @@ -191,10 +191,10 @@ for invokable classes no need to use `alias` and `method` attributes: ```yaml services: App\GraphQL\Resolver\Greetings: - # only for sf < 3.3 - #class: App\GraphQL\Resolver\Greetings - tags: - - { name: overblog_graphql.resolver } + # only for sf < 3.3 + #class: App\GraphQL\Resolver\Greetings + tags: + - { name: overblog_graphql.resolver } ``` This way resolver can be accessed with service id `App\GraphQL\Resolver\Greetings`. @@ -204,10 +204,10 @@ for mutation: ```yaml services: App\GraphQL\Mutation\CalcMutation: - # only for sf < 3.3 - #class: App\GraphQL\Mutation\CalcMutation - tags: - - { name: overblog_graphql.mutation, method: addition, alias: add } + # only for sf < 3.3 + #class: App\GraphQL\Mutation\CalcMutation + tags: + - { name: overblog_graphql.mutation, method: addition, alias: add } ``` `addition` mutation can be access by using `App\GraphQL\Mutation\CalcMutation::addition` or `add` alias. From cf4697c38e2cf4241c95c841b13e262c909a24e8 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 19 Apr 2018 19:22:44 +0200 Subject: [PATCH 12/36] relay-paginator.md --- Resources/doc/helpers/relay-paginator.md | 49 ++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Resources/doc/helpers/relay-paginator.md b/Resources/doc/helpers/relay-paginator.md index 8e606faa4..c1551ad36 100644 --- a/Resources/doc/helpers/relay-paginator.md +++ b/Resources/doc/helpers/relay-paginator.md @@ -174,6 +174,11 @@ class DataBackend { return count($array); } + + public function countAll() + { + return count($this->data); + } } $backend = new DataBackend(); @@ -195,6 +200,50 @@ $result = $paginator->backward( You should get the 4 last items of the _data set_. +#### Within a resolver + +````yaml +resolve: '@=resolver("App\\GraphQL\\Resolver\\Greetings::sayHello", [args])' +```` + +```` +sayHello(first: 1, after: "YXJyYXljb25uZWN0aW9uOjI="){ # after: base64_encode('arrayconnection:2') + edges { + cursor # YXJyYXljb25uZWN0aW9uOjM= + node # D + } + pageInfo { + hasNextPage # true + } +} +```` + +````php +getData($offset, $limit); + }); + + return $paginator->auto($args, function() use ($backend) { + return $backend->countAll(); + }); + } +} +```` + #### Promise handling Paginator also supports promises if you [use that feature](https://github.com/webonyx/graphql-php/pull/67) From 9b48a77080c0d3a4296cc8a091b1e006c29959fe Mon Sep 17 00:00:00 2001 From: Yuri Mironenko Date: Fri, 20 Apr 2018 14:01:40 +0300 Subject: [PATCH 13/36] Change graphql format --- Resources/doc/definitions/type-system/input-object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/doc/definitions/type-system/input-object.md b/Resources/doc/definitions/type-system/input-object.md index 10280b627..edba9d5b6 100644 --- a/Resources/doc/definitions/type-system/input-object.md +++ b/Resources/doc/definitions/type-system/input-object.md @@ -5,7 +5,7 @@ Input object # src/MyBundle/Resources/config/graphql/HumanAndDroid.types.yml # # This implements the following type system shorthand: -# type HeroInput { +# input HeroInput { # name: Episode! # } HeroInput: From 47f54b1daf55a24c61af5e2a5fee51981c1f9289 Mon Sep 17 00:00:00 2001 From: Michal Zdrojewski Date: Wed, 25 Apr 2018 14:39:01 +0100 Subject: [PATCH 14/36] Fixed typos and changed the wording slightly. --- Resources/doc/definitions/expression-language.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/doc/definitions/expression-language.md b/Resources/doc/definitions/expression-language.md index f1c918779..4e852a310 100644 --- a/Resources/doc/definitions/expression-language.md +++ b/Resources/doc/definitions/expression-language.md @@ -1,7 +1,7 @@ Expression language =================== -All definitions configs entries can use expression language but it must be explicitly triggered using "@=" like prefix. +All definition config entries can use expression language but it must be explicitly triggered using "@=" like prefix. **Functions description:** @@ -43,11 +43,11 @@ Expression | Description | Scope Custom expression function -------------------------- -Custom expression function is easy as creating a tagged service. -Adding useful expression function can help user create simple resolver without having to leave config file, -this also improve performance by removing a useless external resolver call. +Adding custom expression function is easy since all you need to do is create a tagged service. +Expression functions can help user create simple resolver without having to leave config file, +this also improves performance by removing a useless external resolver call. -Here an example to add an custom expression equivalent to php `json_decode`: +Here is an example to add a custom expression equivalent to php `json_decode`: ```php Date: Wed, 9 May 2018 13:21:23 +0200 Subject: [PATCH 15/36] User Deprecated: Referencing controllers with a single colon is deprecated since Symfony 4.1 --- Resources/config/routing/graphql.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/config/routing/graphql.yml b/Resources/config/routing/graphql.yml index daeda1615..a0b1f1ce1 100644 --- a/Resources/config/routing/graphql.yml +++ b/Resources/config/routing/graphql.yml @@ -1,23 +1,23 @@ overblog_graphql_endpoint: path: / defaults: - _controller: overblog_graphql.controller.graphql:endpointAction + _controller: overblog_graphql.controller.graphql::endpointAction _format: "json" overblog_graphql_batch_endpoint: path: /batch defaults: - _controller: overblog_graphql.controller.graphql:batchEndpointAction + _controller: overblog_graphql.controller.graphql::batchEndpointAction _format: "json" overblog_graphql_multiple_endpoint: path: /graphql/{schemaName} defaults: - _controller: overblog_graphql.controller.graphql:endpointAction + _controller: overblog_graphql.controller.graphql::endpointAction _format: "json" overblog_graphql_batch_multiple_endpoint: path: /graphql/{schemaName}/batch defaults: - _controller: overblog_graphql.controller.graphql:batchEndpointAction + _controller: overblog_graphql.controller.graphql::batchEndpointAction _format: "json" From 0972d9a373574527611fee990d8711be4790d3b3 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Fri, 11 May 2018 07:17:17 +0200 Subject: [PATCH 16/36] Improve documentation (#327) Object access control --- README.md | 1 + .../doc/security/fields-access-control.md | 12 ++++++- Resources/doc/security/index.md | 1 + .../doc/security/object-access-control.md | 33 +++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 Resources/doc/security/object-access-control.md diff --git a/README.md b/README.md index f951b0bf1..b861cf6ff 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Documentation - [Promise](Resources/doc/data-fetching/promise.md) - [Security](Resources/doc/security/index.md) - [Handle CORS](Resources/doc/security/handle-cors.md) + - [Object access control](Resources/doc/security/object-access-control.md) - [Fields access control](Resources/doc/security/fields-access-control.md) - [Fields public control](Resources/doc/security/fields-public-control.md) - [Limiting query depth](Resources/doc/security/limiting-query-depth.md) diff --git a/Resources/doc/security/fields-access-control.md b/Resources/doc/security/fields-access-control.md index 523234dc5..7949edbfb 100644 --- a/Resources/doc/security/fields-access-control.md +++ b/Resources/doc/security/fields-access-control.md @@ -1,10 +1,14 @@ Fields access Control ====================== -An access control can be add on each field using `config.fields.*.access` or globally with `config.fieldsDefaultAccess`. +An access control can be added on each field using `config.fields.*.access` or globally with `config.fieldsDefaultAccess`. If `config.fields.*.access` value is true field will be normally resolved but will be `null` otherwise. Act like access is`true` if not set. +Note: +- in query mode: execute resolver -> execute access -> manage result in function of access +- in mutation mode: execute access -> execute resolver if access result is true + In the example below the Human name is available only for authenticated users. ```yaml @@ -32,3 +36,9 @@ Human: description: "The home planet of the human, or null if unknown." interfaces: [Character] ``` + +Performance +----------- +Checking access on each field can be a performance issue and may be dealt with using: +- using a custom cache to do the check only once +- using [Object access control](object-access-control.md) diff --git a/Resources/doc/security/index.md b/Resources/doc/security/index.md index be362d504..b49af5486 100644 --- a/Resources/doc/security/index.md +++ b/Resources/doc/security/index.md @@ -2,6 +2,7 @@ Security ======== * [Handle CORS](handle-cors.md) +* [Object access control](object-access-control.md) * [Fields access control](fields-access-control.md) * [Fields public control](fields-public-control.md) * [Limiting query depth](limiting-query-depth.md) diff --git a/Resources/doc/security/object-access-control.md b/Resources/doc/security/object-access-control.md new file mode 100644 index 000000000..fc670a588 --- /dev/null +++ b/Resources/doc/security/object-access-control.md @@ -0,0 +1,33 @@ +Object access Control +====================== + +If your GraphQL schema have multiple paths to the same resolver, you may end up with duplicated access control on the different fields leading to this resolver. + +An access control can be added on the whole object using a decorator type for this protected field and make every parent extend this type. + + +An access control can be added on each field using `config.fields.*.access` or globally with `config.fieldsDefaultAccess`. +If `config.fields.*.access` value is true field will be normally resolved but will be `null` otherwise. +Act like access is`true` if not set. + +In the example below the user field protection is set by the decorator: + +```yaml +ProtectedUser: + type: object + decorator: true + config: + fields: + user: {type: User, access: '@=isAuthenticated()'} + +Foo: + type: object + inherits: [ProtectedUser] + config: + fields: + other: {type: String!} + +Bar: + type: object + inherits: [ProtectedUser] +``` From 843a9d7b108c30a803c75d20e3906035533a213d Mon Sep 17 00:00:00 2001 From: Xavier HAUSHERR Date: Mon, 14 May 2018 11:04:52 +0200 Subject: [PATCH 17/36] Update graphql.yml --- Resources/config/routing/graphql.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/config/routing/graphql.yml b/Resources/config/routing/graphql.yml index a0b1f1ce1..7a88f5a5f 100644 --- a/Resources/config/routing/graphql.yml +++ b/Resources/config/routing/graphql.yml @@ -1,23 +1,23 @@ overblog_graphql_endpoint: path: / defaults: - _controller: overblog_graphql.controller.graphql::endpointAction + _controller: Overblog\GraphQLBundle\Controller\GraphController::endpointAction _format: "json" overblog_graphql_batch_endpoint: path: /batch defaults: - _controller: overblog_graphql.controller.graphql::batchEndpointAction + _controller: Overblog\GraphQLBundle\Controller\GraphController::batchEndpointAction _format: "json" overblog_graphql_multiple_endpoint: path: /graphql/{schemaName} defaults: - _controller: overblog_graphql.controller.graphql::endpointAction + _controller: Overblog\GraphQLBundle\Controller\GraphController::endpointAction _format: "json" overblog_graphql_batch_multiple_endpoint: path: /graphql/{schemaName}/batch defaults: - _controller: overblog_graphql.controller.graphql::batchEndpointAction + _controller: Overblog\GraphQLBundle\Controller\GraphController::batchEndpointAction _format: "json" From ede385dadd35f5818bc5b06171b103d9bf82ea4c Mon Sep 17 00:00:00 2001 From: Xavier HAUSHERR Date: Mon, 14 May 2018 11:27:11 +0200 Subject: [PATCH 18/36] Update services.yml --- Resources/config/services.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/config/services.yml b/Resources/config/services.yml index e8e1d307f..fc921aa6d 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -119,6 +119,10 @@ services: - "@overblog_graphql.request_parser" - "%overblog_graphql.handle_cors%" - "%overblog_graphql.batching_method%" + + Overblog\GraphQLBundle\Controller\GraphController: + public: true + alias: 'overblog_graphql.controller.graphql' overblog_graphql.command.dump_schema: class: Overblog\GraphQLBundle\Command\GraphQLDumpSchemaCommand From 2a74fd6dc1e790bb92e2df45af344d8b423f76ee Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Tue, 22 May 2018 07:49:50 +0200 Subject: [PATCH 19/36] Add a slide add the slide GraphQL in Symfony *by Bernd Alter* --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b861cf6ff..dc0cf1eac 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ Documentation Talks and slides to help you start ---------------------------------- +* GraphQL in Symfony *by Bernd Alter* - [Twitter](https://twitter.com/bazoo0815) + - [Talk about GraphQL and its implementation with Symfony (26.04.2017)](https://www.slideshare.net/berndalter7/graphql-in-symfony) `English` * GraphQL is right in front of us, let's to it! *by Renato Mendes Figueiredo* - [Twitter](https://twitter.com/renatomefi), [GitHub](https://github.com/renatomefi) - [Slides at http://talks.mefi.in/graphql-scotphp17](http://talks.mefi.in/graphql-scotphp17/) `English` - [Video at SymfonyCamp UA 2017](https://www.youtube.com/watch?v=jyoYlnCPNgk) `English` From ed16cb63ad4daf4c83073e29230eaa57d79df3ad Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 23 May 2018 09:44:39 +0200 Subject: [PATCH 20/36] Example with a PHP constant --- Resources/doc/definitions/type-system/enum.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Resources/doc/definitions/type-system/enum.md b/Resources/doc/definitions/type-system/enum.md index ae8f1986d..66d1d3172 100644 --- a/Resources/doc/definitions/type-system/enum.md +++ b/Resources/doc/definitions/type-system/enum.md @@ -17,7 +17,8 @@ Episode: # to deprecate a value, only set the deprecation reason #deprecationReason: "Just because" EMPIRE: - value: 5 + # We can use a PHP constant to avoid a magic number + value: '@=constant("App\\StarWars\\Movies::MOVIE_EMPIRE")' description: "Released in 1980." JEDI: 6 # using the short syntax (JEDI value equal to 6) FORCEAWAKENS: # in this case FORCEAWAKENS value = FORCEAWAKENS From 03b41ee967f738e10de9ed4481f7d7fc68fedf19 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 23 May 2018 09:44:39 +0200 Subject: [PATCH 21/36] Example with a PHP constant From 9f15ec7387c463b4bbbaef8c702b542d660c83c0 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Mon, 28 May 2018 13:09:57 +0200 Subject: [PATCH 22/36] Fix travis build --- .travis.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 88b4102cb..d02172fe0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,15 +14,16 @@ branches: matrix: fast_finish: true include: - - php: 5.6 - env: COMPOSER_UPDATE_FLAGS=--prefer-lowest SYMFONY_DEPRECATIONS_HELPER=disabled - - php: 5.6 - env: SYMFONY_VERSION=3.1.* SYMFONY_DEPRECATIONS_HELPER=disabled - - php: 7.0 - env: SYMFONY_VERSION=3.2.* PHPUNIT_VERSION=^5.7.26 - - php: 7.1 - php: 7.2 - env: SYMFONY_VERSION=4.1.* STABILITY=beta + env: PHP_CS_FIXER=true + - php: 7.2 + env: SYMFONY_VERSION=3.4.* + - php: 7.2 + env: SYMFONY_VERSION=4.0.* TEST_COVERAGE=true + - php: 7.2 + env: SYMFONY_VERSION=4.1.* DEPENDENCIES=beta + - php: nightly + env: COMPOSER_UPDATE_FLAGS=--ignore-platform-reqs allow_failures: - php: nightly env: COMPOSER_UPDATE_FLAGS=--ignore-platform-reqs From 1ed56daa7b5aefc916d29d5bfbf43b0606907997 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Mon, 28 May 2018 13:13:55 +0200 Subject: [PATCH 23/36] Fix composer minimum stability --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d02172fe0..b322bd236 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ matrix: - php: 7.2 env: SYMFONY_VERSION=4.0.* TEST_COVERAGE=true - php: 7.2 - env: SYMFONY_VERSION=4.1.* DEPENDENCIES=beta + env: SYMFONY_VERSION=4.1.* STABILITY=beta - php: nightly env: COMPOSER_UPDATE_FLAGS=--ignore-platform-reqs allow_failures: From 3ce5878402257adb7e22cc057cd827935aac8f51 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Tue, 29 May 2018 07:49:51 +0200 Subject: [PATCH 24/36] Fix examples indentation --- Resources/doc/definitions/resolver.md | 130 +++++++++++++------------- 1 file changed, 64 insertions(+), 66 deletions(-) diff --git a/Resources/doc/definitions/resolver.md b/Resources/doc/definitions/resolver.md index 95d61e4d7..98b2e4680 100644 --- a/Resources/doc/definitions/resolver.md +++ b/Resources/doc/definitions/resolver.md @@ -1,5 +1,3 @@ - - # Resolver To ease development we named 2 types of resolver: @@ -42,18 +40,18 @@ use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface; class Greetings implements ResolverInterface, AliasedInterface { - public function sayHello($name) - { - return sprintf('hello %s!!!', $name); - } - - /** - * {@inheritdoc} - */ - public static function getAliases() - { - return ['sayHello' => 'say_hello']; - } + public function sayHello($name) + { + return sprintf('hello %s!!!', $name); + } + + /** + * {@inheritdoc} + */ + public static function getAliases() + { + return ['sayHello' => 'say_hello']; + } } ```` @@ -73,10 +71,10 @@ use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface; class Greetings implements ResolverInterface { - public function sayHello($name) - { - return sprintf('hello %s!!!', $name); - } + public function sayHello($name) + { + return sprintf('hello %s!!!', $name); + } } ``` @@ -94,10 +92,10 @@ use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface; class Greetings implements ResolverInterface { - public function __invoke($name) - { - return sprintf('hello %s!!!', $name); - } + public function __invoke($name) + { + return sprintf('hello %s!!!', $name); + } } ``` @@ -115,20 +113,20 @@ use Overblog\GraphQLBundle\Definition\Resolver\MutationInterface; class CalcMutation implements MutationInterface, AliasedInterface { - private $value; - - public function addition($number) - { - $this->value += $number; - } - - /** - * {@inheritdoc} - */ - public static function getAliases() - { - return ['addition' => 'add']; - } + private $value; + + public function addition($number) + { + $this->value += $number; + } + + /** + * {@inheritdoc} + */ + public static function getAliases() + { + return ['addition' => 'add']; + } } ``` `addition` mutation can be access by using `App\GraphQL\Mutation\CalcMutation::addition` or @@ -137,11 +135,11 @@ class CalcMutation implements MutationInterface, AliasedInterface You can also define custom dirs using the config (Symfony <3.3): ```yaml overblog_graphql: -definitions: - auto_mapping: - directories: - - "%kernel.root_dir%/src/*Bundle/CustomDir" - - "%kernel.root_dir%/src/AppBundle/{foo,bar}" + definitions: + auto_mapping: + directories: + - "%kernel.root_dir%/src/*Bundle/CustomDir" + - "%kernel.root_dir%/src/AppBundle/{foo,bar}" ``` If using Symfony 3.3+ disabling auto mapping can be a solution to leave place to native @@ -149,21 +147,21 @@ DI `autoconfigure`: ```yaml overblog_graphql: -definitions: - auto_mapping: false + definitions: + auto_mapping: false ``` Here an example of how this can be done with DI `autoconfigure`: ```yaml services: -App\Mutation\: - resource: '../src/Mutation' - tags: ['overblog_graphql.mutation'] - -App\Resolver\: - resource: '../src/Resolver' - tags: ['overblog_graphql.resolver'] + App\Mutation\: + resource: '../src/Mutation' + tags: ['overblog_graphql.mutation'] + + App\Resolver\: + resource: '../src/Resolver' + tags: ['overblog_graphql.resolver'] ``` ## The service way @@ -175,12 +173,12 @@ Using the php way examples: ```yaml services: -App\GraphQL\Resolver\Greetings: - # only for sf < 3.3 - #class: App\GraphQL\Resolver\Greetings - tags: - - { name: overblog_graphql.resolver, method: sayHello, alias: say_hello } # add alias say_hello - - { name: overblog_graphql.resolver, method: sayHello } # add service id "App\GraphQL\Resolver\Greetings" + App\GraphQL\Resolver\Greetings: + # only for sf < 3.3 + #class: App\GraphQL\Resolver\Greetings + tags: + - { name: overblog_graphql.resolver, method: sayHello, alias: say_hello } # add alias say_hello + - { name: overblog_graphql.resolver, method: sayHello } # add service id "App\GraphQL\Resolver\Greetings" ``` `SayHello` resolver can be access by using `App\GraphQL\Resolver\Greetings::sayHello` or @@ -190,11 +188,11 @@ for invokable classes no need to use `alias` and `method` attributes: ```yaml services: -App\GraphQL\Resolver\Greetings: - # only for sf < 3.3 - #class: App\GraphQL\Resolver\Greetings - tags: - - { name: overblog_graphql.resolver } + App\GraphQL\Resolver\Greetings: + # only for sf < 3.3 + #class: App\GraphQL\Resolver\Greetings + tags: + - { name: overblog_graphql.resolver } ``` This way resolver can be accessed with service id `App\GraphQL\Resolver\Greetings`. @@ -203,11 +201,11 @@ for mutation: ```yaml services: -App\GraphQL\Mutation\CalcMutation: - # only for sf < 3.3 - #class: App\GraphQL\Mutation\CalcMutation - tags: - - { name: overblog_graphql.mutation, method: addition, alias: add } + App\GraphQL\Mutation\CalcMutation: + # only for sf < 3.3 + #class: App\GraphQL\Mutation\CalcMutation + tags: + - { name: overblog_graphql.mutation, method: addition, alias: add } ``` `addition` mutation can be access by using `App\GraphQL\Mutation\CalcMutation::addition` or `add` alias. From 27298264285f28f3d6a1ffe9f8698ea08dc3c298 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 31 May 2018 08:51:27 +0200 Subject: [PATCH 25/36] Update InheritanceProcessor.php --- Config/Processor/InheritanceProcessor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Config/Processor/InheritanceProcessor.php b/Config/Processor/InheritanceProcessor.php index dc548f57c..008fbe9d7 100644 --- a/Config/Processor/InheritanceProcessor.php +++ b/Config/Processor/InheritanceProcessor.php @@ -121,7 +121,7 @@ private static function checkTypeExists($name, array $configs, $child) { if (!isset($configs[$name])) { throw new \InvalidArgumentException(sprintf( - 'Type %s inherits by %s not found.', + 'Type %s inherited by %s not found.', json_encode($name), json_encode($child) )); @@ -142,9 +142,9 @@ private static function checkAllowedInheritsTypes($name, array $config, array $a { if (empty($config['decorator']) && isset($config['type']) && !in_array($config['type'], $allowedTypes)) { throw new \InvalidArgumentException(sprintf( - 'Type %s can\'t inherits %s because %s is not allowed type (%s).', - json_encode($name), + 'Type %s can\'t inherit %s because its type (%s) is not allowed type (%s).', json_encode($child), + json_encode($name), json_encode($config['type']), json_encode($allowedTypes) )); From 315137e8ff08a1d84f20d79c89dbd22830160b61 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 31 May 2018 08:54:01 +0200 Subject: [PATCH 26/36] Update InheritanceProcessorTest.php --- Tests/Config/Processor/InheritanceProcessorTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Config/Processor/InheritanceProcessorTest.php b/Tests/Config/Processor/InheritanceProcessorTest.php index 3854dcaa6..c4f6b291e 100644 --- a/Tests/Config/Processor/InheritanceProcessorTest.php +++ b/Tests/Config/Processor/InheritanceProcessorTest.php @@ -17,7 +17,7 @@ class InheritanceProcessorTest extends TestCase /** * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Type "toto" inherits by "bar" not found. + * @expectedExceptionMessage Type "toto" inherited by "bar" not found. */ public function testExtendsUnknownType() { @@ -53,7 +53,7 @@ public function testCircularExtendsType() /** * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Type "toto" can't inherits "bar" because "enum" is not allowed type (["object","interface"]). + * @expectedExceptionMessage Type "bar" can't inherit "toto" because its type ("enum") is not allowed type (["object","interface"]). */ public function testNotAllowedType() { From 3102ea7a010274d2f48313d49e26de1709db45cb Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Sun, 3 Jun 2018 13:44:02 +0200 Subject: [PATCH 27/36] Remove useless import --- DependencyInjection/OverblogGraphQLExtension.php | 1 - 1 file changed, 1 deletion(-) diff --git a/DependencyInjection/OverblogGraphQLExtension.php b/DependencyInjection/OverblogGraphQLExtension.php index aa8c9ff93..f185e2078 100644 --- a/DependencyInjection/OverblogGraphQLExtension.php +++ b/DependencyInjection/OverblogGraphQLExtension.php @@ -21,7 +21,6 @@ use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\HttpKernel\Kernel; class OverblogGraphQLExtension extends Extension implements PrependExtensionInterface { From c011b3fdadf821247f7e0dfa9e069167e9330a19 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Sun, 3 Jun 2018 13:08:24 +0200 Subject: [PATCH 28/36] doc(graphql-schema-language): rewrite and add section for mutations --- .../definitions/graphql-schema-language.md | 113 +++++++++++++++--- 1 file changed, 99 insertions(+), 14 deletions(-) diff --git a/Resources/doc/definitions/graphql-schema-language.md b/Resources/doc/definitions/graphql-schema-language.md index 6d36c48ca..9a342f553 100644 --- a/Resources/doc/definitions/graphql-schema-language.md +++ b/Resources/doc/definitions/graphql-schema-language.md @@ -8,32 +8,117 @@ or this [cheat sheet](https://github.com/sogko/graphql-shorthand-notation-cheat- #### Usage +##### Define Types + +> [GraphQL documentation about types and fields](https://graphql.github.io/learn/schema/#object-types-and-fields). + + ```graphql # config/graphql/schema.types.graphql -type Query { - bar: Foo! - baz(id: ID!): Baz +type Character { + # Name of the character + name: String! + # This character appears in those episodes + appearsIn: [Episode]! } +``` + +##### Define Enumeration types -scalar Baz +> [GraphQL documentation about Enumerations types](https://graphql.github.io/learn/schema/#enumeration-types). -interface Foo { - # Description of my is_foo field - is_foo: Boolean +```graphql +# Enumeration of episodes +enum Episode { + NEWHOPE + EMPIRE + JEDI } -type Bar implements Foo { - is_foo: Boolean - user: User! +``` + +##### Define Interfaces + +> [GraphQL documentation about Interfaces](https://graphql.github.io/learn/schema/#interfaces). + +```graphql +interface Character { + id: ID! + name: String! + friends: [Character] + appearsIn: [Episode]! +} +``` + +```graphql +type Human implements Character { + id: ID! + name: String! + friends: [Character] + appearsIn: [Episode]! + starships: [Starship] + totalCredits: Int } -enum User { - TATA - TITI - TOTO +type Droid implements Character { + id: ID! + name: String! + friends: [Character] + appearsIn: [Episode]! + primaryFunction: String } ``` +##### Define queries + +> [GraphQL documentation about Query type](https://graphql.github.io/learn/schema/#the-query-and-mutation-types). + +```graphql +type RootQuery { + # Access all characters + characters: [Character]! +} +``` + +Do not forget to configure your schema **query** type, as described in the [schema documentation](https://github.com/overblog/GraphQLBundle/blob/master/Resources/doc/definitions/schema.md). + +```yml +overblog_graphql: + definitions: + schema: + query: RootQuery +``` + +##### Define mutations + +> [GraphQL documentation about Mutation type](https://graphql.github.io/learn/schema/#the-query-and-mutation-types). + +```graphql +input CreateCharacter { + name: String! +} + +input UpdateCharacter { + name: String! +} + +type RootMutation { + createCharacter(character: CreateCharacter!): Character! + updateCharacter(characterId: ID!, character: UpdateCharacter!): Character! +} +``` + +Do not forget to configure your schema **mutation** type, as described in the [schema documentation](https://github.com/overblog/GraphQLBundle/blob/master/Resources/doc/definitions/schema.md). + +```yml +overblog_graphql: + definitions: + schema: + mutation: RootMutation +``` + +--- + When using this shorthand syntax, you define your field resolvers (and some more configuration) separately from the schema. Since the schema already describes all of the fields, arguments, and result types, the only thing left is a collection of callable that are called to actually execute these fields. From d9c457d6b041a945b39aca3969720e8621d3dee8 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Sun, 10 Jun 2018 11:45:41 +0200 Subject: [PATCH 29/36] typo --- Resources/doc/definitions/relay/connection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/doc/definitions/relay/connection.md b/Resources/doc/definitions/relay/connection.md index 4a93236ea..c3db5ef49 100644 --- a/Resources/doc/definitions/relay/connection.md +++ b/Resources/doc/definitions/relay/connection.md @@ -22,7 +22,7 @@ User: resolve: '@=resolver("friends", [value, args])' friendsForward: type: userConnection - argsBuilder: "Relay::ForwardConnection + argsBuilder: "Relay::ForwardConnection" resolve: '@=resolver("friends", [value, args])' friendsBackward: type: userConnection From f022e8e7b5e14728af5cc8c6a6ce99f635e14b9d Mon Sep 17 00:00:00 2001 From: Andriy Komm Date: Tue, 19 Jun 2018 11:04:26 +0200 Subject: [PATCH 30/36] doc(type-inheritance): added an usage example for decorators --- Resources/doc/definitions/type-inheritance.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Resources/doc/definitions/type-inheritance.md b/Resources/doc/definitions/type-inheritance.md index 3b9c43a93..9d2ab8c44 100644 --- a/Resources/doc/definitions/type-inheritance.md +++ b/Resources/doc/definitions/type-inheritance.md @@ -138,6 +138,8 @@ CharacterWizard: for example `CharacterWizard` config is the result of `array_replace_recursive(CharacterConfig, CharacterWizardConfig)` +**Decorators:** + You can also create decorator types to be used as reusable templates. Decorators are only virtual and will not exists in final schema. That is the reason why decorator should never be reference as type in schema definition. @@ -151,3 +153,46 @@ ObjectA: fields: bar: {type: String!} ``` + +You can use interfaces with decorators. Because they are only virtual, you do not have to implement the interface on the decorator itself. +But you have to implement it on the type you decorate. + +Imagine the following situation: + +```yaml +Node: + type: 'interface' + config: + # [...] resolveType logic unimportant here + fields: + id: + type: 'ID!' + # [...] resolve logic unimportant here + +NodeEditPermission: + type: 'object' + decorator: true + config: + # This interface must be implemented by type which inherits from this decorator + interfaces: ["Node"] + fields: + canEdit: + type: 'Boolean!' + # The `value` is enforced to implement "Node" no matter which type uses this decorator. + # The resolver could be for example a (cached) user of symfony/security authorization checker + # which does ->isGranted([attribute] $attribute, [subject] $value) + resolve: '@=resolver("Permission.nodeAttribute", ["edit", value])' + +Product: + type: 'object' + inherits: + - 'NodeEditPermission' + # - 'NodeRemovePermission' + # - [...] + config: + fields: + # Must implement "Node" interface because "NodeEditPermission" decorator requires it + # Not implementing would raise an graphql error + id: + type: 'ID!' +``` \ No newline at end of file From 897627a1db19e222bc493184095593af8578a00b Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Thu, 26 Jul 2018 07:05:11 +0200 Subject: [PATCH 31/36] docs: improve mutation example --- Resources/doc/definitions/mutation.md | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Resources/doc/definitions/mutation.md b/Resources/doc/definitions/mutation.md index 802dc2842..6ed8663ad 100644 --- a/Resources/doc/definitions/mutation.md +++ b/Resources/doc/definitions/mutation.md @@ -34,4 +34,56 @@ IntroduceShipInput: type: "String!" ``` +To implement the logic behind your mutation, you should create a new class that +implements `MutationInterface` and `AliasedInterface` interfaces. + +```php +factionRepository = $factionRepository; + } + + public function createShip(string $shipName, int $factionId): array + { + // `$shipName` has the value of `args['input']['shipName']` + // `$factionId` has the value of `args['input']['factionId']` + + // Do something with `$shipName` and `$factionId` ... + $ship = new Ship($shipName); + $faction = $this->factionRepository->find($factionId); + $faction->addShip($ship); + // ... + + + // Then returns our payload, it should fits `IntroduceShipPayload` type + return [ + 'ship' => $ship, + 'faction' => $faction, + ]; + } + + /** + * {@inheritdoc} + */ + public static function getAliases() + { + return [ + // `create_ship` is the name of the mutation that you SHOULD use inside of your types definition + // `createShip` is the method that will be executed when you call `@=resolver('create_ship')` + 'createShip' => 'create_ship' + ]; + } +} +``` + Here the same example [using relay mutation](relay/mutation.md). From 6344b714b61dd32926b08c065e68ef7247cf1af3 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Thu, 26 Jul 2018 07:06:22 +0200 Subject: [PATCH 32/36] docs(mutation): add link to relay --- Resources/doc/definitions/mutation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/doc/definitions/mutation.md b/Resources/doc/definitions/mutation.md index 6ed8663ad..bba66517e 100644 --- a/Resources/doc/definitions/mutation.md +++ b/Resources/doc/definitions/mutation.md @@ -1,6 +1,6 @@ # Mutation -Here an example without using relay: +Here an example of mutation without using [relay](https://facebook.github.io/relay/): ```yaml Mutation: From 12179f57e76a2ea3bdd77866177b081f4802b10b Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Sun, 19 Aug 2018 18:51:28 +0200 Subject: [PATCH 33/36] EOL 0.10 --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 8a1bcab34..2c1ddd3d3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ Versions requirements |----------------------------------------------------------------:|------------:|------------------:|--------------------:| | [`0.12`](https://github.com/overblog/GraphQLBundle/tree/master) | `>= 7.1` | `>= 3.1` | DEV | | [`0.11`](https://github.com/overblog/GraphQLBundle/tree/0.11) | `>= 5.6` | `>= 3.1, <= 4.0` | Active support | -| [`0.10`](https://github.com/overblog/GraphQLBundle/tree/0.10) | `>= 5.5.9` | `>= 2.8, <= 3.1` | Active support | +| [`0.10`](https://github.com/overblog/GraphQLBundle/tree/0.10) | `>= 5.5.9` | `>= 2.8, <= 3.1` | End of life | | [`0.9`](https://github.com/overblog/GraphQLBundle/tree/0.9) | `>= 5.5.9` | `>= 2.8, <= 3.1` | End of life | | [`0.8`](https://github.com/overblog/GraphQLBundle/tree/0.8) | `>= 5.4 ` | `>= 2.7, <= 3.1` | End of life | From 65c621b5b65d69bb370279f17d908c08b093b1f7 Mon Sep 17 00:00:00 2001 From: Yanis Batura Date: Fri, 24 Aug 2018 09:36:31 +0700 Subject: [PATCH 34/36] Fix typos, improve phrasing in "Handle CORS" doc page --- docs/security/handle-cors.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/security/handle-cors.md b/docs/security/handle-cors.md index 2b48d6b37..e39ac769a 100644 --- a/docs/security/handle-cors.md +++ b/docs/security/handle-cors.md @@ -2,9 +2,9 @@ Handle CORS =========== The bundle comes out of the box with a generic and simple CORS (Cross-Origin Resource Sharing) handler -but we recommends using [NelmioCorsBundle](https://github.com/nelmio/NelmioCorsBundle) for more flexibility... +but we recommend using [NelmioCorsBundle](https://github.com/nelmio/NelmioCorsBundle) for more flexibility... -The handler is disabled by default. To enabled it: +The handler is disabled by default. To enable it: ```yaml overblog_graphql: @@ -13,7 +13,7 @@ overblog_graphql: handle_cors: true ``` -Here the values of the headers that will be returns on preflight request: +These headers will be returned on preflight requests: Headers | Value -------------------------------- | --------------------------------------- From 8fc6851b7b15c93f3be89221a0704828b3f05626 Mon Sep 17 00:00:00 2001 From: Yanis Batura Date: Fri, 24 Aug 2018 09:44:05 +0700 Subject: [PATCH 35/36] Improve phrasing in "Object access control" --- docs/security/object-access-control.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/security/object-access-control.md b/docs/security/object-access-control.md index fc670a588..c490cfaa9 100644 --- a/docs/security/object-access-control.md +++ b/docs/security/object-access-control.md @@ -1,14 +1,14 @@ Object access Control ====================== -If your GraphQL schema have multiple paths to the same resolver, you may end up with duplicated access control on the different fields leading to this resolver. +If your GraphQL schema has multiple paths to the same resolver, you may end up with duplicated access control on the different fields leading to this resolver. -An access control can be added on the whole object using a decorator type for this protected field and make every parent extend this type. +Access control can be added to an object as a whole using a decorator type for this protected field and make every parent extend this type. -An access control can be added on each field using `config.fields.*.access` or globally with `config.fieldsDefaultAccess`. -If `config.fields.*.access` value is true field will be normally resolved but will be `null` otherwise. -Act like access is`true` if not set. +Access control can be added to individual fields using `config.fields.*.access` or globally with `config.fieldsDefaultAccess`. +If the value returned by `config.fields.*.access` is true, the field will be resolved normally, and `null` will be returned otherwise. +If not set, acts like if access is `true`. In the example below the user field protection is set by the decorator: From 291aa3b278cdc4038deaaaf993dcb9ae3f29af1a Mon Sep 17 00:00:00 2001 From: Yanis Batura Date: Fri, 24 Aug 2018 09:47:25 +0700 Subject: [PATCH 36/36] Fix typos, punctuation in "Fields public control" --- docs/security/fields-public-control.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/security/fields-public-control.md b/docs/security/fields-public-control.md index 2ec84b1b3..98338b8e2 100644 --- a/docs/security/fields-public-control.md +++ b/docs/security/fields-public-control.md @@ -1,7 +1,7 @@ Fields public Control ===================== -You can use `config.fields.*.public` to control if a field needs to be removed the results. +You can use `config.fields.*.public` to control if a field needs to be removed from the results. If `config.fields.*.public` value is true or is not set, the field will be visible. If value is false, then the field will be removed (in any query, including inspection queries). @@ -20,7 +20,7 @@ AnObject: ``` -You can also use `config.fieldsDefaultPublic` to handle the setting globally on an object : +You can also use `config.fieldsDefaultPublic` to handle the setting globally on an object: ```yaml AnObject: @@ -34,6 +34,6 @@ AnObject: type: "String" ``` -Have you noticed `typeName` and `fieldName` here ? This variables are always set to the current +Have you noticed `typeName` and `fieldName` here? These variables are always set to the current type name and current field name, meaning you can apply a per field `public` setting on all the fields with one line of yaml.