获取数据

未匹配的标注

综述

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 个不同的查询。

以下是使用推迟的字段 authorBlogStory 解析器示例:

<?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 是一个实例:

然后 resolve 解析器将返回服务自身的 Promises 来替代 GraphQL\Deferred

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/graphql-php/dat...

译文地址:https://learnku.com/docs/graphql-php/dat...

上一篇 下一篇
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
贡献者:5
讨论数量: 0
发起讨论 查看所有版本


暂无话题~