Documentation improvements (wip)

This commit is contained in:
Vladimir Razuvaev 2017-08-17 02:17:01 +07:00
parent b294329a40
commit a2be92937e
5 changed files with 197 additions and 133 deletions

View File

@ -4,7 +4,7 @@ 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 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). [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). or passed down to nested fields (for objects).

View File

@ -1,143 +1,145 @@
# Errors in GraphQL # Errors in GraphQL
Query execution process never throws exceptions. Instead all errors that occur during query execution Query execution process never throws exceptions. Instead all errors are caught and collected in
are caught, collected and included in response. [execution result](executing-queries/#execution-result).
There are 3 types of errors in GraphQL (Syntax, Validation and Execution errors): Later `$result->toArray()` automatically converts these errors to array using default
error formatting. But you can apply [custom error filtering and formatting](#custom-error-filtering-and-formatting)
for your specific requirements.
# Default Error formatting
By default each error entry is converted to associative array with following structure:
**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 ```php
[ [
'errors' => [ 'message' => 'Error message',
[ 'category' => 'graphql',
'message' => "Syntax Error GraphQL request (1:7) Expected Name, found <EOF>\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' => [ 'locations' => [
['line' => 1, 'column' => 2] ['line' => 1, 'column' => 2]
], ],
'path': [ 'path': [
'listField',
0,
'fieldWithException' 'fieldWithException'
] ]
]
]
] ]
``` ```
Entry at key **locations** points to character in query string which caused the error.
In some cases (like deep fragment fields) locations will include several entries to track down path to
field with error in query.
Obviously when **Syntax** or **Validation** error is detected - process is interrupted and query is not Entry at key **path** exists only for errors caused by exceptions thrown in resolvers. It contains path
executed. In such scenarios response only contains **errors**, but not **data**. from the very root field to actual field value producing an error
(including indexes for list types and field names for composite types).
GraphQL is forgiving to **Execution** errors which occur in resolvers of nullable fields. **Internal errors**
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 - error bubbles up to first nullable field. This nullable field is As of version **0.10.0** all exceptions thrown in resolvers are reported with generic message **"Internal server error"**.
replaced with `null` and error entry is added to response. If all fields up to the root are non-null - This is done to avoid information leak in production environments (e.g. database connection errors, file access errors, etc).
**data** entry will be removed from response and only **errors** key will be presented.
# Debugging tools Only exceptions implementing interface `GraphQL\Error\ClientAware` and claiming themselves as **safe** will
be reported with full error message.
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 and will be `null`
for **Syntax** or **Validation** errors.
For example: For example:
```php
use GraphQL\Error\ClientAware;
class MySafeException extends \Exception implements ClientAware
{
public function isClientSafe()
{
return true;
}
public function getCategory()
{
return 'businessLogic';
}
}
```
When such exception is thrown it will be reported with full error message:
```php
[
'message' => 'My reported error',
'category' => 'businessLogic',
'locations' => [
['line' => 10, 'column' => 2]
],
'path': [
'path',
'to',
'fieldWithException'
]
]
```
To change default **"Internal server error"** message to something else, use:
```
GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error");
```
#Debugging tools
During development or debugging use `$result->toArray(true)` to add **debugMessage** key to
each formatted error entry. If you also want to add exception trace - pass flags instead:
```
use GraphQL\Error\FormattedError;
$debug = FormattedError::INCLUDE_DEBUG_MESSAGE | FormattedError::INCLUDE_TRACE;
$result = GraphQL::executeQuery(/*args*/)->toArray($debug);
```
This will make each error entry to look like this:
```php
[
'message' => 'Internal server error',
'debugMessage' => 'Actual exception message',
'category' => 'internal',
'locations' => [
['line' => 10, 'column' => 2]
],
'path': [
'listField',
0,
'fieldWithException'
],
'trace' => [
/* Formatted original exception trace */
]
]
```
# Custom Error Handling and Formatting
It is possible to define custom **formatter** and **handler** for result errors.
**Formatter** is responsible for converting instances of `GraphQL\Error\Error` to array.
**Handler** is useful for error filtering and logging.
For example these are default formatter and handler:
```php ```php
$result = GraphQL::executeAndReturnResult() use GraphQL\Error\Error;
->setErrorFormatter(function(GraphQL\Error\Error $err) { use GraphQL\Error\FormattedError;
$resolverException = $err->getPrevious();
if ($resolverException instanceof MyResolverException) { $myErrorFormatter = function(Error $error) {
$formattedError = [ return FormattedError::createFromException($error);
'message' => $resolverException->getMessage(), };
'code' => $resolverException->getCode()
]; $myErrorHandler = function(array $errors, callable $formatter) {
} else { return array_map($formatter, $errors);
$formattedError = [ };
'message' => $err->getMessage()
]; $result = GraphQL::executeQuery(/* $args */)
} ->setErrorFormatter($myErrorFormatter)
return $formattedError; ->setErrorHandler($myErrorHandler)
})
->toArray(); ->toArray();
``` ```
You may also re-throw exceptions in result handler for debugging, etc.
# Schema Errors # Schema Errors
So far we only covered errors which occur during query execution process. But schema definition can So far 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. also throw `GraphQL\Error\InvariantViolation` 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 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: when it makes sense to return `500` error code for GraphQL endpoint:
@ -148,7 +150,7 @@ try {
// ... // ...
]); ]);
$body = GraphQL::execute($schema, $query); $body = GraphQL::executeQuery($schema, $query);
$status = 200; $status = 200;
} catch(\Exception $e) { } catch(\Exception $e) {
$body = json_encode([ $body = json_encode([

View File

@ -2,26 +2,36 @@
Query execution is a complex process involving multiple steps, including query **parsing**, Query execution is a complex process involving multiple steps, including query **parsing**,
**validating** and finally **executing** against your [schema](type-system/schema/). **validating** and finally **executing** against your [schema](type-system/schema/).
**graphql-php** provides convenient facade for this process in class `GraphQL\GraphQL`: **graphql-php** provides convenient facade for this process in class
[`GraphQL\GraphQL`](/reference/#graphqlgraphql):
```php ```php
use GraphQL\GraphQL; use GraphQL\GraphQL;
$result = GraphQL::execute( $result = GraphQL::executeQuery(
$schema, $schema,
$queryString, $queryString,
$rootValue = null, $rootValue = null,
$contextValue = null, $contextValue = null,
$variableValues = null, $variableValues = null,
$operationName = null $operationName = null,
$fieldResolver = null,
$validationRules = null,
$promiseAdapter = null
); );
``` ```
Method returns `array` with **data** and **errors** keys, as described by It returns an instance of [`GraphQL\Executor\ExecutionResult`](/reference/#graphqlexecutorexecutionresult)
[GraphQL specs](http://facebook.github.io/graphql/#sec-Response-Format). which can be easily converted to array:
This array is suitable for further serialization (e.g. using `json_encode`).
See also section on [error handling](error-handling/).
```php
$serializableResult = $result->toArray();
```
Returned array contains **data** and **errors** keys, as described by
[GraphQL spec](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 and formatting](error-handling/).
Description of method arguments: Description of method arguments:
@ -33,10 +43,39 @@ rootValue | `mixed` | Any value that represents a root of your data graph. It i
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.<br><br>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. 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.<br><br>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) 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. operationName | `string` | Allows the caller to specify which operation in queryString will be run, in cases where queryString contains multiple top-level operations.
fieldResolver | `callable` | A resolver function to use when one is not provided by the schema. If not provided, the [default field resolver is used](data-fetching/#default-field-resolver).
validationRules | `array` | A set of rules for query validation step. Default value is all available rules. Empty array would allow to skip query validation (may be convenient for persisted queries which are validated before persisting and assumed valid during execution)
promiseAdapter | `GraphQL\Executir\Promise\PromiseAdapter` | Adapter for async-enabled PHP platforms like ReactPHP (read about [async platforms integration](data-fetching/#async-php))
# Execution Result
```php
namespace GraphQL\Executor;
class ExecutionResult
{
/**
* @var array
*/
public $data;
/**
* @var GraphQL\Error\Error[]
*/
public $errors;
/**
*
*/
public setErrorsHandler(callable $errorsHandler);
public setErrorFormatter(callable $errorsHandler);
}
```
# Parsing # Parsing
Following reading describes implementation details of query execution process. It may clarify some 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 internals of GraphQL but is not required to use it. Feel free to skip to next section
on [Error Handling](error-handling/) for essentials. on [Error Handling](error-handling/) for essentials.
TODOC TODOC
@ -46,3 +85,21 @@ TODOC
# Executing # Executing
TODOC TODOC
# Errors explained
There are 3 types of errors in GraphQL:
- **Syntax**: query has invalid syntax and could not be parsed;
- **Validation**: query is incompatible with type system (e.g. unknown field is requested);
- **Execution**: occurs when some field resolver throws (or returns unexpected value).
Obviously when **Syntax** or **Validation** error is detected - process is interrupted and query is not
executed.
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 registered.
If exception is thrown in non-null field - error bubbles up to first nullable field. This nullable field is
replaced with `null` and error entry is added to response. If all fields up to the root are non-null -
**data** entry will be removed from response and only **errors** key will be presented.

View File

@ -88,12 +88,14 @@ $variableValues = isset($input['variables']) ? $input['variables'] : null;
try { try {
$rootValue = ['prefix' => 'You said: ']; $rootValue = ['prefix' => 'You said: '];
$result = GraphQL::execute($schema, $query, $rootValue, null, $variableValues); $result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variableValues);
} catch (\Exception $e) { } catch (\Exception $e) {
$result = [ $result = [
'error' => [ 'errors' => [
[
'message' => $e->getMessage() 'message' => $e->getMessage()
] ]
]
]; ];
} }
header('Content-Type: application/json; charset=UTF-8'); header('Content-Type: application/json; charset=UTF-8');

View File

@ -2,16 +2,18 @@
Schema is a container of your type hierarchy, which accepts root types in constructor and provides Schema is a container of your type hierarchy, which accepts root types in constructor and provides
methods for receiving information about your types to internal GrahpQL tools. methods for receiving information about your types to internal GrahpQL tools.
In **graphql-php** schema is an instance of `GraphQL\Type\Schema` which accepts configuration array In **graphql-php** schema is an instance of [`GraphQL\Type\Schema`](/reference/#graphqltypeschema)
in constructor: which accepts configuration array in constructor:
```php ```php
use GraphQL\Type\Schema;
$schema = new Schema([ $schema = new Schema([
'query' => $queryType, 'query' => $queryType,
'mutation' => $mutationType, 'mutation' => $mutationType,
]); ]);
``` ```
See possible constructor options [below](#configuration-options) See possible constructor options [below](#configuration-options).
# Query and Mutation types # Query and Mutation types
Schema consists of two root types: Schema consists of two root types:
@ -76,7 +78,8 @@ Field names of Mutation type are usually verbs and they almost always have argum
with complex input values (see [Input Types](input-types/) for details). with complex input values (see [Input Types](input-types/) for details).
# Configuration Options # Configuration Options
Schema constructor expects an array with following options: Schema constructor expects an instance of [`GraphQL\Type\SchemaConfig`](/reference/#graphqltypeschemaconfig)
or an array with following options:
Option | Type | Notes Option | Type | Notes
------------ | -------- | ----- ------------ | -------- | -----