wwwision/cr-graphql

Neos内容存储库的简单GraphQL API

0.2.0 2021-09-30 09:01 UTC

This package is auto-updated.

Last update: 2024-08-25 17:26:46 UTC


README

Neos内容存储库的简单GraphQL适配器

描述

此包为Neos内容存储库提供简单的GraphQL API。它可以在Neos发行版中使用,也可以与独立的内容存储库一起使用。

免责声明:这仅仅是一个实验。您可以自由使用它或根据您的需求复制和调整它,但请了解其局限性

局限性

  • API仅提供对活动工作空间节点(这可能在将来略有变化,但不会成为完整的CR API!)的读取访问
  • API只是内容存储库PHP API的一个薄包装。除了默认的Neos渲染外,目前还没有缓存!
  • CR节点可以无限嵌套,GraphQL查询不能(请参阅以下示例)

安装

通过composer安装此包

composer require wwwision/cr-graphql

路由

此包包含相应的路由,但默认情况下它们不会激活。这可以通过一些Settings.yaml进行更改

Neos:
  Flow:
    mvc:
      routes:
        'Wwwision.CR.GraphQL':
          position: 'start'
          variables:
            path: 'graphql'

注意: path变量定义了GraphQL API将要公开的URL路径,在上面的示例中,这将是https://your-server.tld/graphql

调整策略

如果在一个Neos发行版中安装,通常不允许未认证的用户调用GraphQL控制器。这可以通过Configuration/Policy.yaml文件中的以下行进行更改

privilegeTargets:

  'Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilege':

    'Some.Package:GraphQL.Endpoint':
      matcher: 'method(t3n\GraphQL\Controller\GraphQLController->queryAction(endpoint=="Wwwision_CR_GraphQL"))'

roles:

  'Neos.Flow:Everybody':
    privileges:
      -
        privilegeTarget: 'Some.Package:GraphQL.Endpoint'
        permission: GRANT

使用

如果正确安装,您应该能够查询GraphQL端点。您可以通过cURL尝试

curl 'http://localhost:8081/graphql' -H 'content-type: application/json' --data-binary '{"operationName":null,"variables":{},"query":"{rootNode {identifier}}"}'

这将返回类似的内容

{"data":{"rootNode":{"identifier":"a1839a7e-8600-4ff3-ab9e-27d54fd8b3d9"}}}

节点属性

节点属性通过NodeProperties标量表示。在实践中,这意味着属性将转换为普通JSON,此包使用Symfony Serializer将非标量属性转换为。常见的对象类型(DateTime、节点引用、资产和图片)由自定义Normalizers覆盖。您可以轻松配置附加类型或更改现有类型的操作。

添加自定义normalizers

此包已提供AssetNormalizer,它将资产转换为类似JSON的对象

{
  "title": "<title of the asset>",
  "mediaType": "<media type of the asset>",
  "url": "<absolute url of the published asset>"
}

要为视频资产添加自定义转换,我们可以创建一个新的Normalizer

<?php
namespace Your\Package;

use Neos\Media\Domain\Model\Video;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

final class AssetNormalizer implements NormalizerInterface
{

    public function supportsNormalization($data, string $format = null)
    {
        return $data instanceof Video;
    }

    /**
     * @param Video $video
     * @param string|null $format
     * @param array $context
     * @return array|\ArrayObject|bool|float|int|string|void|null
     */
    public function normalize($video, string $format = null, array $context = [])
    {
        return [
            'id' => $video->getIdentifier(),
            'title' => $video->getTitle(),
            'width' => $video->getWidth(),
            'height' => $video->getHeight(),
        ];
    }

}

并通过Settings.yaml注册它

Wwwision:
  CR:
    GraphQL:
      normalizers:
        'YourVideoNormalizer':
          className: 'Your\Package\VideoNormalizer'
          position: 'start'

注意: 我们需要将position设置为"start",以便在新normalizer在现有的AssetNormalizer之前评估(因为这也支持Video属性)。

替换现有normalizers

如果您想替换/删除现有的normalizer,可以通过覆盖相应的设置来实现

Wwwision:
  CR:
    GraphQL:
      normalizers:
        # disable provided image normalizer
        'Image': ~
        # change implementation of provided node normalizer
        'Node':
          className: 'Your\Package\SomeOtherImplementation'

示例查询

