API 平台:如何规范化 GraphQL 中的嵌入式实体集合?

API Platform: How to normalize a collection of embedded entities in GraphQL?

我正在尝试在 GraphQL 中创建一个可选的子资源集合(带分页)。我希望能够查询:

query {
  getA(id: '/api/A/1') {
    aId
    subresources {
      totalCount
      pageInfo {
        endCursor
        startCursor
        hasNextPage
        hasPreviousPage
      }
      edges {
        node {
          bId
        }
      }
    }
  }
}

并得到结果:

{
  aId: 1,
  subresources: {
    "totalCount": XX,
    "pageInfo": {
      "endCursor": "MQ==",
      "startCursor": "MA==",
      "hasNextPage": true,
      "hasPreviousPage": false
    },
    edges: [
      {
        node: {
          bId: 11
        }
      },
      {
        node: {
          bId: 12
        }
      },
      {
        node: {
          bId: 13
        }
      }
    ]
  }
}

我根本没有使用 Doctrine - 我使用的是自定义数据提供程序。我遇到的问题是,即使我 return 来自 DataProvider::getItem()A 实体具有 B 子资源数组,我得到一个空数组 subresources 在 GraphQL 中。不过,我在 REST 中得到了正确的数据。

我正在按照 SymfonyCasts and I found a related API Platform issue 中给出的说明进行操作,但我仍然没有运气。

我通过 API 平台核心进行了追踪,我认为这与实体在 GraphQL 中的规范化方式有关。具体来说,在 ItemNormalizer::normalizeCollectionOfRelations() 中 returned 了一个空数组。但是,有一条评论说“to-many 由 GraphQL 解析器直接处理”,但我不确定它指的是什么。

这是实体代码。

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;

#[ApiResource(
   graphql: ['item_query', 'collection_query', 'create', 'update', 'delete'],
   collectionOperations: ['get', 'post'],
   itemOperations: ['get', 'put', 'patch', 'delete'],
   normalizationContext: ['groups' => ['read']],
   denormalizationContext: ['groups' => ['write']],
)]
class A {
   #[ApiProperty(identifier: true)]
   #[Groups(['read', 'write'])]
   public ?int $aId = null,

   /** @var B[] */
   #[ApiProperty(readableLink: true, writableLink: true)]
   #[Groups(['read', 'write'])]
   public $subresources = []
}

并且:

#[ApiResource(
   graphql: ['item_query', 'collection_query', 'create', 'update', 'delete'],
   collectionOperations: ['get', 'post'],
   itemOperations: ['get', 'put', 'patch', 'delete'],
   normalizationContext: ['groups' => ['read']],
   denormalizationContext: ['groups' => ['write']],
)]
class B {
   #[ApiProperty(identifier: true)]
   #[Groups(['read', 'write'])]
   public ?int $bId = null,
}

我的 ADataProvider:

   public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): A {
      $bs = $this->bDataProvider->getCollection(B::class, null, []);
      return new A(123, $bs);
   }

我的 BDataProvider:

   /**
    * @return ArrayPaginator<B>
    */
   public function getCollection(string $resourceClass, string $operationName = null, array $context = []): ArrayPaginator {
      return ArrayPaginator::fromList([new B(11), new B(12), new B(13)]);
   }

ArrayPaginator 实现了 IteratorAggregatePaginatorInterface.

具体来说,我看到了这个错误:

{
  "errors": [
    {
      "debugMessage": "Collection returned by the collection data provider must implement ApiPlatform\Core\DataProvider\PaginatorInterface or ApiPlatform\Core\DataProvider\PartialPaginatorInterface.",
      "message": "Internal server error",
      "extensions": {
        "category": "internal"
      },
      "locations": [
        {
          "line": 29,
          "column": 5
        }
      ],
      "path": [
        "a",
        "b"
      ],
      "trace": [
        {
          "file": "/homedir/core/src/GraphQl/Resolver/Stage/SerializeStage.php",
          "line": 100,
          "call": "ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStage::serializeCursorBasedPaginatedCollection(array(0), array(5), array(6))"
        },

TLDR:如何使用注释(或 YAML)使作为子资源集合的属性在 GraphQL 中可选?

各位help/ideas不胜感激,感谢阅读!

找到解决方案:ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface需要由BDataProvider实现。

它在 api 平台的 graphql 解析器的 ReadStage 中使用。令人惊讶的是,它在 REST 解析器中找不到任何地方,所以它不会在 REST 请求中被调用。

唯一需要实施的方法是getSubresource()。我的第一个基本实现如下所示:

public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) {
  if ($context['collection']) {
     return $this->getCollection($resourceClass, $operationName, $context);
  }
  $id = // get your id from $identifiers;
  return $this->getItem($resourceClass, $id, $operationName, $context);
}

不幸的是,在文档中找不到它,但有一些拉动 (1, 2) 打开以添加它。