amazeelabs/graphql_directives

基于指令的Drupal GraphQL模式。

2.6.1 2024-09-09 12:00 UTC

This package is auto-updated.

Last update: 2024-09-09 12:01:17 UTC


README

GraphQL模式实现的指令方法。提供了一种名为'Directable'的模式插件,它可以加载GraphQL模式定义文件,并使用指令插件实现。

用法

创建一个GraphQL模式定义文件,并用指令注解它。

type Query {
  hello: String @value(string: "Hello world!")
}

使用“Directable”模式插件配置GraphQL服务器,并将模式定义路径设置为创建的模式文件。

指令定义将自动添加到模式中。为了支持具有自动补全和语法检查的IDE,有一个drush命令可以生成包含所有指令及其实现位置信息的模式文件。

drush graphql:directives >> directives.graphqls

链式调用

指令可以链式调用以组合可重用的数据生成器。它们从左到右组成,意味着左侧指令的输出将作为父值传递给其右侧邻居。

type Query {
  # This will emit "three".
  list: String! @value(json: "[\"one\", \"two\", \"three\"]") @seek(pos: 2)
}

映射

@map指令允许遍历其左侧邻居的输出,并将右侧的所有指令应用于每个项目。

type Query {
  # This will emit ["a", "b"].
  map: [String!]!
    @value(json: "[{\"x\": \"a\"},{\"x\": \"b\"}]")
    @map
    @prop(key: "x")
}

默认值

由于Drupal的数据结构无法保证完整性,GraphQL模式将在可能的情况下强制执行默认值。每当类型在非空位置(末尾没有!)中使用时,如果值是null,则尝试应用默认值。默认值由类型决定,例如Int类型的默认值是0Boolean类型的默认值是falseString类型的默认值是"",列表类型(如[String!]!)的默认值是[]

对于自定义类型、接口、联合或标量,可以使用@default指令开始一个生成默认值的指令链。

scalar MyScalar @default @value(string: "bar")

type Query {
  # This will emit `''`.
  string: String! @value
  # This will emit `0`.
  int: Int! @value
  # This will emit `[]`.
  list: [String!]! @value
  # This will emit `bar`
  manual: MyScalar! @value
}

类型解析

指令还可以用于解析联合和接口的运行时类型。为此,将任何可以用于解析字段值的指令应用于接口或联合。指令链应解析为字符串值,该值将被视为类型ID。

union Letters @prop(key: "type") = A | B

然后,将解析出的类型ID与带有@type指令注解的对象类型进行匹配,以检索实际类型。

type A @type(id: "a") {
  type: String!
}

type B @type(id: "b") {
  type: String!
}

参数处理

指令可以使用ArgumentTrait来应用动态参数。如果指令参数等于$,则当前值将作为参数值传递。如果它的$后面跟有任何字符,则这些字符将用作键从当前查询参数中检索值。

实现此行为的参数将被标记为(动态)。

type Query {
  static: Post @loadEntity(type: "node", id: "1")
  parent: Post @value(int: 1) @loadEntity(type: "node", id: "$")
  argument(id: String!): Post @loadEntity(type: "node", id: "$id")
}

指令

@value

@value指令允许您为字段定义一个静态值,作为原始值或JSON编码的字符串。如果没有任何参数,它将发出null

type Query {
  null: String @value
  hello: String! @value(string: "Hello world!")
  theAnswer: Int! @value(int: 42)
  pi: Float! @value(float: 3.14)
  true: Boolean! @value(float: true)
  object: MyType! @value(json: "{\"foo\":\"bar\"}")
}

@seek

从列表或可迭代对象中提取值。参数pos标记目标位置。

type Query {
  # This will emit "three".
  list: String! @value(json: "[\"one\", \"two\", \"three\"]") @seek(pos: 2)
}

@prop

从对象或映射中提取属性。参数key标记目标键。

type Query {
  # This will emit "bar".
  prop: String! @value(json: "{\"foo\": \"bar\"}") @prop(key: "foo")
}

@map

遍历当前结果列表,并将以下指令应用于每个项目。

type Query {
  # This will emit ["a", "b"].
  map: [String!]!
    @value(json: "[{\"x\": \"a\"},{\"x\": \"b\"}]")
    @map
    @prop(key: "x")
}

@type

为对象类型注解一个特定的ID,该ID将用于接口和联合类型解析。

union Letters @prop(key: "type") = A | B

type A @type(id: "a") {
  type: String!
}

type B @type(id: "b") {
  type: String!
}

@arg

检索参数值并将其注入为当前值,该值将作为父值传递给后续指令。

type Query {
  post(path: String!): Page @arg(name: "path") @route(path: "$") @loadEntity
}

@route

将路径(动态)解析为Drupal Url 对象。

type Query {
  post(path: String!): Page @route(path: "$path") @loadEntity
}

@loadEntity

以各种方式加载Drupal实体。如果没有参数,它假定父值包含由@route生成的Url对象,并尝试从那里加载实体。

如果与iduuid一起使用,可选的operation参数可以定义特定的访问检查操作。

type Query {
  post(path: String!): Page @route(path: "$path") @loadEntity
}

否则,它需要定义一个静态的type参数以及一个id(动态)或uuid(动态)参数。

type Query {
  id(id: String!): Post @loadEntity(type: "node", id: "$id")
  uuid(uuid: String!): Post @loadEntity(type: "node", uuid: "$uuid")
}

@resolveEntity[...]

