获取数据
综述
GraphQL 是不可以进行数据存储的。你可以使用任何基础数据存储引擎,包括 SQL 或 NoSQL 数据库,纯文件或内存数据结构。
为了将 GraphQL 查询转换为 PHP 数组,graphql-php 遍历查询字段(使用深度优先算法),并在每个字段上运行特殊的 resolve 函数。此 resolve 函数由你提供,作为 字段定义 或 查询执行调用的一部分。
由 resolve 函数返回的结果直接包含在响应中(对于标量和枚举)或传递给嵌套字段(用于对象)。
我们来看一个例子。考虑以下 GraphQL 查询:
{
lastStory {
title
author {
name
}
}
}
我们需要一个可以实现它的模式。在最顶层,Schema 包含查询类型:
<?php
use GraphQL\Type\Definition\ObjectType;
$queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'lastStory' => [
'type' => $blogStoryType,
'resolve' => function() {
return [
'id' => 1,
'title' => 'Example blog post',
'authorId' => 1
];
}
]
]
]);
正如我们看到的字段 lastStory 具有 resolve 函数,负责提取数据。
在我们的例子中,我们只是返回数组值,但在实际应用程序中,你将查询 数据库 / 缓存 / 搜索索引 并返回结果。
由于 lastStory 是复合类型 BlogStory,因此此结果传递给此类型的字段:
<?php
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
$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()
]
]
]);
这里的 $blogStory 是上面 lastStory 字段返回的数组。
同样,在真实应用程序中,你将通过 authorId 从数据存储中获取用户数据并将其返回。另外,请注意,你不必返回数组。你可以返回任何值,graphql-php 会将它传递给嵌套的解析器。
但问题出现了 - 字段 title 没有 resolve 选项。它将如何解决呢?
所有字段都有一个默认解析器。当你为一个字段定义你自己的 resolve 函数时,你只需重写这个默认的解析器。
默认字段解析器
graphql-php 提供以下默认字段解析器:
<?php
function defaultFieldResolver($source, $args, $context, \GraphQL\Type\Definition\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;
}
如你所见,它将按键返回值(用于数组)或属性(用于对象)。如果没有设置值 - 它将返回 null 。
为了重写默认解析器,需要将其作为 executeQuery 的参数来调用。
每种类型的默认字段解析器
有时,为每种类型设置默认字段解析器可能会很方便。你可以通过在
config 类型中的 resolveField 选项 来完成此操作 。例如:
<?php
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
$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;
}
}
]);
请记住,字段解析器 优先于 每种类型的默认字段解析器,而 解析器 优先于 默认字段解析器。
解决 N + 1 问题
从以下版本开始: 0.9.0
数据读取中最令人讨厌的问题之一是所谓的 N+1 问题。
考虑一下以下的 GraphQL 查询:
{
topStories(limit: 10) {
title
author {
name
email
}
}
}
简单的字段解析过程将需要多达 10 次调用底层数据存储来获取所有 10 个故事的作者。
graphql-php 提供了一些工具来缓解这个问题:它允许你将实际的字段解析推迟到一个批处理查询而不是执行 10 个不同的查询。
以下是使用推迟的字段 author 的 BlogStory 解析器示例:
<?php
'resolve' => function($blogStory) {
MyUserBuffer::add($blogStory['authorId']);
return new GraphQL\Deferred(function () use ($blogStory) {
MyUserBuffer::loadBuffered();
return MyUserBuffer::get($blogStory['authorId']);
});
}
在这个例子中,我们先用 10 个作者 id 填充缓冲区。然后 graphql-php 继续解决其他非延迟字段,直到没有剩余。
之后,它会调用封装,GraphQL\Deferred
然后依次加载所有缓冲的ID(使用SQL IN(?),Redis MGET 或其他类似工具)并返回最终字段值。
Facebook 最初在 Dataloader 项目中提出了这种方法。该解决方案可以没有成本的进行非常有趣的优化。考虑以下查询:
{
topStories(limit: 10) {
author {
email
}
}
category {
stories(limit: 10) {
author {
email
}
}
}
}
即使 author 段位于查询的不同级别 - 它可以缓存在同一个缓冲区中。在这个例子中,只有一个查询将被执行,所有的故事作者与简单的 20 个查询进行相互比较。
异步 PHP
从 0.10.0 版本开始(0.9.0 版本 API 虽略有不同,但仍可使用,不推荐使用旧版本)
如果项目环境支持异步操作(如:HHVM,ReactPHP,Icicle.io, appserver.io, PHP 线程等),你可以使用异步字段解析来提升服务能力。
必要性:
服务必须支持 Promises A+ 中所表明的 Promises 理念。
开始使用这一特性,需要将执行查询的 facade
方法从 executeQuery 转为 promiseToExecute:
<?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->then(function(ExecutionResult $result) {
return $result->toArray();
});
此处的 $promiseAdapter 是一个实例:
-
如 ReactPHP (加载 react/promise 作为 compose 依赖):
GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter
-
其他平台: 编写自己实现接口的类:
GraphQL\Executor\Promise\PromiseAdap...
.
然后 resolve 解析器将返回服务自身的 Promises 来替代 GraphQL\Deferred
。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。