一些GraphQL查询示例

通过ID获取单个节点

{
  node(identifier: "6db34628-60c7-4c9a-f6dd-54742816039e") {
    identifier
    name
    type
    properties
  }
}

Neos.Demo站点上的结果可能如下所示

{
  "data": {
    "node": {
      "identifier": "6db34628-60c7-4c9a-f6dd-54742816039e",
      "name": "i-down-the-rabbit-hole",
      "type": "Neos.Demo:Document.Chapter",
      "properties": {
        "title": "I. Down the Rabbit-hole",
        "chapterImage": {
          "width": 359,
          "height": 500,
          "url": "http://localhost:8081/_Resources/Persistent/3/0/d/0/30d0d71c6e7e4dd53636a8b9a5d5c8fd9b73f10f/alice-1.jpg"
        },
        "chapterDescription": "Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, \"and what is the use of a book,\" thought Alice, \"without pictures or conversations?\"",
        "layout": "chapter",
        "uriPathSegment": "i-down-the-rabbit-hole"
      }
    }
  }
}

递归获取所有文档节点

GraphQL不支持递归查询(出于一些合理的理由),但可以通过使用片段来绕过这一限制。以下查询将获取根节点(/sites)、所有站点节点(例如/sites/neosdemo),以及实现Neos.Neos:Document节点类型的所有子节点,最多5级

{
  rootNode {
    sites: childNodes {
      site: childNodes {
        name
        childNodes(filter: "Neos.Neos:Document") {
          ...NodesRecursive
        }
      }
    }
  }
}
fragment NodesRecursive on Node {
  ...NodeFields
  childNodes(filter: "Neos.Neos:Document") {
    ...NodeFields
    childNodes(filter: "Neos.Neos:Document") {
      ...NodeFields
      childNodes(filter: "Neos.Neos:Document") {
        ...NodeFields
        childNodes(filter: "Neos.Neos:Document") {
          ...NodeFields
        }
      }
    }
  }
}
fragment NodeFields on Node {
  identifier
  name
  type
  properties
}

结果可能如下所示

{
  "data": {
    "rootNode": {
      "sites": [
        {
          "site": [
            {
              "name": "neosdemo",
              "childNodes": [
                {
                  "identifier": "e35d8910-9798-4c30-8759-b3b88d30f8b5",
                  "name": "home",
                  "type": "Neos.Neos:Shortcut",
                  "properties": {
                    "title": "Home",
                    "targetMode": "parentNode",
                    "uriPathSegment": "home",
                    "metaRobotsNoindex": true
                  },
                  "childNodes": []
                },
                {
                  "identifier": "a3474e1d-dd60-4a84-82b1-18d2f21891a3",
                  "name": "features",
                  "type": "Neos.Demo:Document.LandingPage",
                  "properties": {
                    "title": "Features",
                    "uriPathSegment": "features"
                  },
                  "childNodes": [
                    {
                      "identifier": "b082c6b6-8a64-4786-b767-d62ef22209b1",
                      "name": "shortcuts",
                      "type": "Neos.Demo:Document.Page",
                      "properties": {
                        "title": "Shortcuts",
                        "uriPathSegment": "shortcuts"
                      },
                      "childNodes": [],
...

递归获取给定文档节点上的所有内容节点

如上所述,无法实现无限递归。但使用以下查询可以获取指定标识符的节点下的所有内容和内容集合节点,最多5级

query Nodes(
  $rootIdentifier: NodeIdentifier!
  $nodeTypeConstraints: NodeTypeConstraints
) {
  node(identifier: $rootIdentifier) {
    ...NodesRecursive
  }
}
fragment NodesRecursive on Node {
  ...NodeFields
  childNodes(filter: $nodeTypeConstraints) {
    ...NodeFields
    childNodes(filter: $nodeTypeConstraints) {
      ...NodeFields
      childNodes(filter: $nodeTypeConstraints) {
        ...NodeFields
        childNodes(filter: $nodeTypeConstraints) {
          ...NodeFields
        }
      }
    }
  }
}
fragment NodeFields on Node {
  identifier
  name
  type
  properties
}

以下变量

{
  "rootIdentifier":"a3474e1d-dd60-4a84-82b1-18d2f21891a3",
  "nodeTypeConstraints": "Neos.Neos:Content,Neos.Neos:ContentCollection"
}