From ccad34517c6033f10fc883c6bacb624b5f9a53c8 Mon Sep 17 00:00:00 2001 From: vladar Date: Tue, 8 Nov 2016 20:02:10 +0700 Subject: [PATCH] Work in progress on better docs (added section on query execution and error handling) --- docs/error-handling.md | 136 +++++++++++++++++++++++ docs/executing-queries.md | 49 ++++++++ docs/type-system/directives.md | 6 +- examples/01-blog/Blog/Type/QueryType.php | 6 + examples/01-blog/graphql.php | 4 +- mkdocs.yml | 4 +- 6 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 docs/error-handling.md create mode 100644 docs/executing-queries.md diff --git a/docs/error-handling.md b/docs/error-handling.md new file mode 100644 index 0000000..3bda7bc --- /dev/null +++ b/docs/error-handling.md @@ -0,0 +1,136 @@ +# Errors in GraphQL + +Query execution process never throws exceptions. Instead all errors that occur during query execution +are caught, collected and included in response. + +There are 3 types of errors in GraphQL (Syntax, Validation and Execution errors): + +**Syntax** errors are returned in response when query has invalid syntax and could not be parsed. +Example output for invalid query `{hello` (missing bracket): +```php +[ + 'errors' => [ + [ + 'message' => "Syntax Error GraphQL request (1:7) Expected Name, found \n\n1: {hello\n ^\n", + 'locations' => [ + ['line' => 1, 'column' => 7] + ] + ] + ] +] +``` + +**Validation** errors - returned in response when query has semantic errors. +Example output for invalid query `{unknownField}`: +```php +[ + 'errors' => [ + [ + 'message' => 'Cannot query field "unknownField" on type "Query".', + 'locations' => [ + ['line' => 1, 'column' => 2] + ] + ] + ] +] +``` + +**Execution** errors - included in response when some field resolver throws +(or returns unexpected value). Example output for query with exception thrown in +field resolver `{fieldWithException}`: +```php +[ + 'data' => [ + 'fieldWithException' => null + ], + 'errors' => [ + [ + 'message' => 'Exception message thrown in field resolver', + 'locations' => [ + ['line' => 1, 'column' => 2] + ], + 'path': [ + 'fieldWithException' + ] + ] + ] +] +``` + +Obviously when **Syntax** or **Validation** error is detected - process is interrupted and query is not +executed. In such scenarios response only contains **errors**, but not **data**. + +GraphQL is forgiving to **Execution** errors which occur in resolvers of nullable fields. +If such field throws or returns unexpected value the value of the field in response will be simply +replaced with `null` and error entry will be added to response. + +If exception is thrown in non-null field - it will be bubbled up to first nullable field which will +be replaced with `null` (and error entry added to response). If all fields up to the root are non-null +**data** entry will be missing in response and only **errors** key will be presented. + +# Debugging tools + +Each error entry contains pointer to line and column in original query string which caused +the error: + +```php +'locations' => [ + ['line' => 1, 'column' => 2] +] +``` + + GraphQL clients like **Relay** or **GraphiQL** leverage this information to highlight +actual piece of query containing error. + +In some cases (like deep fragment fields) locations will include several entries to track down the +path to field with error in query. + +**Execution** errors also contain **path** from the very root field to actual field value producing +an error (including indexes for array types and fieldNames for object types). So in complex situation +this path could look like this: + +```php +'path' => [ + 'lastStoryPosted', + 'author', + 'friends', + 3 + 'fieldWithException' +] +``` + +# Custom Error Formatting + +If you want to apply custom formatting to errors - use **GraphQL::executeAndReturnResult()** instead +of **GraphQL::execute()**. + +It has exactly the same [signature](executing-queries/), but instead of array it +returns `GraphQL\Executor\ExecutionResult` instance which holds errors in public **$errors** +property and data in **$data** property. + +Each entry of **$errors** array contains instance of `GraphQL\Error\Error` which wraps original +exceptions thrown by resolvers. To access original exceptions use `$error->getPrevious()` method. +But note that previous exception is only available for **Execution** errors. + +# Schema Errors +We only covered errors which occur during query execution process. But schema definition can also +throw if there is an error in one of type definitions. + +Usually such errors mean that there is some logical error in your schema and it is the only case +when it makes sense to return `500` error code for GraphQL endpoint: + +```php +try { + $schema = new Schema([ + // ... + ]); + + $result = GraphQL::execute($schema, $query); +} catch(\Exception $e) { + header('Content-Type: application/json', true, 500); + echo json_encode([ + 'message' => 'Unexpected error' + ]); + exit; +} +``` diff --git a/docs/executing-queries.md b/docs/executing-queries.md new file mode 100644 index 0000000..718290d --- /dev/null +++ b/docs/executing-queries.md @@ -0,0 +1,49 @@ +# Overview +Query execution is a complex process involving multiple steps, including query **parsing**, +**validating** and finally **executing** against your schema. + +**graphql-php** provides convenient facade for this process in class `GraphQL\GraphQL`: + +```php +use GraphQL\GraphQL; + +$result = GraphQL::execute( + $schema, + $queryString, + $rootValue = null, + $contextValue = null, + $variableValues = null, + $operationName = null +); +``` + +Method returns `array` with **data** and **errors** keys, as described by +[GraphQL specs](http://facebook.github.io/graphql/#sec-Response-Format). +This array is suitable for further serialization (e.g. using `json_encode`). +See also section on [error handling](error-handling/). + + +Description of method arguments: + +Argument | Type | Notes +------------ | -------- | ----- +schema | `GraphQL\Schema` | **Required.** Instance of your application [Schema](type-system/schema/) +queryString | `string` or `GraphQL\Language\AST\Document` | **Required.** Actual GraphQL query string to be parsed, validated and executed. If you parse query elsewhere before executing - pass corresponding ast document here to avoid new parsing. +rootValue | `mixed` | Any value that represents a root of your data graph. It is passed as 1st argument to field resolvers of [Query type](type-system/schema/#query-and-mutation-types). Can be omitted or set to null if actual root values are fetched by Query type itself. +contextValue | `mixed` | Any value that holds information shared between all field resolvers. Most often they use it to pass currently logged in user, locale details, etc.

