2016-11-08 16:41:38 +03:00
<!DOCTYPE html>
<!-- [if IE 8]><html class="no - js lt - ie9" lang="en" > <![endif] -->
<!-- [if gt IE 8]><! --> < html class = "no-js" lang = "en" > <!-- <![endif] -->
< head >
< meta charset = "utf-8" >
< meta http-equiv = "X-UA-Compatible" content = "IE=edge" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Fetching Data - graphql-php< / title >
< link rel = "shortcut icon" href = "../img/favicon.ico" >
< link href = 'https://fonts.googleapis.com/css?family=Lato:400,700|Roboto+Slab:400,700|Inconsolata:400,700' rel = 'stylesheet' type = 'text/css' >
< link rel = "stylesheet" href = "../css/theme.css" type = "text/css" / >
< link rel = "stylesheet" href = "../css/theme_extra.css" type = "text/css" / >
< link rel = "stylesheet" href = "../css/highlight.css" >
< script >
// Current page data
var mkdocs_page_name = "Fetching Data";
var mkdocs_page_input_path = "data-fetching.md";
var mkdocs_page_url = "/data-fetching/";
< / script >
< script src = "../js/jquery-2.1.1.min.js" > < / script >
< script src = "../js/modernizr-2.8.3.min.js" > < / script >
< script type = "text/javascript" src = "../js/highlight.pack.js" > < / script >
< script src = "../js/theme.js" > < / script >
< / head >
< body class = "wy-body-for-nav" role = "document" >
< div class = "wy-grid-for-nav" >
< nav data-toggle = "wy-nav-shift" class = "wy-nav-side stickynav" >
< div class = "wy-side-nav-search" >
< a href = ".." class = "icon icon-home" > graphql-php< / a >
< div role = "search" >
< form id = "rtd-search-form" class = "wy-form" action = "../search.html" method = "get" >
< input type = "text" name = "q" placeholder = "Search docs" / >
< / form >
< / div >
< / div >
< div class = "wy-menu wy-menu-vertical" data-spy = "affix" role = "navigation" aria-label = "main navigation" >
< ul class = "current" >
< li >
< li class = "toctree-l1 " >
< a class = "" href = ".." > About< / a >
< / li >
< li >
< li >
< li class = "toctree-l1 " >
< a class = "" href = "../getting-started/" > Getting Started< / a >
< / li >
< li >
< li >
< ul class = "subnav" >
< li > < span > Type System< / span > < / li >
< li class = "toctree-l1 " >
< a class = "" href = "../type-system/" > Introduction< / a >
< / li >
< li class = "toctree-l1 " >
< a class = "" href = "../type-system/object-types/" > Object Types< / a >
< / li >
< li class = "toctree-l1 " >
< a class = "" href = "../type-system/scalar-types/" > Scalar Types< / a >
< / li >
< li class = "toctree-l1 " >
< a class = "" href = "../type-system/enum-types/" > Enumeration Types< / a >
< / li >
< li class = "toctree-l1 " >
< a class = "" href = "../type-system/lists-and-nonnulls/" > Lists and Non-Null< / a >
< / li >
< li class = "toctree-l1 " >
< a class = "" href = "../type-system/interfaces/" > Interfaces< / a >
< / li >
< li class = "toctree-l1 " >
< a class = "" href = "../type-system/unions/" > Unions< / a >
< / li >
< li class = "toctree-l1 " >
< a class = "" href = "../type-system/input-types/" > Input Types< / a >
< / li >
< li class = "toctree-l1 " >
< a class = "" href = "../type-system/directives/" > Directives< / a >
< / li >
< li class = "toctree-l1 " >
< a class = "" href = "../type-system/schema/" > Schema< / a >
< / li >
< / ul >
< li >
< li >
< li class = "toctree-l1 " >
< a class = "" href = "../executing-queries/" > Executing Queries< / a >
< / li >
< li >
< li >
< li class = "toctree-l1 current" >
< a class = "current" href = "./" > Fetching Data< / a >
< ul >
2016-12-14 16:23:08 +03:00
< li class = "toctree-l3" > < a href = "#overview" > Overview< / a > < / li >
< li class = "toctree-l3" > < a href = "#default-field-resolver" > Default Field Resolver< / a > < / li >
< li class = "toctree-l3" > < a href = "#default-field-resolver-per-type" > Default Field Resolver per Type< / a > < / li >
< li class = "toctree-l3" > < a href = "#solving-n1-problem" > Solving N+1 Problem< / a > < / li >
< li class = "toctree-l3" > < a href = "#async-php" > Async PHP< / a > < / li >
2016-11-08 16:41:38 +03:00
< / ul >
< / li >
< li >
< li >
< li class = "toctree-l1 " >
2016-12-14 16:23:08 +03:00
< a class = "" href = "../error-handling/" > Handling Errors< / a >
2016-11-08 16:41:38 +03:00
< / li >
< li >
< li >
< li class = "toctree-l1 " >
< a class = "" href = "../complementary-tools/" > Complementary Tools< / a >
< / li >
< li >
< / ul >
< / div >
< / nav >
< section data-toggle = "wy-nav-shift" class = "wy-nav-content-wrap" >
< nav class = "wy-nav-top" role = "navigation" aria-label = "top navigation" >
< i data-toggle = "wy-nav-top" class = "fa fa-bars" > < / i >
< a href = ".." > graphql-php< / a >
< / nav >
< div class = "wy-nav-content" >
< div class = "rst-content" >
< div role = "navigation" aria-label = "breadcrumbs navigation" >
< ul class = "wy-breadcrumbs" >
< li > < a href = ".." > Docs< / a > » < / li >
< li > Fetching Data< / li >
< li class = "wy-breadcrumbs-aside" >
< / li >
< / ul >
< hr / >
< / div >
< div role = "main" >
< div class = "section" >
2016-12-14 16:23:08 +03:00
< h1 id = "overview" > Overview< / h1 >
< p > 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.< / p >
< p > In order to convert GraphQL query to PHP array < strong > graphql-php< / strong > traverses query fields (using depth-first algorithm) and
runs special < code > resolve< / code > function on each field. This < code > resolve< / code > function is provided by you as a part of
< a href = "../type-system/object-types/#field-configuration-options" > field definition< / a > .< / p >
< p > Result returned by < code > resolve< / code > function is directly included in response (for scalars and enums)
or passed down to nested fields (for objects).< / p >
< p > Let's walk through an example. Consider following GraphQL query:< / p >
< pre > < code class = "graphql" > {
lastStory {
title
author {
name
}
}
}
< / code > < / pre >
< p > We need Schema that can fulfill it. On the very top level Schema contains Query type:< / p >
< pre > < code class = "php" > $queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'lastStory' => [
'type' => $blogStoryType,
'resolve' => function() {
return [
'id' => 1,
'title' => 'Example blog post',
'authorId' => 1
];
}
]
]
]);
< / code > < / pre >
< p > As we see field < code > lastStory< / code > has < code > resolve< / code > function that is responsible for fetching data.< / p >
< p > In our example we simply return array value, but in real-world application you would query
your database/cache/search index and return result.< / p >
< p > Since < code > lastStory< / code > is of complex type < code > BlogStory< / code > this result is passed down to fields of this type:< / p >
< pre > < code class = "php" > $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()
]
]
]);
< / code > < / pre >
< p > Here < code > $blogStory< / code > is the array returned by < code > lastStory< / code > field above. < / p >
< p > Again: in real-world applications you would fetch user data from datastore by < code > authorId< / code > and return it.
Also note that you don't have to return arrays. You can return any value, < strong > graphql-php< / strong > will pass it untouched
to nested resolvers.< / p >
< p > But then the question appears - field < code > title< / code > has no < code > resolve< / code > option. How is it resolved?< / p >
< p > The answer is: there is default resolver for all fields. When you define your own < code > resolve< / code > function
for a field you simply override this default resolver.< / p >
< h1 id = "default-field-resolver" > Default Field Resolver< / h1 >
< p > < strong > graphql-php< / strong > provides following default field resolver:< / p >
< pre > < code class = "php" > function defaultFieldResolver($source, $args, $context, ResolveInfo $info)
{
$fieldName = $info-> fieldName;
$property = null;
if (is_array($source) || $source instanceof \ArrayAccess) {
if (isset($source[$fieldName])) {
$property = $source[$fieldName];
}
} else if (is_object($source)) {
if (isset($source-> {$fieldName})) {
$property = $source-> {$fieldName};
}
}
return $property instanceof \Closure ? $property($source, $args, $context) : $property;
}
< / code > < / pre >
< p > As you see it returns value by key (for arrays) or property (for objects). If value is not set - it returns < code > null< / code > .< / p >
< p > To override default resolver - use:< / p >
< pre > < code class = "php" > GraphQL\GraphQL::setDefaultFieldResolver($myResolverCallback);
< / code > < / pre >
< h1 id = "default-field-resolver-per-type" > Default Field Resolver per Type< / h1 >
< p > Sometimes it might be convenient to set default field resolver per type. You can do so by providing
< a href = "../type-system/object-types/#configuration-options" > resolveField option in type config< / a > . For example:< / p >
< pre > < code class = "php" > $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;
}
}
]);
< / code > < / pre >
< p > Keep in mind that < strong > field resolver< / strong > has precedence over < strong > default field resolver per type< / strong > which in turn
has precedence over < strong > default field resolver< / strong > .< / p >
< h1 id = "solving-n1-problem" > Solving N+1 Problem< / h1 >
2016-12-17 01:21:05 +03:00
< p > Since: 0.9.0< / p >
2016-12-14 16:23:08 +03:00
< p > One of the most annoying problems with data fetching is so-called < a href = "https://secure.phabricator.com/book/phabcontrib/article/n_plus_one/" > N+1 problem< / a > .< / p >
< p > Consider following GraphQL query:< / p >
< pre > < code > {
topStories(limit: 10) {
title
author {
name
email
}
}
}
< / code > < / pre >
< p > Naive field resolution process would require up to 10 calls to underlying data store to fetch authors for all 10 stories.< / p >
< p > < strong > graphql-php< / strong > provides tools to mitigate this problem: it allows you to defer actual field resolution to later stage
when one batched query could be executed instead of 10 distinct queries.< / p >
< p > Here is an example of < code > BlogStory< / code > resolver for field < code > author< / code > that uses deferring:< / p >
< pre > < code class = "php" > 'resolve' => function($blogStory) {
MyUserBuffer::add($blogStory['authorId']);
return new GraphQL\Deferred(function () use ($blogStory) {
2016-12-17 01:21:05 +03:00
MyUserBuffer::loadBuffered();
2016-12-14 16:23:08 +03:00
return MyUserBuffer::get($blogStory['authorId']);
});
}
< / code > < / pre >
< p > In this example we fill up buffer with 10 author ids first. Then < strong > graphql-php< / strong > continues
resolving other non-deferred fields until there are none of them left.< / p >
< p > After that it calls < code > Closures< / code > wrapped by < code > GraphQL\Deferred< / code > which in turn load all buffered
ids once (using SQL IN(?), Redis MGET or other similar tools) and return final field value.< / p >
< p > Originally this approach was advocated by Facebook in their < a href = "https://github.com/facebook/dataloader" > Dataloader< / a >
project.< / p >
< p > This solution enables very interesting optimizations at no cost. Consider following query:< / p >
< pre > < code > {
topStories(limit: 10) {
author {
email
}
}
category {
stories(limit: 10) {
author {
email
}
}
}
}
< / code > < / pre >
< p > Even if < code > author< / code > field is located on different levels of 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 naive implementation.< / p >
< h1 id = "async-php" > Async PHP< / h1 >
2016-12-17 01:21:05 +03:00
< p > Since: 0.9.0< / p >
2016-12-14 16:23:08 +03:00
< p > If your project runs in environment that supports async operations
(like < code > HHVM< / code > , < code > ReactPHP< / code > , < code > Icicle.io< / code > , < code > appserver.io< / code > < code > PHP threads< / code > , etc) you can leverage
the power of your platform to resolve fields asynchronously.< / p >
< p > The only requirement: your platform must support the concept of Promises compatible with
< a href = "https://promisesaplus.com/" > Promises A+< / a > specification.< / p >
< p > To enable async support - set adapter for promises:< / p >
< pre > < code > GraphQL\GraphQL::setPromiseAdapter($adapter);
< / code > < / pre >
< p > Where < code > $adapter< / code > is an instance of class implementing < code > GraphQL\Executor\Promise\PromiseAdapter< / code > interface.< / p >
< p > Then in your < code > resolve< / code > functions you should return < code > Promises< / code > of your platform instead of
< code > GraphQL\Deferred< / code > instances.< / p >
< p > Platforms supported out of the box:< / p >
< ul >
< li > < a href = "https://github.com/reactphp/react" > ReactPHP< / a > (requires < strong > react/promise< / strong > as composer dependency):
< code > GraphQL\GraphQL::setPromiseAdapter(new GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter());< / code > < / li >
< / ul >
< p > To integrate other platform - implement < code > GraphQL\Executor\Promise\PromiseAdapter< / code > interface. < / p >
2016-11-08 16:41:38 +03:00
< / div >
< / div >
< footer >
< div class = "rst-footer-buttons" role = "navigation" aria-label = "footer navigation" >
2016-12-14 16:23:08 +03:00
< a href = "../error-handling/" class = "btn btn-neutral float-right" title = "Handling Errors" > Next < span class = "icon icon-circle-arrow-right" > < / span > < / a >
2016-11-08 16:41:38 +03:00
2016-12-14 16:23:08 +03:00
< a href = "../executing-queries/" class = "btn btn-neutral" title = "Executing Queries" > < span class = "icon icon-circle-arrow-left" > < / span > Previous< / a >
2016-11-08 16:41:38 +03:00
< / div >
< hr / >
< div role = "contentinfo" >
<!-- Copyright etc -->
< / div >
Built with < a href = "http://www.mkdocs.org" > MkDocs< / a > using a < a href = "https://github.com/snide/sphinx_rtd_theme" > theme< / a > provided by < a href = "https://readthedocs.org" > Read the Docs< / a > .
< / footer >
< / div >
< / div >
< / section >
< / div >
< div class = "rst-versions" role = "note" style = "cursor: pointer" >
< span class = "rst-current-version" data-toggle = "rst-current-version" >
2016-12-14 16:23:08 +03:00
< span > < a href = "../executing-queries/" style = "color: #fcfcfc;" > « Previous< / a > < / span >
2016-11-08 16:41:38 +03:00
2016-12-14 16:23:08 +03:00
< span style = "margin-left: 15px" > < a href = "../error-handling/" style = "color: #fcfcfc" > Next » < / a > < / span >
2016-11-08 16:41:38 +03:00
< / span >
< / div >
< / body >
< / html >