rozbehsharahi/graphql3

TYPO3 的 GraphQL 扩展

安装: 116

依赖者: 0

建议者: 0

安全: 0

星标: 9

关注者: 2

分支: 0

开放问题: 1

类型:typo3-cms-extension

v1.8.1 2024-08-04 23:25 UTC

README

此扩展尚属新品,尚未经过真实用户的测试。您可以通过测试和使用 问题板 来贡献。

此包允许您为 TYPO3 页面注册一个 graphql 架构。

如果您注册了一个架构,它可以通过网站根页的 /graphql/graphiql 访问

https://www.example.com/my-site-root/graphql
https://www.example.com/my-site-root/graphiql (仅开发环境使用)

使用方法

使用 webonyx/graphql-php 包的类型注册架构。

文档: https://webonyx.github.io/graphql-php/

graphql3 的用法如下所示

use GraphQL\Type\Schema;
use RozbehSharahi\Graphql3\Registry\SchemaRegistry;

/** @var SchemaRegistry $schemaRegistry */
$schemaRegistry->registerCreator(fn() => new Schema([
    'query' => new ObjectType([
        'name' => 'Query',
        'fields' => [
            'noop' => [
                'type' => Type::string(),
                'resolve' => fn () => 'noop',
            ],
        ],
    ]),
]))

之后,您应该已经能够访问您的 graphql 端点了。

register 方法期望一个 webonyx/graphql-php 包的架构,因此您可以从这里开始做任何事情。

然而,graphql3 的主要重点是提供可扩展的构建器/类型/节点/解析器,这将有助于在 TYPO3 网站上引入 GraphQL。

例如,以下代码完全等价,但使用了内部类型之一。

use RozbehSharahi\Graphql3\Registry\SchemaRegistry;
use RozbehSharahi\Graphql3\Type\NoopQueryType;

