PHPackages                             kuandd/graphql-relay - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. kuandd/graphql-relay

ActiveLibrary[API Development](/categories/api)

kuandd/graphql-relay
====================

implement relay schema depends on webonyx/graphql-php

0.1.4(6y ago)09MITPHPPHP &gt;=7.1

Since Jun 14Pushed 6y ago1 watchersCompare

[ Source](https://github.com/sandersyao/graphql-relay)[ Packagist](https://packagist.org/packages/kuandd/graphql-relay)[ RSS](/packages/kuandd-graphql-relay/feed)WikiDiscussions master Synced yesterday

READMEChangelogDependencies (4)Versions (6)Used By (0)

graphql-relay
=============

[](#graphql-relay)

基于 webonyx/graphql-php 的 relay 协议实现

动机
--

[](#动机)

1. 基于 graphql-resolve 项目，实现 relay 协议;

状态
--

[](#状态)

不稳定

例子
--

[](#例子)

### node查询 &amp; node接口 &amp; global id

[](#node查询--node接口--global-id)

定义个名为order的例子查询，假设它会返回两个字段，id和sn（当前版本需要注意的是id是必填的，以后会考虑将其和实际业务做成映射关系管理起来，当然也会默认采用id这个字段，做到向后兼容）。需要注意的是，其必须引用 IsNodeQuery Trait 该特性提供对应的解析方法，为返回值增加类型标识数据来告诉 Node 接口当前要解析的类型是什么。

```
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQLRelay\Traits\IsNodeQuery;
use GraphQLResolve\AbstractQuery;

class OrderQuery extends AbstractQuery
{
    use IsNodeQuery;

    /**
     * 查询名
     *
     * @return string
     */
    public function name(): string
    {
        return  'order';
    }

    /**
     * 返回类型
     *
     * @return \GraphQL\Type\Definition\ObjectType
     */
    public function type()
    {
        return  OrderType::getObject();
    }

    /**
     * 解析
     *
     * @return Closure
     */
    public function resolve(): Closure
    {
        return  function ($root, $args, $context, ResolveInfo $resolveInfo) {

            return  [
                'id'    => '1',
                'sn'    => 'abc',
            ];
        };
    }
}
```

声明一个Order类型作为以上查询的返回类型。需要注意的是，该类型必须实现 Node 接口（GraphQL 语言中的的接口，由 NodeInterface 类型提供）。因为node查询返回的类型为Node接口。

```
use GraphQL\Type\Definition\Type;
use GraphQLResolve\AbstractObjectType;
use GraphQLResolve\Contracts\HasInterface;
use GraphQLRelay\Types\NodeInterface;

/**
 * 模拟订单类型
 *
 * Class Order
 * @package GraphQLRelay\Tests\Sim
 */
class OrderType extends AbstractObjectType
    implements HasInterface
{
    /**
     * 类型名
     *
     * @return string
     */
    public function name(): string
    {
        return  'Order';
    }

    /**
     * 获取字段
     *
     * @return \Closure|mixed
     */
    public function fields()
    {
        return  function () {

            return  NodeInterface::mergeFields([
                [
                    'name'  => 'sn',
                    'type'  => Type::string(),
                ],
            ]);
        };
    }

    /**
     * 实现接口
     *
     * @return array
     */
    public function implements(): array
    {
        return  [
            NodeInterface::getObject(),
        ];
    }
}
```

还是需要先定义根节点查询并且将定义好的order查询和node查询作为其字段 (Field)，这里的node查询为本Relay包提供的。

```
use GraphQLRelay\Queries\Node;
use GraphQLResolve\AbstractObjectType;

/**
 * 根查询
 *
 * Class Query
 * @package GraphQLRelay\Tests\Sim
 */
class Query extends AbstractObjectType
{
    /**
     * 字段
     *
     * @return \Closure|mixed
     */
    public function fields()
    {
        return function () {

            return [
                OrderQuery::fetchOptions(),
                Node::fetchOptions(),
            ];
        };
    }
}
```

完成以上 Schema 结构配置和解析模拟之后，我们需要实例化Schema并使用该结构解析出对应的值：

```
//以下这行已经解释了 global id 的算法
$id     = base64_encode(base64_encode('Order:1'));
$query  = 'query OperationQuery ($id: ID!) {
node (id: $id) {
id
... on Order{
sn
}
}}';
$rootValue = null;
$variableValues = [
    'id'    => $id,
];
$context = [];
$operationName = null;

$result = GraphQL::executeQuery(
    //这个例子中省略了 Schema 实例的创建过程
    $schema,
    $query,
    $rootValue,
    $context,
    $variableValues,
    $operationName
);
$data = $result->toArray();
var_dump($data);
```

经过以上处理将会打印结果如下：

```
array(1) {
  ["data"]=>
  array(1) {
    ["node"]=>
    array(2) {
      ["id"]=>
      string(16) "VDNKa1pYSTZNUT09"
      ["sn"]=>
      string(3) "abc"
    }
  }
}
```

### Connection &amp; Edge 协议

[](#connection--edge-协议)

实际的业务场景里，订单一般都会有商品，我们在这里使用连接（Connection）进行关联。 再来看看订单类型：

```
use GraphQL\Type\Definition\Type;
use GraphQLResolve\AbstractObjectType;
use GraphQLResolve\Contracts\HasInterface;
use GraphQLRelay\Types\NodeInterface;

/**
 * 模拟订单类型
 *
 * Class Order
 * @package GraphQLRelay\Tests\Sim
 */
class OrderType extends AbstractObjectType
    implements HasInterface
{
    /**
     * 类型名
     *
     * @return string
     */
    public function name(): string
    {
        return  'Order';
    }

    /**
     * 获取字段
     *
     * @return \Closure|mixed
     */
    public function fields()
    {
        return  function () {

            return  NodeInterface::mergeFields([
                [
                    'name'  => 'sn',
                    'type'  => Type::string(),
                ],
                OrderGoodsQuery::fetchOptions(),
            ]);
        };
    }

    /**
     * 实现接口
     *
     * @return array
     */
    public function implements(): array
    {
        return  [
            NodeInterface::getObject(),
        ];
    }
}
```

我们在返回字段中加入了一个查询字段在类型 OrderGoodsQuery 中定义：

```
use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQLRelay\ConnectionBuilder;
use GraphQLRelay\Relay;
use GraphQLResolve\AbstractQuery;

/**
 * 订单商品查询
 *
 * Class OrderGoodsQuery
 * @package GraphQLRelay\Tests\Sim
 */
class OrderGoodsQuery extends AbstractQuery
{
    /**
     * 字段名
     *
     * @return string
     */
    public function name(): string
    {
        return  'listGoods';
    }

    /**
     * 获取查询参数
     *
     * @return array
     */
    public function args()
    {
       return   Relay::mergeConnectionArgs();
    }

    /**
     * 返回类型
     *
     * @return \GraphQL\Type\Definition\ObjectType|mixed
     */
    public function type()
    {
        return  ConnectionBuilder::getObject(OrderGoodsType::getObject(), function ($nodeData) {

            return  $nodeData['id'];
        });
    }

    /**
     * 解析
     *
     * @return Closure
     */
    public function resolve(): Closure
    {
        return  function ($root, $context, $args, ResolveInfo $info) {

            return  [
                'pageInfo'  => [
                    'hasPreviousPage'   => false,
                    'hasNextPage'       => false,
                ],
                'edges'     => [
                    [
                        'id'        => 1,
                        'name'      => 'a',
                        'quantity'  => 1.0,
                        'unit'      => 'unit',
                    ],
                ],
            ];
        };
    }
}
```

需要注意的是以上代码中使用了 ConnectionBuilder 来创建连接（Connection）类型，考虑到各种连接的结构大同小异，所以这里没有让开发者自行创建一个对应的链接类，而是采用构建者的方式按照"模板"来创建类型。

而模板的变量只有一个：对应的节点类型，通过参数传入。我们接下来看一下节点的代码：

```
use GraphQL\Type\Definition\Type;
use GraphQLResolve\AbstractObjectType;

/**
 * 订单商品模拟
 *
 * Class OrderGoodsType
 * @package GraphQLRelay\Tests\Sim
 */
class OrderGoodsType extends AbstractObjectType
{
    /**
     * 名称
     *
     * @return string
     */
    public function name(): string
    {
        return  'OrderGoods';
    }

    /**
     * 返回字段
     *
     * @return \Closure|mixed
     */
    public function fields()
    {
        return  function () {
            return  [
                [
                    'name'  => 'id',
                    'type'  => Type::string(),
                ],
                [
                    'name'  => 'name',
                    'type'  => Type::string(),
                ],
                [
                    'name'  => 'quantity',
                    'type'  => Type::float(),
                ],
                [
                    'name'  => 'unit',
                    'type'  => Type::string(),
                ],
            ];
        };
    }
}
```

节点又回到了我们最简单的类型。

最后让我们测试一下Relay-Connection协议的效果：

```
$id     = base64_encode(base64_encode('Order:1'));
$query  = 'query OperationQuery ($id: ID!) {
node (id: $id) {
id
... on Order{
listGoods {
    edges {
        cursor
        node {
            id
            name
        }
    }
}
}
}}';
$rootValue = null;
$variableValues = [
    'id'    => $id,
];
$context = [];
$operationName = null;

$result = GraphQL::executeQuery(
    $this->schema,
    $query,
    $rootValue,
    $context,
    $variableValues,
    $operationName
);
$data = $result->toArray();
var_dump($data);
```

以上代码将输出：

```
array(1) {
  ["data"]=>
  array(1) {
    ["node"]=>
    array(2) {
      ["id"]=>
      string(16) "VDNKa1pYSTZNUT09"
      ["listGoods"]=>
      array(1) {
        ["edges"]=>
        array(1) {
          [0]=>
          array(2) {
            ["cursor"]=>
            string(13) "5d0389139f092"
            ["node"]=>
            array(2) {
              ["id"]=>
              string(1) "1"
              ["name"]=>
              string(1) "a"
            }
          }
        }
      }
    }
  }
}
```

### Mutation input &amp; clientMutationId 协议

[](#mutation-input--clientmutationid-协议)

创建订单并获得可预期的结果，通过clientMutationId对应请求的结果，我们需要继承 AbstractObjectType 抽象类来声明一个变更。

```
use Closure;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ObjectType;
use GraphQLRelay\Queries\AbstractRelayMutation;
use GraphQL\Type\Definition\ResolveInfo;

/**
 * 创建订单模拟
 *
 * Class CreateOrderQuery
 * @package GraphQLRelay\Tests\Sim
 */
class CreateOrderQuery extends AbstractRelayMutation
{
    /**
     * 名字
     *
     * @return string
     */
    public function name(): string
    {
        return  'createOrder';
    }

    /**
     * 获取输出对象
     *
     * @return InputObjectType
     */
    public function getInputObject(): InputObjectType
    {
        return  OrderInput::getObject();
    }

    /**
     * 获取返回对象
     *
     * @return ObjectType
     */
    public function getPayloadObject(): ObjectType
    {
        return  OrderCreated::getObject();
    }

    /**
     * 解析
     *
     * @return Closure
     */
    public function getResolve(): Closure
    {
        return  function ($root, $args, $context, ResolveInfo $info) {

            return  [
                'id'    => 1,
                'sn'    => 'abc',
            ];
        };
    }
}
```

Relay 协议中对变更有两个主要约束：

1. 输入参数有且只有一个input，且类型为InputObject类型；
2. 如果参数中带有clientMutationId则，输出结果中必然有clientMutationId属性。

对于后者我没有做变通，目前只是强制添加的clientMutationId属性。

```
use GraphQLRelay\Types\AbstractRelayPayloadObject;

/**
 * 创建订单输出结果
 *
 * Class OrderCreated
 * @package GraphQLRelay\Tests\Sim
 */
class OrderCreated extends AbstractRelayPayloadObject
{
    /**
     * 获取输出字段
     *
     * @return mixed
     */
    public function fields()
    {
        return  OrderType::getInstance()->fields();
    }
}
```

以上的输出类型代码中直接引用Order类型的字段，这种写法可以复用很多业务逻辑。

```
use GraphQL\Type\Definition\Type;
use GraphQLRelay\Types\AbstractRelayInputObject;

class OrderInput extends AbstractRelayInputObject
{
    public function fields()
    {
        return  function () {
            return  [
                [
                    'name'  => 'userId',
                    'type'  => Type::string(),
                ],
            ];
        };
    }
}
```

在输入参数中，目前只有一个参数userId。

我们来看看测试效果，不过这里就省略了根变更声明：

```
$id         = 1;
$mutationId = uniqid();
$query      = 'mutation TestMutation ($order: OrderInput!) {
createOrder (input: $order) {
id
sn
clientMutationId
}}';
$rootValue = null;
$variableValues = [
    'order'     => [
        'clientMutationId'  => $mutationId,
        'userId'            => $id,
    ],
];
$context = [];
$operationName = null;

$result = GraphQL::executeQuery(
    $this->schema,
    $query,
    $rootValue,
    $context,
    $variableValues,
    $operationName
);
$data = $result->toArray();
var_dump($data);
```

输出结果如下：

```
array(1) {
  ["data"]=>
  array(1) {
    ["createOrder"]=>
    array(3) {
      ["id"]=>
      string(28) "VDNKa1pYSkRjbVZoZEdWa09qRT0="
      ["sn"]=>
      string(3) "abc"
      ["clientMutationId"]=>
      string(13) "5d038dc1ee27c"
    }
  }
}
```

以上三种协议均已经实现，只不过该项目还处于不稳定状态，请勿部署于生产环境。

接下来要做的：
-------

[](#接下来要做的)

1. connection 的 cursor 参数解析还是个问题，目前只是将callable参数传入 builder 来解决输出问题，但并没有解决参数问题。
2. 进一步完善测试用例；

希望本轮子能节省你们的开发时间，也欢迎大家的PR。

###  Health Score

22

—

LowBetter than 22% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity4

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

5

Last Release

2517d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/db0645c4eac893e5b2aed5893ea3c344d31b1cc435d4187c36d3b7edf4a93a23?d=identicon)[sandersyao](/maintainers/sandersyao)

---

Top Contributors

[![sandersyao](https://avatars.githubusercontent.com/u/2403124?v=4)](https://github.com/sandersyao "sandersyao (1 commits)")

---

Tags

apigraphql

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/kuandd-graphql-relay/health.svg)

```
[![Health](https://phpackages.com/badges/kuandd-graphql-relay/health.svg)](https://phpackages.com/packages/kuandd-graphql-relay)
```

###  Alternatives

[ivome/graphql-relay-php

A PHP port of GraphQL Relay reference implementation

271632.4k5](/packages/ivome-graphql-relay-php)[wp-graphql/wp-graphql-woocommerce

WooCommerce bindings for WPGraphQL

69146.8k](/packages/wp-graphql-wp-graphql-woocommerce)[rubix/server

Deploy your Rubix ML models to production with scalable stand-alone inference servers.

632.3k](/packages/rubix-server)[alexaandrov/laravel-graphql-client

GraphQL client for laravel/lumen

125.6k](/packages/alexaandrov-laravel-graphql-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
