ivome / graphql-relay-php
GraphQL Relay参考实现的一个PHP端口
Requires
- php: ^7.1 || ^8.0
- webonyx/graphql-php: ^14.0 || ^15.0
Requires (Dev)
- phpunit/phpunit: ^7.5 || ^8.5 || ^9.5
- satooshi/php-coveralls: ~1.0
README
这是一个库,允许使用graphql-php参考实现轻松创建符合Relay规范的GraphQL服务器。
注意:该代码是从Facebook的原始graphql-relay js实现迁移过来,并对一些PHP相关部分进行了调整和扩展
当前状态
基本的辅助函数功能已就绪,包括测试。由于PHP的限制,异步功能尚未迁移。请参阅此处和此处的讨论。
入门指南
需要了解GraphQL和graphql-php实现的基本知识,以便为该库提供上下文。
有关GraphQL的一般概述,请参阅README,该文档位于GraphQL规范。
该库旨在与graphql-php的GraphQL服务器参考实现一起使用。
有关Relay兼容GraphQL服务器应提供的功能概述,请参阅GraphQL Relay规范,该规范位于Relay网站。概述描述了一系列示例,这些示例作为测试存在于该存储库中。一种开始使用此存储库的好方法是阅读该文档和库中相应的测试。
使用Relay库为graphql-php
通过composer安装此存储库
composer require ivome/graphql-relay-php
在构建graphql-php的架构时,可以使用提供的库函数来简化Relay模式的创建。
连接
提供辅助函数来构建GraphQL类型以及实现返回这些类型的字段的resolve
方法。
Relay::connectionArgs
返回当字段返回支持双向分页的连接类型时应提供的参数。Relay::forwardConnectionArgs
返回当字段返回仅支持正向分页的连接类型时应提供的参数。Relay::backwardConnectionArgs
返回当字段返回仅支持反向分页的连接类型时应提供的参数。Relay::connectionDefinitions
返回给定节点类型的一个connectionType
及其关联的edgeType
。Relay::edgeType
返回一个新的edgeType
。Relay::connectionType
返回一个新的connectionType
。Relay::connectionFromArray
是一个辅助方法,它接受一个数组以及来自connectionArgs
的参数,进行分页和过滤,并返回一个对象,该对象符合connectionType
的resolve
函数期望的形状。Relay::cursorForObjectInConnection
是一个辅助方法,它接受一个数组和成员对象,并返回一个用于突变负载的游标。
以下是从 测试模式 中这些方法的示例用法。
$shipConnection = Relay::connectionDefinitions([ 'nodeType' => $shipType ]); // this could also be written as // // $shipEdge = Relay::edgeType([ // 'nodeType' => $shipType // ]); // $shipConnection = Relay::connectionType([ // 'nodeType' => $shipType, // 'edgeType' => $shipEdge // ]); $factionType = new ObjectType([ 'name' => 'Faction', 'description' => 'A faction in the Star Wars saga', 'fields' => function() use ($shipConnection) { return [ 'id' => Relay::globalIdField(), 'name' => [ 'type' => Type::string(), 'description' => 'The name of the faction.' ], 'ships' => [ 'type' => $shipConnection['connectionType'], 'description' => 'The ships used by the faction.', 'args' => Relay::connectionArgs(), 'resolve' => function($faction, $args) { // Map IDs from faction back to ships $data = array_map(function($id) { return StarWarsData::getShip($id); }, $faction['ships']); return Relay::connectionFromArray($data, $args); } ] ]; }, 'interfaces' => [$nodeDefinition['nodeInterface']] ]);
这显示了向表示连接的 Faction
对象添加一个 ships
字段。它使用 connectionDefinitions({nodeType: shipType})
创建连接类型,将 connectionArgs
作为此函数的参数,然后通过传递船舶数组和参数到 connectionFromArray
来实现解析函数。
对象标识
提供了构建节点 GraphQL 类型以及实现围绕本地 ID 的全局 ID 的辅助函数。
Relay::nodeDefinitions
返回对象可以实现的Node
接口,并返回包含在查询类型中的node
根字段。为了实现这一点,它接受一个将 ID 解析为对象的函数,并确定给定对象的类型。Relay::toGlobalId
接受一个类型名称和特定于此类型名称的 ID,并返回一个在所有类型中都是唯一的“全局 ID”。Relay::fromGlobalId
接受由toGlobalID
创建的“全局 ID”,并返回用于创建它的类型名称和 ID。Relay::globalIdField
创建节点上的id
字段的配置。Relay::pluralIdentifyingRootField
创建一个接受非 ID 标识符列表(如用户名)并映射到相应对象的字段。
以下是从 测试模式 中这些方法的示例用法。
$nodeDefinition = Relay::nodeDefinitions( // The ID fetcher definition function ($globalId) { $idComponents = Relay::fromGlobalId($globalId); if ($idComponents['type'] === 'Faction'){ return StarWarsData::getFaction($idComponents['id']); } else if ($idComponents['type'] === 'Ship'){ return StarWarsData::getShip($idComponents['id']); } else { return null; } }, // Type resolver function ($object) { return isset($object['ships']) ? self::getFactionType() : self::getShipType(); } ); $factionType = new ObjectType([ 'name' => 'Faction', 'description' => 'A faction in the Star Wars saga', 'fields' => function() use ($shipConnection) { return [ 'id' => Relay::globalIdField(), 'name' => [ 'type' => Type::string(), 'description' => 'The name of the faction.' ], 'ships' => [ 'type' => $shipConnection['connectionType'], 'description' => 'The ships used by the faction.', 'args' => Relay::connectionArgs(), 'resolve' => function($faction, $args) { // Map IDs from faction back to ships $data = array_map(function($id) { return StarWarsData::getShip($id); }, $faction['ships']); return Relay::connectionFromArray($data, $args); } ] ]; }, 'interfaces' => [$nodeDefinition['nodeInterface']] ]); $queryType = new ObjectType([ 'name' => 'Query', 'fields' => function () use ($nodeDefinition) { return [ 'node' => $nodeDefinition['nodeField'] ]; }, ]);
这使用 Relay::nodeDefinitions
构建节点接口和 node
字段;它使用 fromGlobalId
在将 ID 映射到对象的函数实现中解析传递的 ID。然后使用 Relay::globalIdField
方法在 Faction
上创建 id
字段,这也确保实现了 nodeInterface
。最后,它将 node
字段添加到查询类型中,使用由 Relay::nodeDefinitions
返回的 nodeField
。
突变
提供了一个辅助函数来构建具有单个输入和客户端突变 ID 的突变。
Relay::mutationWithClientMutationId
接受一个名称、输入字段、输出字段和一个突变方法,该方法将输入字段映射到输出字段,并在过程中执行突变。然后它创建并返回一个字段配置,该配置可以用作突变类型的顶级字段。
以下是从 测试模式 中这些方法的示例用法。
$shipMutation = Relay::mutationWithClientMutationId([ 'name' => 'IntroduceShip', 'inputFields' => [ 'shipName' => [ 'type' => Type::nonNull(Type::string()) ], 'factionId' => [ 'type' => Type::nonNull(Type::id()) ] ], 'outputFields' => [ 'ship' => [ 'type' => $shipType, 'resolve' => function ($payload) { return StarWarsData::getShip($payload['shipId']); } ], 'faction' => [ 'type' => $factionType, 'resolve' => function ($payload) { return StarWarsData::getFaction($payload['factionId']); } ] ], 'mutateAndGetPayload' => function ($input) { $newShip = StarWarsData::createShip($input['shipName'], $input['factionId']); return [ 'shipId' => $newShip['id'], 'factionId' => $input['factionId'] ]; } ]); $mutationType = new ObjectType([ 'name' => 'Mutation', 'fields' => function () use ($shipMutation) { return [ 'introduceShip' => $shipMutation ]; } ]);
此代码创建了一个名为 IntroduceShip
的突变,它接受一个派系 ID 和一个船名作为输入。它输出相关的 Faction
和 Ship
。然后 mutateAndGetPayload
获取一个具有每个输入字段属性的对象,通过构建新船执行突变,然后返回一个将由输出字段解析的对象。
然后我们的突变类型使用 Relay::mutationWithClientMutationId
返回的值创建 introduceShip
字段。
贡献
在克隆此存储库后,请运行以下命令以确保安装依赖项:
composer install
开发完成后,可以通过运行以下命令评估完整的测试套件:
bin/phpunit tests