GraphQL
这是基于 JavaScript 参考实现的GraphQL规范的PHP 实现。
相关项目
要求
- PHP版本> = 7.1
- EXT-MBSTRING
安装
运行以下命令以通过Composer安装程序包:
composer require digiaonline/graphql
例
下面是一个简单的示例,演示如何从GraphQL模式文件构建可执行模式,该文件包含
星球大战主题模式的Schema
定义语言(SDL)(Schema
定义本身,见下文)。在
此示例中,我们使用该SDL构建可执行模式,并使用它来查询英雄的名称。该
查询的结果是一个关联数组,其结构类似于我们运行的查询。
use Digia\GraphQL\Language\FileSourceBuilder;
use function Digia\GraphQL\buildSchema;
use function Digia\GraphQL\graphql;
$sourceBuilder = new FileSourceBuilder(__DIR__ . '/star-wars.graphqls');
$schema = buildSchema($sourceBuilder->build(), [
'Query' => [
'hero' => function ($rootValue, $arguments) {
return getHero($arguments['episode'] ?? null);
},
],
]);
$result = graphql($schema, '
query HeroNameQuery {
hero {
name
}
}');
\print_r($result);
上面的脚本产生以下输出:
Array
(
[data] => Array
(
[hero] => Array
(
[name] => "R2-D2"
)
)
)
此示例中使用的GraphQL架构文件包含以下内容:
schema {
query: Query
}
type Query {
hero(episode: Episode): Character
human(id: String!): Human
droid(id: String!): Droid
}
interface Character {
id: String!
name: String
friends: [Character]
appearsIn: [Episode]
}
type Human implements Character {
id: String!
name: String
friends: [Character]
appearsIn: [Episode]
homePlanet: String
}
type Droid implements Character {
id: String!
name: String
friends: [Character]
appearsIn: [Episode]
primaryFunction: String
}
enum Episode { NEWHOPE, EMPIRE, JEDI }
创建架构
要对GraphQL API执行查询,首先需要定义API的结构。这是
通过创建模式来完成的。有两种方法可以执行此操作,您可以使用SDL执行此操作,也可以通过编程方式执行此操作。
但是,我们强烈建议您使用SDL,因为它更易于使用。要从
SDL 创建可执行Schema
,您需要调用该buildSchema
函数。
该buildSchema
函数有三个参数:
-
$source``Schema
定义(SDL)作为Source
实例 -
$resolverRegistry
关联数组或ResolverRegistry
包含所有解析器的实例 -
$options
用于构建Schema
的选项,其中还包括自定义类型和指令
要创建Source
实例,您可以使用提供的FileSourceBuilder
或MultiFileSourceBuilder
类。
Resolver registry
Resolver registry
本质上是一个简单的映射,其类型名称将作为其键,其对应的解析器
实例将作为其值。对于较小的项目,您可以使用关联数组和lambda函数(即:匿名函数)来定义
Resolver registry
。但是,在较大的项目中,我们建议您实现自己的解析器。您可以
在“ 解析器”(#Resolvers)部分下阅读有关解析器的更多信息。
关联数组示例:
$schema = buildSchema($source, [
'Query' => [
'hero' => function ($rootValue, $arguments) {
return getHero($arguments['episode'] ?? null);
},
],
]);
解析器类示例:
$schema = buildSchema($source, [
'Query' => [
'hero' => new HeroResolver(),
],
]);
解析器中间件
如果您发现自己在多个解析器中编写相同的逻辑,则应考虑使用中间件。解析器
中间件允许您跨多个解析器有效地管理功能。
在中间件示例之前:
// use Digia\GraphQL\Schema\Resolver\ResolverRegistry;
$resolves=new ResolverRegistry([
'Query' => [
'hero' => function ($rootValue, $arguments) {
return getHero($arguments['episode'] ?? null);
},
],
], [
new BeforeMiddleware()
])
$schema = buildSchema($source,$resolves);
class BeforeMiddleware implements ResolverMiddlewareInterface
{
public function resolve(callable $resolveCallback, $rootValue, array $arguments, $context, ResolveInfo $info) {
$newRootValue = $this->doSomethingBefore();
return $resolveCallback($newRootValue, $arguments, $context, $info);
}
}
中间件示例后:
// use Digia\GraphQL\Schema\Resolver\ResolverRegistry;
$resolves=new ResolverRegistry([
'Query' => [
'hero' => function ($rootValue, $arguments) {
return getHero($arguments['episode'] ?? null);
},
],
], [
new AfterMiddleware()
])
$schema = buildSchema($source,$resolves);
class AfterMiddleware implements ResolverMiddlewareInterface
{
public function resolve(callable $resolveCallback, $rootValue, array $arguments, $context, ResolveInfo $info) {
$result = $resolveCallback($rootValue, $arguments, $context, $info);
$this->doSomethingAfter();
return $result;
}
}
解析器中间件可用于许多事情; 例如日志记录,输入清理,性能
测量,授权和缓存。
如果您想了解有关Schema
的更多信息,可以参考规范。
执行
Queries
要对Schema
执行查询,您需要调用该graphql
函数并将其Schema
和要执行的查询传递给它
。您还可以通过更改查询来运行Mutations
和subscriptions
。
$query = '
query HeroNameQuery {
hero {
name
}
}';
$result = graphql($schema, $query);
如果您想了解有关查询的更多信息,可以参考规范。
Resolvers
Schema
中的每个类型都有一个与之关联的解析器(Resolvers
),允许解析实际值。但是,大多数
类型不需要自定义解析器,因为可以使用默认解析器解析它们。通常这些解析器
是lambda
函数(即:匿名函数),但您也可以通过扩展AbstractTypeResolver
或AbstractFieldResolver
来定义自己的解析器。或者自行实现ResolverInterface
。
解析器函数接收四个参数:
-
$rootValue
父对象,在某些情况下可为null
-
$arguments
在查询中提供给该字段的参数 -
$context
传递给每个可以保存重要上下文信息的解析器的值 -
$info
保存与当前查询相关的字段特定信息的值
Lambda函数示例:
function ($rootValue, array $arguments, $context, ResolveInfo $info): string {
return [
'type' => 'Human',
'id' => '1000',
'name' => 'Luke Skywalker',
'friends' => ['1002', '1003', '2000', '2001'],
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
'homePlanet' => 'Tatooine',
];
}
Type解析器示例:
class HumanResolver extends AbstractTypeResolver
{
public function resolveName($rootValue, array $arguments, $context, ResolveInfo $info): string
{
return $rootValue['name'];
}
}
Field解析器示例:
class NameResolver extends AbstractFieldResolver
{
public function resolve($rootValue, array $arguments, $context, ResolveInfo $info): string
{
return $rootValue['name'];
}
}
N + 1问题
解析器函数可以返回“值”,promise或promise数组。
下面的解析器函数说明了如何使用promise来解决N + 1问题,完整的例子可以在
这个测试用例中找到。
$movieType = newObjectType([
'fields' => [
'title' => ['type' => stringType()],
'director' => [
'type' => $directorType,
'resolve' => function ($movie, $args) {
DirectorBuffer::add($movie['directorId']);
return new Promise(function (callable $resolve, callable $reject) use ($movie) {
DirectorBuffer::loadBuffered();
$resolve(DirectorBuffer::get($movie['directorId']));
});
}
]
]
]);
Variables
通过将查询传递给graphql
函数,可以在执行查询时传入变量。
$query = '
query HeroNameQuery($id: ID!) {
hero(id: $id) {
name
}
}';
$variables = ['id' => '1000'];
$result = graphql($schema, $query, null, null, $variables);
Context
如果您需要将一些重要的上下文信息传递给查询,可以使用$contextValues
参数graphql
来执行此操作。此数据将作为$context
参数传递给所有解析器。
$contextValues = [
'currentlyLoggedInUser' => $currentlyLoggedInUser,
];
$result = graphql($schema, $query, null, $contextValues, $variables);
Scalars
模式中的叶节点称为标量(Scalars
),每个标量(Scalars
)可以解析出一些具体数据。
GraphQL中的内置或指定的标量如下:
- Boolean
- Float
- Int
- ID
- String
自定义标量
除了指定的标量之外,您还可以定义自己的自定义标量,
并通过将它们传递给buildSchema
函数作为$options
的一部分参数来让您的Schema
了解它们。
自定义日期标量类型示例:
$dateType = newScalarType([
'name' => 'Date',
'serialize' => function ($value) {
if ($value instanceof DateTime) {
return $value->format('Y-m-d');
}
return null;
},
'parseValue' => function ($value) {
if (\is_string($value)){
return new DateTime($value);
}
return null;
},
'parseLiteral' => function ($node) {
if ($node instanceof StringValueNode) {
return new DateTime($node->getValue());
}
return null;
},
]);
$schema = buildSchema($source, [
'Query' => QueryResolver::class,
[
'types' => [$dateType],
],
]);
每个标量都必须被强制执行,由三个不同的函数完成。serialize
函数将
PHP值转换为相应的输出值。parseValue
函数将变量输入值转换为
相应的PHP值,parseLiteral
函数将AST文字(抽象语法树)转换为相应的PHP值。
高级用法
如果您正在寻找本文档尚未涵盖的内容,最好的办法是查看本项目中的
测试。你会惊奇地发现那里有很多例子。
整合
Laravel
这是一个演示如何在Laravel项目中使用此库的示例。您需要一个application service
来将此库公开给您的应用程序,一个服务提供者来注册该服务,一个控制器和一个
处理GraphQL POST请求的路由。
app/GraphQL/GraphQLService.php
class GraphQLService
{
private $schema;
public function __construct(Schema $schema)
{
$this->schema = $schema;
}
public function executeQuery(string $query, array $variables, ?string $operationName): array
{
return graphql($this->schema, $query, null, null, $variables, $operationName);
}
}
app/GraphQL/GraphQLServiceProvider.php
class GraphQLServiceProvider
{
public function register()
{
$this->app->singleton(GraphQLService::class, function () {
$schemaDef = \file_get_contents(__DIR__ . '/schema.graphqls');
$executableSchema = buildSchema($schemaDef, [
'Query' => QueryResolver::class,
]);
return new GraphQLService($executableSchema);
});
}
}
app/GraphQL/GraphQLController.php
class GraphQLController extends Controller
{
private $graphqlService;
public function __construct(GraphQLService $graphqlService)
{
$this->graphqlService = $graphqlService;
}
public function handle(Request $request): JsonResponse
{
$query = $request->get('query');
$variables = $request->get('variables') ?? [];
$operationName = $request->get('operationName');
$result = $this->graphqlService->executeQuery($query, $variables, $operationName);
return response()->json($result);
}
}
routes/api.php
Route::post('/graphql', 'app\GraphQL\GraphQLController@handle');