检索实体的各种简单属性。以下指令被支持:

  • @resolveEntityId
  • @resolveEntityUuid
  • @resolveEntityType
  • @resolveEntityBundle
  • @resolveEntityLabel
  • @resolveEntityPath
  • @resolveEntityLanguage
type Query {
  post(id: String!): Post @loadEntity(type: "node", id: "$id")
}

type Post {
  title: String! @resolveEntityLabel
}

@resolveEntityTranslation

检索实体的特定翻译,由lang(动态)参数定义。

type Query {
  post(id: String!, lang: String!): Post
    @loadEntity(type: "node", id: "$id")
    @resolveEntityTranslation(lang: "$lang")
}

@resolveEntityTranslations

检索实体的所有翻译。

type Query {
  post(id: String!): Post @loadEntity(type: "node", id: "$id")
}

type Post {
  translations: [Post!]! @resolveEntityTranslations
}

@resolveProperty

通过其path参数检索实体的属性。

type Post {
  body: String @resolveProperty(path: "body.value")
}

@lang

切换当前执行语言,用于当前字段以下的所有子树。接受一个code参数(动态),如果省略,则使用父值。如果后者是字符串,则按原样使用,如果是TranslatableInteface的实例,则从那里推导语言。

type Query {
  post(id: String!): Post @loadEntity(type: "node", id: "$id") @lang
}

@resolveMenuItems

检索菜单实体的所有项。接受一个可选的max_level参数,用于限制最大菜单级别数。树被展平为一个列表,以避免嵌套片段的必要性。应使用@resolveMenuItemId@resolveMenuItemParentId指令在消费者中重建树。菜单项列表还通过语言过滤,尊重当前执行上下文语言,因为它可以通过@lang控制。

type Query {
  menu: Limited! @loadEntity(type: "menu", id: "main", operation: "view label")
}

type Menu {
  items: [MenuItem!]! @lang(code: "fr") @resolveMenuItems(max_level: 2)
}

@resolveMenuItem[...]

各种菜单项属性。

  • @resolveMenuItemId
  • @resolveMenuItemParentId
  • @resolveMenuItemLabel
  • @resolveMenuItemUrl

@resolveEntityReference & @resolveEntityReferenceRevisions

解析附加到给定field的引用实体。将尝试检索与当前宿主实体匹配的翻译。

type Query {
  post(id: String!): Post @loadEntity(type: "node", id: "$id") @lang
}

type Post {
  title: String! @resolveEntityLabel
  related: [Post!]! @resolveEntityReference(field: "field_related")
}

@drupalView

执行Drupal视图。

type Query {
  contentHub(locale: String!, args: String): ContentHubView!
    @lang(code: "$locale")
    @drupalView(id: "content_hub:default", args: "$args")
}

type ContentHubView {
  total: Int!
  rows: [Post!]!
  filters: ContentHubViewFilters!
}

type ContentHubViewFilters {
  tag: [ViewFilter!]!
  category: [ViewFilter!]!
}

type ViewFilter {
  value: String!
  label: String!
}

id 参数

视图ID和显示由冒号分隔。例如:content_hub:default

args 参数

查询字符串格式中的过滤器、页码等。例如:page=1&pageSize=9&contextualFilters=40/12&tag[]=1&type=foo

保留键

  • page:页码。从1开始。默认为1。
  • pageSize:每页的项目数。默认为10。
  • contextualFilters:上下文过滤器值,例如40/12/10

其余键被视为过滤器。

允许使用NPM query-string包的格式,其中arrayFormat设置为bracket,例如tag[]=1&tag[]=2的数组值。

返回结果

结果的结构如下

type Result = {
  total: number;
  items: [DrupalEntity];
  filters: {
    [filterKey: string]: Array<{
      value: string;
      label: string;
    }>;
  };
};

翻译

使用@lang指令为视图设置语言。如果结果实体具有所选语言,它们将返回在该语言中。

如果必须按语言过滤结果,请使用视图中的Content: Translation language (= Interface text language selected for page)过滤器。

扩展

要添加自定义指令,请创建一个模块,并在src/Plugin/GraphQL/Directive目录中添加新插件。插件的"ID"将是调用它的schema的句柄,没有@前缀。

<?php
namespace Drupal\my_directives\Plugin\GraphQL\Directive;

use Drupal\graphql_directives\DirectiveInterface;

/**
* @Directive(
*   id="echo",
*   description="Return the same string that you put in.",
*   arguments = {
*     "input" = "String!",
*   }
* )
 */
class EchoDirective extends PluginBase implements DirectiveInterface {
  /**
   * {@inheritdoc}
   */
  public function buildResolver(
    ResolverBuilder $builder,
    array $arguments
  ) : ResolverInterface {
    return $builder->fromValue($arguments['input']);
  }
}

自动加载

@amazeelabs/codegen-autoloader 提供了一个使用 drupal 模式添加新指令的便捷选项。生成的 JSON 文件与此模块的 Autoload 注册表 配置选项兼容。

模式扩展

该模块提供了一个 DirectableSchemaExtensionPluginBase 类,可用于创建响应父模式定义中指令的模式扩展。Drupal GraphQL 模块的模式扩展插件提供了两个模式定义:一个用于 "基本" 模式,另一个用于实际扩展。在可定向模式扩展的情况下,基本模式定义应包含指令,而扩展模式定义了派生类型和字段。

请参考 graphql_directives_test 模块以获取一个非常简单的示例。