/** @var SchemaRegistry $schemaRegistry */
$schemaRegistry->registerCreator(fn () => (new NoopQueryType());

为了有一些真正的 TYPO3 代码,请继续下一章 入门

入门

我们假设您有一个运行的 TYPO3 扩展和一个 Configuration/Services.yaml (如下),这将使构造函数注入和服务定位工作。

https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html#dependency-injection-autoconfigure

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  Your\Extension\:
    resource: '../Classes/*'
    exclude: '../Classes/Domain/Model/*'

在扩展的 Classes 目录中创建一个 graphql 设置类。

<?php

namespace Your\Extension;

use GraphQL\Type\Schema;
use RozbehSharahi\Graphql3\Registry\SchemaRegistry;
use RozbehSharahi\Graphql3\Setup\SetupInterface;
use RozbehSharahi\Graphql3\Type\QueryType;

class GraphqlSetup implements SetupInterface
{
    public function __construct(
        protected SchemaRegistry $schemaRegistry,
        protected QueryType $queryType
    ) {
    }

    public function setup(): void
    {
        $this->schemaRegistry->registerCreator(fn() => new Schema([
            'query' => $this->queryType,
        ]));
    }
}

您可以将 GraphqlSetup 类放在任何地方。只要您实现了 GraphqlSetupInterface,graphql3 就会自动检测您的类并调用 setup 方法。但是,请确保清除所有缓存!

此时,您的 graphql 端点应该已经可访问。

https://../your-site-root/graphql
https://../your-site-root/graphiql # in dev mode

内置的 QueryType 已经为以下实体提供了基于 tca 的架构

  • 页面
  • tt_content
  • 语言

例如

{
  pages {
    items {
      title
      subtitle
      parentPage {
        title
      }
      createdAt(format: "Y-m-d h:i")
      media {
        uid
        extension
        size
        publicUrl
        imageUrl(variant: "default", maxHeight: 100)
      }
    }
  }
  contents {
    items {
      header
      bodytext
      rendered
    }
  }
}

通过实现 QueryTypeExtenderInterface,扩展架构非常简单。

<?php

declare(strict_types=1);

namespace Your\Extension;

use GraphQL\Type\Definition\Type;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNode;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNodeCollection;
use RozbehSharahi\Graphql3\Type\QueryTypeExtenderInterface;

class ExampleQueryTypeExtender implements QueryTypeExtenderInterface
{
    public function extend(GraphqlNodeCollection $nodes): GraphqlNodeCollection
    {
        return $nodes->add(
            GraphqlNode::create('someNode')
                ->withType(Type::string())
                ->withResolver(fn () => 'Hello World')
        );
    }
}

QueryType 实现使用了内置的基于 tca 的构建器。如果您想在 graphql 中添加自己的实体,也可以使用它们

<?php

declare(strict_types=1);

namespace Your\Extension;

use RozbehSharahi\Graphql3\Builder\RecordListNodeBuilder;
use RozbehSharahi\Graphql3\Builder\RecordNodeBuilder;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNodeCollection;
use RozbehSharahi\Graphql3\Type\QueryTypeExtenderInterface;

class ExampleQueryTypeExtender implements QueryTypeExtenderInterface
{
    public function __construct(
        protected RecordNodeBuilder $recordNodeBuilder,
        protected RecordListNodeBuilder $recordListNodeBuilder
    ) {
    }

    public function extend(GraphqlNodeCollection $nodes): GraphqlNodeCollection
    {
        return $nodes
            ->add($this->recordListNodeBuilder->for('sys_log')->build())
            ->add($this->recordNodeBuilder->for('sys_log')->build())
        ;
    }
}

在给定的示例中,使用了 record-node-builder 和 record-list-node-builder。这些类将根据 TCA 配置自动生成一个 graphql 架构。现在可以运行以下查询

{
  sysLogs {
    items {
      updatedAt
      details
      type
    }
  }
}

在 graphql3 上注册架构将激活 graphql 端点。这项任务应该由单个扩展完成,在大多数情况下是您的项目主要扩展。其他扩展应仅提供架构字段和类型...,这将在下一章 文档 中解释。

文档

让我们从 QueryType 开始。

QueryType

查询类型是基本的查询配置,您可以使用它开始。它已经提供了一些根节点,如 page。然而,可以在运行时扩展查询,这使任何扩展都可以钩入。

<?php

namespace Your\Extension;

use GraphQL\Type\Schema;
use RozbehSharahi\Graphql3\Registry\SchemaRegistry;
use RozbehSharahi\Graphql3\Setup\SetupInterface;
use RozbehSharahi\Graphql3\Type\QueryType;

class GraphqlSetup implements SetupInterface
{
    public function __construct(
        protected SchemaRegistry $schemaRegistry,
        protected QueryType $queryType
    ) {
    }

    public function setup(): void
    {
        $this->schemaRegistry->registerCreator(fn() => new Schema([
            'query' => $this->queryType,
        ]));
    }
}

您可能已经检查了您的 /graphiql 路由,并发送了以下查询

{
  page(uid: 1) {
    uid
    title
    parentPage { title }
  }
}

QueryType 通过 QueryTypeExtenderInterface 暴露根节点/字段的配置。通过实现该接口,您可以编辑根查询上的所有节点。

记录类型构建器

为了暴露TYPO3表格,需要一个GraphQL记录类型。GraphQL3提供了一个基于TCA的记录类型构建器。该构建器将根据TCA配置生成webonyx/graphql对象类型。

以下示例展示了使用record-type-builder创建根查询中的页面节点。

<?php

namespace Your\Extension;

use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Schema;
use RozbehSharahi\Graphql3\Builder\RecordTypeBuilder;
use RozbehSharahi\Graphql3\Domain\Model\Record;
use RozbehSharahi\Graphql3\Registry\SchemaRegistry;
use RozbehSharahi\Graphql3\Setup\SetupInterface;

class GraphqlSetup implements SetupInterface
{
    public function __construct(
        protected SchemaRegistry $schemaRegistry,
        protected RecordTypeBuilder $recordTypeBuilder
    ) {
    }

    public function setup(): void
    {
        $this->schemaRegistry->registerCreator(fn() => new Schema([
            'query' => new ObjectType([
                'name' => 'Query',
                'fields' => [
                    'page' => [
                        'type' => $this->recordTypeBuilder->for('pages')->build(),
                        'resolve' => fn () => Record::create('pages', [
                            'uid' => 1, 
                            'title' => 'A hard coded page, which should be loaded by a resolver'
                        ]),
                    ],
                ],
            ]),
        ]));
    }
}

在给定的示例中,只将一个硬编码的页面数组传递给类型。

拥有一个uid参数和通过该uid加载页面的解析器是非常有意义的。这将在后续章节中解决。

除了动态创建记录类型之外,构建器还将提供可扩展性和类型缓存。

任何扩展都可以通过实现\RozbehSharahi\Graphql3\Builder\RecordTypeBuilderExtenderInterface来钩入任何表格的类型创建。

以下代码展示了如何通过添加额外的节点md5来扩展页面类型。

<?php

declare(strict_types=1);

namespace Your\Extension;

use RozbehSharahi\Graphql3\Builder\RecordTypeBuilderExtenderInterface;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNode;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNodeCollection;
use RozbehSharahi\Graphql3\Domain\Model\Tca\TableConfiguration;

class Md5PageTypeExtender implements RecordTypeBuilderExtenderInterface
{
    public function supportsTable(TableConfiguration $tableConfiguration): bool
    {
        return 'pages' === $tableConfiguration->getName();
    }

    public function extendNodes(
        TableConfiguration $tableConfiguration, 
        GraphqlNodeCollection $nodes
    ): GraphqlNodeCollection {
        return $nodes->add(
            GraphqlNode::create('md5')->withResolver(fn ($page) => md5(json_encode($page, JSON_THROW_ON_ERROR)))
        );
    }
}

只要类实现了\RozbehSharahi\Graphql3\Builder\RecordTypeBuilderExtenderInterface,位置并不重要。Symfony依赖注入将负责加载扩展器。但是,请清除缓存!

扩展器也可以删除或编辑现有字段。为此,请查看GraphqlNodeCollectionGraphqlNode,它们将在《GraphqlNode和GraphqlNodeCollection》章节中解释。

节点构建器

GraphQL3提供了节点构建器,以方便创建整个节点。这包括类型、解析器和参数。

每个节点构建器都实现了NodeBuilderInterface,这意味着它提供了一个返回GraphqlNode实例的构建方法。

在接下来的章节中,我们将以record-type-builder为例。查看vendor/rozbehsharahi/graphql3/Classes/Builder以了解其他存在并可使用的构建器。

创建一个有意义的页面节点时,我们很可能会需要

  • 一个加载页面的解析器
  • 一个uid参数,它定义了要加载哪个页面
  • 一个描述页面字段的类型

可以使用以下方式使用RecordNodeBuilder

<?php

declare(strict_types=1);

namespace Your\Extension;

use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Schema;
use RozbehSharahi\Graphql3\Builder\RecordNodeBuilder;
use RozbehSharahi\Graphql3\Registry\SchemaRegistry;
use RozbehSharahi\Graphql3\Setup\SetupInterface;

class GraphqlSetup implements SetupInterface
{
    public function __construct(
        protected SchemaRegistry $schemaRegistry,
        protected RecordNodeBuilder $recordNodeBuilder
    ) {
    }

    public function setup(): void
    {
        $this->schemaRegistry->registerCreator(fn() => new Schema([
            'query' => new ObjectType([
                'name' => 'Query',
                'fields' => [
                    'page' => $this->recordNodeBuilder->for('pages')->build()->toArray()
                ],
            ]),
        ]));
    }
}

底层操作如下

  • 创建一个uid参数
  • 通过\RozbehSharahi\Graphql3\Resolver\RecordResolver创建解析器
  • 通过\RozbehSharahi\Graphql3\Builder\RecordTypeBuilder创建页面类型

这将激活许多功能。例如

  • 访问检查
  • 通过扩展器扩展
  • 灵活地将TCA字段映射到GraphQL字段

列表节点构建器

记录节点构建器将创建单个节点,如pagecontentlogs,列表节点构建器将创建pagescontentlogs

它们自带一系列内置功能,如

  • 分页
  • 过滤
  • 计数
  • 分面(将随后介绍)

假设您已将sys_log添加到您的GraphQL端点中,如下所示

<?php

declare(strict_types=1);

namespace Your\Extension;

use RozbehSharahi\Graphql3\Builder\RecordListNodeBuilder;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNodeCollection;
use RozbehSharahi\Graphql3\Type\QueryTypeExtenderInterface;

class ExampleQueryTypeExtender implements QueryTypeExtenderInterface
{
    public function __construct(
        protected RecordListNodeBuilder $recordListNodeBuilder
    ) {
    }

    public function extend(GraphqlNodeCollection $nodes): GraphqlNodeCollection
    {
        return $nodes
            ->add($this->recordListNodeBuilder->for('sys_log')->build())
        ;
    }
}

通过这种方式,您已经可以进行以下查询

  sysLogs(
    pageSize:2 
    page: 2 
    filters: [
      {type: "eq", field: "type", value: "2"}
    ]
  ) {
    items {
      updatedAt
      details
      type
    }
  }

将提供内置过滤器的更完整文档。

语言

列表节点构建器在sys_language_uid关系方面遵循以下逻辑

当表格在TCA上设置了languageField时,它将为语言过滤添加一个额外的查询参数。

{
  pages(language: "de") {
   ...
  }
}

该参数目前期望的是twoLetterIsoCode。如果给定语言在当前站点上可用,它将被设置为查询的过滤器。不可用的语言将导致异常,并显示描述性错误消息。

语言被继承到子关系中。例如,获取语言为"en"的页面记录将导致子记录也被过滤为语言"en"。

访问控制

graphql3 的访问控制是基于 symfony/security-core 包实现的。它作为基于 JWT 令牌的安全应用程序实现。

JWT 认证

graphql3 上的所有访问控制相关代码都基于 jwt-authentication,这在 \RozbehSharahi\Graphql3\Domain\Model\JwtUser 类中进行了抽象。从核心代码的角度来看,只有通过 JWT 认证头(Bearer 令牌,header-line: Authorization)进行认证。因此,核心代码将不知道令牌的来源,但需要在 JWT 令牌上设置两个字段:字符串类型的 username(用户名)和字符串数组类型的 roles(角色)。

为了创建访问令牌,可以使用以下命令

# Manual creation of a token (asks for username and roles)
vendor/bin/typo3 graphql3:create-token:manual

# Creation of a token by frontend-user in database
vendor/bin/typo3 graphql3:create-token:frontend-user [user-uid]

所有代码都将始终像 jwt-token 认证已发生一样操作,但是如果 graphql3 发现当前登录的 fe-user,它将将该用户映射到 jwt-token。这反映在实现的 \RozbehSharahi\Graphql3\Domain\Model\JwtUser::createFromTypo3Session 中。

为了在 JwtUser::$roles(字符串数组)和 TYPO3 之间提供兼容性,实现了一种映射约定。当 jwt-user 需要与 TYPO3-Backend 用户组匹配时,它必须包含一个具有以下方案的用户角色:ROLE_GROUP_ID::[fe_group.uid]。根据此约定,内部投票者将决定用户是否有权访问受特定 typo3 用户组限制的特定记录。为了抽象此约定,有一个方法 JwtUser::hasGroupId(x),它将底层调用 JwtUser::hasRole('ROLE_GROUP_ID::x')

将 TYPO3 用户映射到 JwtUser 的当前映射非常基础。大多数情况都应满足这一点。尽管如此,如果您需要例如用户组继承,则需要扩展 graphql3。如果您需要此类功能,请在 Github 上的 Issue-Entry 中告诉我。

Jwt 认证设置/配置

目前 graphql3 支持以下算法

  • RS256
  • 带有密码保护的秘密的 RS256
  • HS256
  • EdDSA

您需要设置以下环境变量。如果没有定义公钥,则对于非对称签名(如 HS256)将回退到私钥。

  • 私钥(用于创建令牌,例如 vendor/bin/typo3 graphql3:create-token:manual
  • 公钥(在像 RS256 这样的算法中需要用于验证令牌)
  • 算法(例如 RS256,HS256,EdDSA,默认 RS256)
  • 密码短语(如果私钥被密码加密则需要)

以下示例配置应该是最常见的设置

GRAPHQL3_JWT_ALGORITHM: "RS256"
GRAPHQL3_JWT_PRIVATE_KEY: "file://my-path-to/private.pem"
GRAPHQL3_JWT_PUBLIC_KEY: "file://my-path-to/public.pem"
GRAPHQL3_JWT_PASSPHRASE: "" # Can stay empty if private key is not secured

在正确设置的环境下,您可以使用命令生成令牌。

请确保您的 apache 配置或 nginx 配置允许授权头。

// on apache .htaccess this line might be needed.
CGIPassAuth On

投票 & ACL

在底层,RecordNodeBuilder 使用一个内部的 RecordResolve,该组件负责根据给定的 uid/slug 解析记录。但是,解析器做的不止这些。例如,它提供访问控制,这可以通过项目级别的 Voters 进行控制。

每次解析记录时,它都会传递给 AccessDecisionManager。在 RecordListNodeBuilder 中也是如此,其中每个加载的记录都会进行检查是否有权限。

为了修改/添加您项目中的访问控制,您可以简单地创建一个实现 \RozbehSharahi\Graphql3\Voter\VoterInterface 的类。在实现接口时,您的投票者将自动添加到投票者堆栈中,无论您将其放置在哪里。

请确保实现 Graphql3VoterInterface 变体,而不是 SymfonyVoterInterface

<?php

namespace Your\Extension;

use RozbehSharahi\Graphql3\Domain\Model\JwtUser;
use RozbehSharahi\Graphql3\Domain\Model\Record;
use RozbehSharahi\Graphql3\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

class PageVoter implements VoterInterface
{
    public function vote(TokenInterface $token, mixed $subject, array $attributes): int
    {
        if (!$subject instanceof Record || $subject->getTable()->getName() !== 'pages') {
            return self::ACCESS_ABSTAIN;
        }
        
        return $token->getUser() instanceof JwtUser ? self::ACCESS_GRANTED : self::ACCESS_DENIED;
    }
}

如这里所示,投票者当然不是必需的,因为这已经由一个通用的内部 RecordResolver 处理。

访问决策管理器配置为使用 UnanimousStrategy。这意味着所有投票者都必须授予或放弃访问权限。如果所有投票者都放弃,则授予访问权限。有关更多信息,请参阅 symfony/security 文档。

可以通过symfony的依赖注入容器在任何扩展的Configuration/Services.yaml中修改现有的\RozbehSharahi\Graphql3\Voter\RecordVoter

灵活表单

简介

尽管GraphQL是一种强类型查询语言,但灵活表单在数据库方面更为宽松。灵活表单的数据是有类型的,然而,如果你深入研究TYPO3逻辑,会发现事情可能变得非常模糊。

可以在flexform xml中将字段类型化为整数,例如将settings.page作为整数。然而,由于TYPO3具有根据另一张表中的字段(例如list_type或更复杂的list_type, CType)动态定义同一数据库字段的灵活表单特性,因此无法验证字段settings.page始终为类型x。虽然可以将每个flex-form定义自动分散到其自己的作用域中,但这会在创建模式时造成大量的XML处理,并且会给代码带来很多复杂性。很可能实现这种自动模式创建的努力超过了它的实用性。

基于这些思考,graphql3将灵活表单字段的集成委托给扩展的使用者,而不是神奇地构建庞大的模式。然而,它试图简化该过程。

在GraphQL中激活灵活表单字段

通过配置激活灵活表单字段目前处于实验状态。因此,API可能会频繁更改。请注意,您也可以简单地编写自己的record-type-extender来公开您的灵活表单字段!

灵活表单可以通过简单的方式访问

{
  content(uid: 1) {
    piFlexform
  }
}

这将返回一个无类型安全的数组,它基本上是flex-form xml的JSON表示。

{
  "data": {
    "content": {
      "piFlexform": {
        "settings": {
          "myField": "..."
        }
      }
    }
  }
}

然而,graphql3为您提供了灵活表单字段的安全集成。为了将flex表单字段公开给GraphQL,您必须配置您的表TCA,以告诉graphql3您的flex表单配置的路径。

$GLOBALS['TCA']['tt_content']['graphql3']['flexFormColumns'] = [
    'pi_flexform::default::settings.myField'
];

到达灵活表单字段的路径分为3部分

  • 基本数据库列:pi_flexform
  • 数据结构变体default(参见TCA文档中的config.ds)
  • 灵活表单字段:settings.myField

通过这种方式,graphql3将根据settings.myField的flex表单定义在内部创建一个“虚拟-tca-column”,并将其作为普通列传递给字段创建者堆栈。将为该字段创建一个唯一名称:flex_piFlexform_default_settings_myField。您可以像为普通DB列那样配置自己的名称。

<settings.myField>
    <config>
        ...
        <graphql3>
            <name>fieldName</name>
        </graphql3>
        ...
    </config>
</settings.myField>

如果您查看RozbehSharahi\Graphql3\Domain\Model\Record::get的实现,您会注意到以flex::开头的列名称将导致flex表单值查找。

这个概念使所有字段创建者都可以与flex表单列兼容。因此,在最佳情况下,每当您编写一个字段创建者时,它也将能够充当flex表单字段创建者。

请记住:在出现错误的情况下,您始终可以创建自己的record-type-extender来公开您的灵活表单字段,并且您可以通过问题板给我发消息。

突变

为了演示目的,graphql3提供了一个用于创建sys_news条目的突变。然而,突变的具体实现非常项目特定和上下文相关,graphql3不会对此做出任何假设。现有的示例突变createSysNews只有在具有ROLE_CREATE::sys_news角色的令牌时才可用。

可以通过MutationTypeExtenderInterface添加突变,当然您可以通过注入AccessChecker来实现访问控制。

以下代码显示了突变的实现

<?php

declare(strict_types=1);

namespace Your\Extension;

use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlArgument;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlArgumentCollection;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNode;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNodeCollection;
use RozbehSharahi\Graphql3\Security\AccessChecker;
use RozbehSharahi\Graphql3\Type\MutationTypeExtenderInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool;

class CreateSysNewsMutationTypeExtender implements MutationTypeExtenderInterface
{
    public function __construct(protected ConnectionPool $connectionPool, protected AccessChecker $accessChecker)
    {
    }

    public function extend(GraphqlNodeCollection $nodes): GraphqlNodeCollection
    {
        if (!$this->accessChecker->check(['ROLE_CREATE::sys_news'])) {
            return $nodes;
        }

        return $nodes->add(
            GraphqlNode::create('createSysNews')
                ->withType(Type::int())
                ->withArguments(
                    GraphqlArgumentCollection::create()->add(
                        GraphqlArgument::create('item')->withType(Type::nonNull(
                            new InputObjectType([
                                'name' => 'SysNewsInput',
                                'fields' => fn () => GraphqlNodeCollection::create()
                                    ->add(GraphqlNode::create('title')->withType(Type::string()))
                                    ->add(GraphqlNode::create('content')->withType(Type::string()))
                                    ->toArray(),
                            ])
                        ))
                    )
                )
                ->withResolver(function ($rootValue, $args) {
                    $query = $this->connectionPool->getQueryBuilderForTable('sys_news');
                    $query->insert('sys_news')->values([
                        'title' => $args['item']['title'],
                        'content' => $args['item']['content'],
                    ]);
                    $query->executeStatement();

                    return $query->getConnection()->lastInsertId('sys_news');
                })
        );
    }
}

实际上,突变和查询之间没有区别,您也可以在webonyx/graphql文档中找到相关信息,因此您可以使用与查询相同的类和方法进行突变。

GraphqlNode 和 GraphqlNodeCollection

当扩展内置节点/类型/...时,您将在扩展器中接收到相关的模式部分,您可以自由地更改它们。例如,实现QueryTypeExtenderInterface的查询类型扩展器将接收到根查询节点,然后可以进行编辑。然而,这不会像您预期的那样是一个数组。它将是一个GraphqlNodeCollection

本章将介绍GraphqlNodeGraphqlNodeCollection的工作原理。

GraphqlNode简单地说是一个表示webonyx/graphql-php数组的对象。它用于自动完成,可以简单地转换为数组。

<?php

namespace Your\Namespace;

use GraphQL\Type\Definition\Type;use RozbehSharahi\Graphql3\Domain\Model\GraphqlNode;

$graphqlNode = GraphqlNode::create()
    ->withType(Type::string())
    ->withResolver(fn() => 'hey')
    ->toArray();

// is equivalent to

$graphqlNode = [
    'type' => Type::string(),
    'resolve' => fn() => 'hey'
];

另一方面,您还有GraphqlNodeCollection,它是GraphqlNode对象的一个集合。

<?php

namespace Your\Namespace;

use GraphQL\Type\Definition\Type;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNode;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNodeCollection;

$nodes = (new GraphqlNodeCollection())
    ->add(GraphqlNode::create('myNode')->withType(Type::int()))
    ->add(GraphqlNode::create('myOtherNode')->withType(Type::string()))
    ->toArray();

// is equivalent to

$nodes = [
    'myNode' => [
        'type' => Type::int()
    ],
    'myOtherNode' => [
        'type' => Type::string()
    ]
];

请注意,graphql-nodes和-collections是不可变的。因此,在调用如addremove等方法时,您需要使用返回值。

<?php

namespace Your\Namespace;

use RozbehSharahi\Graphql3\Domain\Model\GraphqlNodeCollection;
use RozbehSharahi\Graphql3\Domain\Model\GraphqlNode;

$nodes = GraphqlNodeCollection::create();
$nodes = $nodes->add(GraphqlNode::create('aNewNode'));

GraphqlArgumentGraphqlArgumentCollection的实现几乎相同。通过自动完成查看您可以选择哪些选项。

贡献

如果您想积极支持此项目或添加功能,请查看贡献指南。

在此继续