graphql-php/docs/data-fetching.md

275 lines
8.0 KiB
Markdown
Raw Normal View History

2016-12-14 16:19:12 +03:00
# Overview
GraphQL is data-storage agnostic. You can use any underlying data storage engine, including SQL or NoSQL database,
plain files or in-memory data structures.
2017-08-20 18:10:37 +03:00
In order to convert the GraphQL query to PHP array, **graphql-php** traverses query fields (using depth-first algorithm) and
2017-08-17 22:56:22 +03:00
runs special **resolve** function on each field. This **resolve** function is provided by you as a part of
[field definition](type-system/object-types.md#field-configuration-options) or [query execution call](executing-queries.md#overview).
2016-12-14 16:19:12 +03:00
2017-08-20 18:10:37 +03:00
Result returned by **resolve** function is directly included in the response (for scalars and enums)
2016-12-14 16:19:12 +03:00
or passed down to nested fields (for objects).
Let's walk through an example. Consider following GraphQL query:
```graphql
{
lastStory {
title
author {
name
}
}
}
```
2017-08-17 22:56:22 +03:00
We need a Schema that can fulfill it. On the very top level the Schema contains Query type:
2016-12-14 16:19:12 +03:00
```php
2017-08-20 18:10:37 +03:00
<?php
use GraphQL\Type\Definition\ObjectType;
2016-12-14 16:19:12 +03:00
$queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'lastStory' => [
'type' => $blogStoryType,
'resolve' => function() {
return [
'id' => 1,
'title' => 'Example blog post',
'authorId' => 1
];
}
]
]
]);
```
2017-08-17 22:56:22 +03:00
As we see field **lastStory** has **resolve** function that is responsible for fetching data.
2016-12-14 16:19:12 +03:00
2017-08-20 18:10:37 +03:00
In our example, we simply return array value, but in the real-world application you would query
your database/cache/search index and return the result.
2016-12-14 16:19:12 +03:00
2017-08-17 22:56:22 +03:00
Since **lastStory** is of composite type **BlogStory** this result is passed down to fields of this type:
2016-12-14 16:19:12 +03:00
```php
2017-08-20 18:10:37 +03:00
<?php
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
2016-12-14 16:19:12 +03:00
$blogStoryType = new ObjectType([
'name' => 'BlogStory',
'fields' => [
'author' => [
'type' => $userType,
'resolve' => function($blogStory) {
$users = [
1 => [
'id' => 1,
'name' => 'Smith'
],
2 => [
'id' => 2,
'name' => 'Anderson'
]
];
return $users[$blogStory['authorId']];
}
],
'title' => [
'type' => Type::string()
]
]
]);
```
2017-08-17 22:56:22 +03:00
Here **$blogStory** is the array returned by **lastStory** field above.
2016-12-14 16:19:12 +03:00
2017-08-20 18:10:37 +03:00
Again: in the real-world applications you would fetch user data from data store 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
2016-12-14 16:19:12 +03:00
to nested resolvers.
2017-08-17 22:56:22 +03:00
But then the question appears - field **title** has no **resolve** option. How is it resolved?
2016-12-14 16:19:12 +03:00
2017-08-17 22:56:22 +03:00
There is a default resolver for all fields. When you define your own **resolve** function
2016-12-14 16:19:12 +03:00
for a field you simply override this default resolver.
# Default Field Resolver
**graphql-php** provides following default field resolver:
```php
2017-08-20 18:10:37 +03:00
<?php
function defaultFieldResolver($objectValue, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
if (is_array($objectValue) || $objectValue instanceof \ArrayAccess) {
if (isset($objectValue[$fieldName])) {
$property = $objectValue[$fieldName];
}
} elseif (is_object($objectValue)) {
if (isset($objectValue->{$fieldName})) {
$property = $objectValue->{$fieldName};
}
2016-12-14 16:19:12 +03:00
}
return $property instanceof Closure
? $property($objectValue, $args, $context, $info)
: $property;
}
2016-12-14 16:19:12 +03:00
```
2017-08-17 22:56:22 +03:00
As you see it returns value by key (for arrays) or property (for objects).
2017-08-20 18:10:37 +03:00
If the value is not set - it returns **null**.
2016-12-14 16:19:12 +03:00
To override the default resolver, pass it as an argument of [executeQuery](executing-queries.md) call.
2016-12-14 16:19:12 +03:00
# Default Field Resolver per Type
Sometimes it might be convenient to set default field resolver per type. You can do so by providing
[resolveField option in type config](type-system/object-types.md#configuration-options). For example:
2016-12-14 16:19:12 +03:00
```php
2017-08-20 18:10:37 +03:00
<?php
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
2016-12-14 16:19:12 +03:00
$userType = new ObjectType([
'name' => 'User',
'fields' => [
'name' => Type::string(),
'email' => Type::string()
],
'resolveField' => function(User $user, $args, $context, ResolveInfo $info) {
switch ($info->fieldName) {
case 'name':
return $user->getName();
case 'email':
return $user->getEmail();
default:
return null;
}
}
]);
```
Keep in mind that **field resolver** has precedence over **default field resolver per type** which in turn
has precedence over **default field resolver**.
# Solving N+1 Problem
2016-12-15 15:27:43 +03:00
Since: 0.9.0
2017-08-17 22:56:22 +03:00
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/). <br>
2016-12-14 16:19:12 +03:00
Consider following GraphQL query:
```
{
topStories(limit: 10) {
title
author {
name
email
}
}
}
```
2017-08-20 18:10:37 +03:00
Naive field resolution process would require up to 10 calls to the underlying data store to fetch authors for all 10 stories.
2016-12-14 16:19:12 +03:00
2017-08-20 18:10:37 +03:00
**graphql-php** provides tools to mitigate this problem: it allows you to defer actual field resolution to a later stage
2016-12-14 16:19:12 +03:00
when one batched query could be executed instead of 10 distinct queries.
2017-08-20 18:10:37 +03:00
Here is an example of **BlogStory** resolver for field **author** that uses deferring:
2016-12-14 16:19:12 +03:00
```php
2017-08-20 18:10:37 +03:00
<?php
2016-12-14 16:19:12 +03:00
'resolve' => function($blogStory) {
MyUserBuffer::add($blogStory['authorId']);
return new GraphQL\Deferred(function () use ($blogStory) {
MyUserBuffer::loadBuffered();
2016-12-14 16:19:12 +03:00
return MyUserBuffer::get($blogStory['authorId']);
});
}
```
2017-08-20 18:10:37 +03:00
In this example, we fill up the buffer with 10 author ids first. Then **graphql-php** continues
2016-12-14 16:19:12 +03:00
resolving other non-deferred fields until there are none of them left.
2017-08-20 18:10:37 +03:00
After that, it calls closures wrapped by `GraphQL\Deferred` which in turn load all buffered
ids once (using SQL IN(?), Redis MGET or other similar tools) and returns final field value.
2016-12-14 16:19:12 +03:00
Originally this approach was advocated by Facebook in their [Dataloader](https://github.com/facebook/dataloader)
2017-08-20 18:10:37 +03:00
project. This solution enables very interesting optimizations at no cost. Consider the following query:
2016-12-14 16:19:12 +03:00
2017-08-20 18:10:37 +03:00
```graphql
2016-12-14 16:19:12 +03:00
{
topStories(limit: 10) {
author {
email
}
}
category {
stories(limit: 10) {
author {
email
}
}
}
}
```
2017-08-20 18:10:37 +03:00
Even though **author** field is located on different levels of the query - it can be buffered in the same buffer.
In this example, only one query will be executed for all story authors comparing to 20 queries
in a naive implementation.
2016-12-14 16:19:12 +03:00
# Async PHP
2017-08-20 18:10:37 +03:00
Since: 0.10.0 (version 0.9.0 had slightly different API which still works, but is deprecated)
2017-08-20 18:10:37 +03:00
If your project runs in an environment that supports async operations
2017-08-17 22:56:22 +03:00
(like HHVM, ReactPHP, Icicle.io, appserver.io, PHP threads, etc)
you can leverage the power of your platform to resolve some fields asynchronously.
2016-12-14 16:19:12 +03:00
The only requirement: your platform must support the concept of Promises compatible with
[Promises A+](https://promisesaplus.com/) specification.
2017-08-17 22:56:22 +03:00
To start using this feature, switch facade method for query execution from
**executeQuery** to **promiseToExecute**:
2016-12-14 16:19:12 +03:00
2017-08-17 22:56:22 +03:00
```php
2017-08-20 18:10:37 +03:00
<?php
use GraphQL\GraphQL;
use GraphQL\Executor\ExecutionResult;
2017-08-17 22:56:22 +03:00
$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();
});
```
2016-12-14 16:19:12 +03:00
2017-08-17 22:56:22 +03:00
Where **$promiseAdapter** is an instance of:
2016-12-14 16:19:12 +03:00
2017-08-17 22:56:22 +03:00
* For [ReactPHP](https://github.com/reactphp/react) (requires **react/promise** as composer dependency): <br>
`GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter`
2016-12-14 16:19:12 +03:00
2017-08-17 22:56:22 +03:00
* Other platforms: write your own class implementing interface: <br>
2017-08-20 18:10:37 +03:00
[`GraphQL\Executor\Promise\PromiseAdapter`](reference.md#graphqlexecutorpromisepromiseadapter).
2016-12-14 16:19:12 +03:00
Then your **resolve** functions should return promises of your platform instead of `GraphQL\Deferred`s.