From 637156fe65a10d330f7d73c0b86933e023e9ef32 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 18 Aug 2017 02:56:22 +0700 Subject: [PATCH] Further documentation improvements --- UPGRADE.md | 14 ++++- docs/data-fetching.md | 71 +++++++++++++---------- docs/index.md | 5 +- docs/reference.md | 97 ++++++++++++++++++++++++++++---- docs/type-system/index.md | 38 +++++-------- docs/type-system/scalar-types.md | 12 ++-- mkdocs.yml | 3 +- 7 files changed, 167 insertions(+), 73 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 6cfed46..d081b52 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,6 +1,6 @@ ## Upgrade v0.8.x, v0.9.x > v0.10.x -### Breaking: minimum PHP version was changed from 5.4 to 5.5 +### Breaking: changed minimum PHP version from 5.4 to 5.5 It allows us to leverage `::class` constant, `generators` and other features of newer PHP versions. ### Breaking: default error formatting @@ -71,8 +71,9 @@ to adjust if you were checking for this error in your custom error formatters. ### Breaking: removed previously deprecated ability to define type as callable See https://github.com/webonyx/graphql-php/issues/35 -### Deprecated: `GraphQL\GraphQL::executeAndReturnResult` renamed to `GraphQL\GraphQL::executeQuery` -Old method name is still available, but will trigger deprecation warning in next version. +### Deprecated: `GraphQL\GraphQL::executeAndReturnResult` +Method is renamed to `GraphQL\GraphQL::executeQuery`. Old method name is still available, +but will trigger deprecation warning in the next version. ### Deprecated: `GraphQL\GraphQL::execute` Use `GraphQL\GraphQL::executeQuery()->toArray()` instead. @@ -95,6 +96,13 @@ $schema->assertValid(); ``` See https://github.com/webonyx/graphql-php/issues/148 +### Non-breaking: usage on async platforms +When using the library on async platforms use separate method `GraphQL::promiseToExecute()`. +It requires promise adapter in it's first argument and always returns a `Promise`. + +Old methods `GraphQL::execute` and `GraphQL::executeAndReturnResult` still work in backwards-compatible manner, +but they are deprecated and will be removed eventually. + ## Upgrade v0.7.x > v0.8.x All of those changes apply to those who extends various parts of this library. If you only use the library and don't try to extend it - everything should work without breaks. diff --git a/docs/data-fetching.md b/docs/data-fetching.md index 7b2b5e5..9b079a2 100644 --- a/docs/data-fetching.md +++ b/docs/data-fetching.md @@ -3,10 +3,10 @@ GraphQL is data-storage agnostic. You can use any underlying data storage engine plain files or in-memory data structures. In order to convert GraphQL query to PHP array **graphql-php** traverses query fields (using depth-first algorithm) and -runs special `resolve` function on each field. This `resolve` function is provided by you as a part of +runs special **resolve** function on each field. This **resolve** function is provided by you as a part of [field definition](type-system/object-types/#field-configuration-options) or [query execution call](executing-queries/#overview). -Result returned by `resolve` function is directly included in response (for scalars and enums) +Result returned by **resolve** function is directly included in response (for scalars and enums) or passed down to nested fields (for objects). Let's walk through an example. Consider following GraphQL query: @@ -22,7 +22,7 @@ Let's walk through an example. Consider following GraphQL query: } ``` -We need Schema that can fulfill it. On the very top level Schema contains Query type: +We need a Schema that can fulfill it. On the very top level the Schema contains Query type: ```php $queryType = new ObjectType([ @@ -44,12 +44,12 @@ $queryType = new ObjectType([ ]); ``` -As we see field `lastStory` has `resolve` function that is responsible for fetching data. +As we see field **lastStory** has **resolve** function that is responsible for fetching data. In our example we simply return array value, but in real-world application you would query your database/cache/search index and return result. -Since `lastStory` is of complex type `BlogStory` this result is passed down to fields of this type: +Since **lastStory** is of composite type **BlogStory** this result is passed down to fields of this type: ```php $blogStoryType = new ObjectType([ @@ -81,15 +81,15 @@ $blogStoryType = new ObjectType([ ]); ``` -Here `$blogStory` is the array returned by `lastStory` field above. +Here **$blogStory** is the array returned by **lastStory** field above. -Again: in real-world applications you would fetch user data from datastore by `authorId` and return it. +Again: in real-world applications you would fetch user data from datastore by **authorId** and return it. Also note that you don't have to return arrays. You can return any value, **graphql-php** will pass it untouched to nested resolvers. -But then the question appears - field `title` has no `resolve` option. How is it resolved? +But then the question appears - field **title** has no **resolve** option. How is it resolved? -The answer is: there is default resolver for all fields. When you define your own `resolve` function +There is a default resolver for all fields. When you define your own **resolve** function for a field you simply override this default resolver. # Default Field Resolver @@ -114,12 +114,10 @@ function defaultFieldResolver($source, $args, $context, ResolveInfo $info) } ``` -As you see it returns value by key (for arrays) or property (for objects). If value is not set - it returns `null`. +As you see it returns value by key (for arrays) or property (for objects). +If value is not set - it returns **null**. -To override default resolver - use: -```php -GraphQL\GraphQL::setDefaultFieldResolver($myResolverCallback); -``` +To override the default resolver, pass it as an argument of [executeQuery](executing-queries) call. # Default Field Resolver per Type Sometimes it might be convenient to set default field resolver per type. You can do so by providing @@ -154,8 +152,8 @@ Keep in mind that **field resolver** has precedence over **default field resolve # Solving N+1 Problem Since: 0.9.0 -One of the most annoying problems with data fetching is so-called [N+1 problem](https://secure.phabricator.com/book/phabcontrib/article/n_plus_one/). - +One of the most annoying problems with data fetching is a so-called +[N+1 problem](https://secure.phabricator.com/book/phabcontrib/article/n_plus_one/).
Consider following GraphQL query: ``` { @@ -219,28 +217,41 @@ In this example only one query will be executed for all story authors comparing in naive implementation. # Async PHP -Since: 0.9.0 +Since: 0.10.0 (version 0.9.0 had slightly different API which is deprecated) If your project runs in environment that supports async operations -(like `HHVM`, `ReactPHP`, `Icicle.io`, `appserver.io` `PHP threads`, etc) you can leverage -the power of your platform to resolve fields asynchronously. +(like HHVM, ReactPHP, Icicle.io, appserver.io, PHP threads, etc) +you can leverage the power of your platform to resolve some fields asynchronously. The only requirement: your platform must support the concept of Promises compatible with [Promises A+](https://promisesaplus.com/) specification. -To enable async support - set adapter for promises: -``` -GraphQL\GraphQL::setPromiseAdapter($adapter); +To start using this feature, switch facade method for query execution from +**executeQuery** to **promiseToExecute**: + +```php +$promise = GraphQL::promiseToExecute( + $promiseAdapter, + $schema, + $queryString, + $rootValue = null, + $contextValue = null, + $variableValues = null, + $operationName = null, + $fieldResolver = null, + $validationRules = null +); +$promise->then(function(ExecutionResult $result) { + return $result->toArray(); +}); ``` -Where `$adapter` is an instance of class implementing `GraphQL\Executor\Promise\PromiseAdapter` interface. +Where **$promiseAdapter** is an instance of: -Then in your `resolve` functions you should return `Promises` of your platform instead of -`GraphQL\Deferred` instances. +* For [ReactPHP](https://github.com/reactphp/react) (requires **react/promise** as composer dependency):
+ `GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter` -Platforms supported out of the box: +* Other platforms: write your own class implementing interface:
+ `GraphQL\Executor\Promise\PromiseAdapter`. -* [ReactPHP](https://github.com/reactphp/react) (requires **react/promise** as composer dependency): - `GraphQL\GraphQL::setPromiseAdapter(new GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter());` - -To integrate other platform - implement `GraphQL\Executor\Promise\PromiseAdapter` interface. +Then your **resolve** functions should return promises of your platform instead of `GraphQL\Deferred`s. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index c8ed25f..6295353 100644 --- a/docs/index.md +++ b/docs/index.md @@ -48,4 +48,7 @@ Schema Language parser. Ready for real-world usage. ## Github -Project source code is [hosted on GitHub](https://github.com/webonyx/graphql-php). \ No newline at end of file +Project source code is [hosted on GitHub](https://github.com/webonyx/graphql-php). + +## Framework Integrations +Read the section about [Complementary tools](complementary-tools/). \ No newline at end of file diff --git a/docs/reference.md b/docs/reference.md index 6742a4b..fe51cae 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -21,8 +21,7 @@ class GraphQL $variableValues = null, $operationName = null, callable $fieldResolver = null, - array $validationRules = null, - GraphQL\Executor\Promise\PromiseAdapter $promiseAdapter = null + array $validationRules = null ); /** @@ -502,22 +501,20 @@ use GraphQL\Executor\Promise\PromiseAdapter; class Executor { /** - * Executes DocumentNode against given schema. + * Executes DocumentNode against given $schema using given $promiseAdapter for deferred resolvers. + * Returns promise which is always fullfilled with instance of ExecutionResult * - * When $promiseAdapter is passed returns Promise instance produced by this adapter. - * By default simply returns ExecutionResult - * - * @return ExecutionResult|Promise + * @return Promise */ - public static function execute( + public static function promiseToExecute( + PromiseAdapter $promiseAdapter, Schema $schema, DocumentNode $ast, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null, - callable $fieldResolver = null, - PromiseAdapter $promiseAdapter = null + callable $fieldResolver = null ); } ``` @@ -601,6 +598,80 @@ class ExecutionResult implements \JsonSerializable } ``` +# GraphQL\Executor\Promise\PromiseAdapter +Required for [Async PHP](data-fetching/#async-php) only. + +```php +interface PromiseAdapter +{ + /** + * Return true if value is promise of underlying system + * + * @param mixed $value + * @return bool + */ + public function isThenable($value); + + /** + * Converts thenable of underlying system into Promise instance + * + * @param object $thenable + * @return Promise + */ + public function convertThenable($thenable); + + /** + * Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described + * in Promises/A+ specs. Then returns new wrapped Promise instance. + * + * @param Promise $promise + * @param callable|null $onFulfilled + * @param callable|null $onRejected + * + * @return Promise + */ + public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null); + + /** + * Creates a Promise + * + * @param callable $resolver + + * @return Promise + */ + public function create(callable $resolver); + + /** + * Creates a fulfilled Promise for a value if the value is not a promise. + * + * @param mixed $value + * + * @return Promise + */ + public function createFulfilled($value = null); + + /** + * Creates a rejected promise for a reason if the reason is not a promise. If + * the provided reason is a promise, then it is returned as-is. + * + * @param \Throwable $reason + * + * @return Promise + */ + public function createRejected($reason); + + /** + * Given an array of promises (or values), returns a promise that is fulfilled when all the + * items in the array are fulfilled. + * + * @param array $promisesOrValues Promises or values. + * + * @return Promise + */ + public function all(array $promisesOrValues); +} +``` + # GraphQL\Type\Definition\ResolveInfo ```php @@ -910,3 +981,9 @@ class FormattedError public static function prepareFormatter(callable $formatter = null, $debug); } ``` + +# GraphQL\Server\OperationParams + +# GraphQL\Server\StandardServer + +# GraphQL\Server\Helper diff --git a/docs/type-system/index.md b/docs/type-system/index.md index 6e4c90e..999bfc0 100644 --- a/docs/type-system/index.md +++ b/docs/type-system/index.md @@ -50,34 +50,26 @@ class MyType extends ObjectType } ``` -You can also mix-and-match styles for convenience. For example: -```php - [ - 'body' => new ObjectType([ - 'name' => 'BlogPostBody', - 'fields' => [ - 'html' => Type::string(), - 'text' => Type::string(), - ] - ]) - ] - ]; - parent::__construct($config); - } +type Query { + greetings(input: HelloInput!): String! +} + +input HelloInput { + firstName: String! + lastName: String } ``` +[Read more](/type-system/type-language/) about it in a dedicated docs section. + # Type Registry Every type must be presented in Schema by single instance (**graphql-php** throws when it discovers several instances with the same `name` in schema). diff --git a/docs/type-system/scalar-types.md b/docs/type-system/scalar-types.md index cce575b..0a2f9e7 100644 --- a/docs/type-system/scalar-types.md +++ b/docs/type-system/scalar-types.md @@ -42,6 +42,7 @@ Here is an example of simple `Email` type: namespace MyApp; use GraphQL\Error\Error; +use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; use GraphQL\Utils\Utils; @@ -62,9 +63,12 @@ class EmailType extends ScalarType { // Assuming internal representation of email is always correct: return $value; - - // If it might be incorrect and you want to make sure that only correct values are included in response - - // use following line instead: + + // If it might be incorrect and you want to make sure that only correct values are included + // in response - use following line instead: + // if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { + // throw new InvariantViolation("Could not serialize following value as email: " . Utils::printSafe($value)); + // } // return $this->parseValue($value); } @@ -77,7 +81,7 @@ class EmailType extends ScalarType public function parseValue($value) { if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { - throw new \UnexpectedValueException("Cannot represent value as email: " . Utils::printSafe($value)); + throw new Error("Cannot represent following value as email: " . Utils::printSafeJson($value)); } return $value; } diff --git a/mkdocs.yml b/mkdocs.yml index 0455ace..d811696 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,12 +15,11 @@ pages: - Schema: type-system/schema.md - Using Type Language: type-system/type-language.md - Executing Queries: executing-queries.md -- Fetching Data: data-fetching.md +- Fetching Data (resolving fields): data-fetching.md - Handling Errors: error-handling.md # - Mutations: mutations.md # - Security: security.md # - Performance tips: performance.md -# - Standard Server: server.md - How it works: how-it-works.md - Class Reference: reference.md - Complementary Tools: complementary-tools.md