It will be available as 3rd argument in all field resolvers. (see section on [Field Definitions](type-system/object-types/#field-configuration-options) for reference) **graphql-php** never modifies this value and passes it *as is* to all underlying resolvers. +variableValues | `array` | Map of variable values passed along with query string. See section on [query variables on official GraphQL website](http://graphql.org/learn/queries/#variables) +operationName | `string` | Allows the caller to specify which operation in queryString will be run, in cases where queryString contains multiple top-level operations. + +Following reading describes implementation details of query execution process. It may clarify some +internals of GraphQL but is not required in order to use it. Feel free to skip to next section +on [Error Handling](error-handling/) for essentials. + + +# Parsing +TODOC + +# Validating +TODOC + +# Executing +TODOC diff --git a/docs/type-system/directives.md b/docs/type-system/directives.md index a3ddfe4..5bdced6 100644 --- a/docs/type-system/directives.md +++ b/docs/type-system/directives.md @@ -1,6 +1,6 @@ # Built-in directives -Directive is a way to dynamically change the structure and shape of queries using variables. -Directive can be attached to a field or fragment inclusion, and can affect execution of the +Directive is a way for client to give GraphQL server additional context and hints on how to execute +the query. Directive can be attached to a field or fragment inclusion, and can affect execution of the query in any way the server desires. GraphQL specification includes two built-in directives: @@ -21,7 +21,7 @@ query Hero($episode: Episode, $withFriends: Boolean!) { ``` Here if `$withFriends` variable is set to `false` - friends section will be ignored and excluded from response. Important implementation detail: those fields will never be executed -(and not just removed from response after execution). +(not just removed from response after execution). # Custom directives **graphql-php** supports custom directives even though their presence does not affect execution of fields. diff --git a/examples/01-blog/Blog/Type/QueryType.php b/examples/01-blog/Blog/Type/QueryType.php index c3f459b..0e7268e 100644 --- a/examples/01-blog/Blog/Type/QueryType.php +++ b/examples/01-blog/Blog/Type/QueryType.php @@ -50,6 +50,12 @@ class QueryType extends BaseType 'type' => Types::string(), 'deprecationReason' => 'This field is deprecated!' ], + 'fieldWithException' => [ + 'type' => Types::string(), + 'resolve' => function() { + throw new \Exception("Exception message thrown in field resolver"); + } + ], 'hello' => Type::string() ], 'resolveField' => function($val, $args, $context, ResolveInfo $info) { diff --git a/examples/01-blog/graphql.php b/examples/01-blog/graphql.php index 88d343a..fe51fe4 100644 --- a/examples/01-blog/graphql.php +++ b/examples/01-blog/graphql.php @@ -46,9 +46,7 @@ try { $data += ['query' => null, 'variables' => null]; if (null === $data['query']) { - $data['query'] = ' - {hello} - '; + $data['query'] = '{hello}'; } // GraphQL schema to be passed to query executor: diff --git a/mkdocs.yml b/mkdocs.yml index 67df152..a916917 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,7 +13,9 @@ pages: - Input Types: type-system/input-types.md - Directives: type-system/directives.md - Defining Schema: type-system/schema.md -- Data Fetching: data-fetching.md +- Executing Queries: executing-queries.md +- Handling Errors: error-handling.md +- Fetching Data: data-fetching.md - Best Practices: best-practices.md - Complementary Tools: complementary-tools.md theme: readthedocs