graphql-php/data-fetching/index.html
2018-11-27 20:10:17 +07:00

485 lines
16 KiB
HTML
Executable File

<!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">
<link rel="shortcut icon" href="../img/favicon.ico">
<title>Fetching Data - graphql-php</title>
<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="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css">
<script>
// Current page data
var mkdocs_page_name = "Fetching Data";
var mkdocs_page_input_path = "data-fetching.md";
var mkdocs_page_url = null;
</script>
<script src="../js/jquery-2.1.1.min.js" defer></script>
<script src="../js/modernizr-2.8.3.min.js" defer></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</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" title="Type search term here" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<ul class="current">
<li class="toctree-l1">
<a class="" href="..">About</a>
</li>
<li class="toctree-l1">
<a class="" href="../getting-started/">Getting Started</a>
</li>
<li class="toctree-l1">
<a class="" href="../complementary-tools/">Complementary Tools</a>
</li>
<li class="toctree-l1">
<span class="caption-text">Type Definitions</span>
<ul class="subnav">
<li class="">
<a class="" href="../type-system/">Introduction</a>
</li>
<li class="">
<a class="" href="../type-system/object-types/">Object Types</a>
</li>
<li class="">
<a class="" href="../type-system/scalar-types/">Scalar Types</a>
</li>
<li class="">
<a class="" href="../type-system/enum-types/">Enumeration Types</a>
</li>
<li class="">
<a class="" href="../type-system/lists-and-nonnulls/">Lists and Non-Null</a>
</li>
<li class="">
<a class="" href="../type-system/interfaces/">Interfaces</a>
</li>
<li class="">
<a class="" href="../type-system/unions/">Unions</a>
</li>
<li class="">
<a class="" href="../type-system/input-types/">Mutations and Input Types</a>
</li>
<li class="">
<a class="" href="../type-system/directives/">Directives</a>
</li>
<li class="">
<a class="" href="../type-system/schema/">Schema</a>
</li>
<li class="">
<a class="" href="../type-system/type-language/">Using Type Language</a>
</li>
</ul>
</li>
<li class="toctree-l1">
<a class="" href="../executing-queries/">Executing Queries</a>
</li>
<li class="toctree-l1 current">
<a class="current" href="./">Fetching Data</a>
<ul class="subnav">
<li class="toctree-l2"><a href="#overview">Overview</a></li>
<li class="toctree-l2"><a href="#default-field-resolver">Default Field Resolver</a></li>
<li class="toctree-l2"><a href="#default-field-resolver-per-type">Default Field Resolver per Type</a></li>
<li class="toctree-l2"><a href="#solving-n1-problem">Solving N+1 Problem</a></li>
<li class="toctree-l2"><a href="#async-php">Async PHP</a></li>
</ul>
</li>
<li class="toctree-l1">
<a class="" href="../error-handling/">Handling Errors</a>
</li>
<li class="toctree-l1">
<a class="" href="../security/">Security</a>
</li>
<li class="toctree-l1">
<a class="" href="../how-it-works/">How it works</a>
</li>
<li class="toctree-l1">
<a class="" href="../reference/">Class Reference</a>
</li>
</ul>
</div>
&nbsp;
</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> &raquo;</li>
<li>Fetching Data</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main">
<div class="section">
<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 the GraphQL query to PHP array, <strong>graphql-php</strong> traverses query fields (using depth-first algorithm) and
runs special <strong>resolve</strong> function on each field. This <strong>resolve</strong> function is provided by you as a part of
<a href="../type-system/object-types/#field-configuration-options">field definition</a> or <a href="../executing-queries/#overview">query execution call</a>.</p>
<p>Result returned by <strong>resolve</strong> function is directly included in the 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 a Schema that can fulfill it. On the very top level the Schema contains Query type:</p>
<pre><code class="php">&lt;?php
use GraphQL\Type\Definition\ObjectType;
$queryType = new ObjectType([
'name' =&gt; 'Query',
'fields' =&gt; [
'lastStory' =&gt; [
'type' =&gt; $blogStoryType,
'resolve' =&gt; function() {
return [
'id' =&gt; 1,
'title' =&gt; 'Example blog post',
'authorId' =&gt; 1
];
}
]
]
]);
</code></pre>
<p>As we see field <strong>lastStory</strong> has <strong>resolve</strong> function that is responsible for fetching data.</p>
<p>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.</p>
<p>Since <strong>lastStory</strong> is of composite type <strong>BlogStory</strong> this result is passed down to fields of this type:</p>
<pre><code class="php">&lt;?php
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
$blogStoryType = new ObjectType([
'name' =&gt; 'BlogStory',
'fields' =&gt; [
'author' =&gt; [
'type' =&gt; $userType,
'resolve' =&gt; function($blogStory) {
$users = [
1 =&gt; [
'id' =&gt; 1,
'name' =&gt; 'Smith'
],
2 =&gt; [
'id' =&gt; 2,
'name' =&gt; 'Anderson'
]
];
return $users[$blogStory['authorId']];
}
],
'title' =&gt; [
'type' =&gt; Type::string()
]
]
]);
</code></pre>
<p>Here <strong>$blogStory</strong> is the array returned by <strong>lastStory</strong> field above. </p>
<p>Again: in the real-world applications you would fetch user data from data store by <strong>authorId</strong> 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 <strong>title</strong> has no <strong>resolve</strong> option. How is it resolved?</p>
<p>There is a default resolver for all fields. When you define your own <strong>resolve</strong> 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">&lt;?php
function defaultFieldResolver($source, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{
$fieldName = $info-&gt;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-&gt;{$fieldName})) {
$property = $source-&gt;{$fieldName};
}
}
return $property instanceof Closure ? $property($source, $args, $context, $info) : $property;
}
</code></pre>
<p>As you see it returns value by key (for arrays) or property (for objects).
If the value is not set - it returns <strong>null</strong>.</p>
<p>To override the default resolver, pass it as an argument of <a href="../executing-queries/">executeQuery</a> call.</p>
<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">&lt;?php
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
$userType = new ObjectType([
'name' =&gt; 'User',
'fields' =&gt; [
'name' =&gt; Type::string(),
'email' =&gt; Type::string()
],
'resolveField' =&gt; function(User $user, $args, $context, ResolveInfo $info) {
switch ($info-&gt;fieldName) {
case 'name':
return $user-&gt;getName();
case 'email':
return $user-&gt;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>
<p>Since: 0.9.0</p>
<p>One of the most annoying problems with data fetching is a so-called
<a href="https://secure.phabricator.com/book/phabcontrib/article/n_plus_one/">N+1 problem</a>. <br>
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 the 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 a later stage
when one batched query could be executed instead of 10 distinct queries.</p>
<p>Here is an example of <strong>BlogStory</strong> resolver for field <strong>author</strong> that uses deferring:</p>
<pre><code class="php">&lt;?php
'resolve' =&gt; function($blogStory) {
MyUserBuffer::add($blogStory['authorId']);
return new GraphQL\Deferred(function () use ($blogStory) {
MyUserBuffer::loadBuffered();
return MyUserBuffer::get($blogStory['authorId']);
});
}
</code></pre>
<p>In this example, we fill up the 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 closures wrapped by <code>GraphQL\Deferred</code> which in turn load all buffered
ids once (using SQL IN(?), Redis MGET or other similar tools) and returns final field value.</p>
<p>Originally this approach was advocated by Facebook in their <a href="https://github.com/facebook/dataloader">Dataloader</a>
project. This solution enables very interesting optimizations at no cost. Consider the following query:</p>
<pre><code class="graphql">{
topStories(limit: 10) {
author {
email
}
}
category {
stories(limit: 10) {
author {
email
}
}
}
}
</code></pre>
<p>Even though <strong>author</strong> 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.</p>
<h1 id="async-php">Async PHP</h1>
<p>Since: 0.10.0 (version 0.9.0 had slightly different API which still works, but is deprecated)</p>
<p>If your project runs in an 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 some 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 start using this feature, switch facade method for query execution from
<strong>executeQuery</strong> to <strong>promiseToExecute</strong>:</p>
<pre><code class="php">&lt;?php
use GraphQL\GraphQL;
use GraphQL\Executor\ExecutionResult;
$promise = GraphQL::promiseToExecute(
$promiseAdapter,
$schema,
$queryString,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null,
$fieldResolver = null,
$validationRules = null
);
$promise-&gt;then(function(ExecutionResult $result) {
return $result-&gt;toArray();
});
</code></pre>
<p>Where <strong>$promiseAdapter</strong> is an instance of:</p>
<ul>
<li>
<p>For <a href="https://github.com/reactphp/react">ReactPHP</a> (requires <strong>react/promise</strong> as composer dependency): <br>
<code>GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter</code></p>
</li>
<li>
<p>Other platforms: write your own class implementing interface: <br>
<a href="../reference/#graphqlexecutorpromisepromiseadapter"><code>GraphQL\Executor\Promise\PromiseAdapter</code></a>. </p>
</li>
</ul>
<p>Then your <strong>resolve</strong> functions should return promises of your platform instead of <code>GraphQL\Deferred</code>s.</p>
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
<a href="../error-handling/" class="btn btn-neutral float-right" title="Handling Errors">Next <span class="icon icon-circle-arrow-right"></span></a>
<a href="../executing-queries/" class="btn btn-neutral" title="Executing Queries"><span class="icon icon-circle-arrow-left"></span> Previous</a>
</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">
<span><a href="../executing-queries/" style="color: #fcfcfc;">&laquo; Previous</a></span>
<span style="margin-left: 15px"><a href="../error-handling/" style="color: #fcfcfc">Next &raquo;</a></span>
</span>
</div>
<script>var base_url = '..';</script>
<script src="../js/theme.js" defer></script>
<script src="../search/main.js" defer></script>
</body>
</html>