Doctrine Embedded 在 GraphQL 中不显示(Api 平台)
Doctrine Embedded not show with GraphQL (Api Platform)
我在 Doctrine 和 Api 平台之间有问题
我需要一些帮助来序列化学说embedded/embeddable。
从现在开始我们正在使用 Api 带有 REST 方法的平台,但是我们想用 GraphQL 创建一个项目,但是有些字段没有显示。
我在这里做了一个测试仓库https://github.com/tattali/apip-graphql
当我查询时
{
__type(name: "User") {
name
fields {
name
}
}
}
我只得到未嵌入的字段
{
"data": {
"__type": {
"name": "User",
"fields": [
{
"name": "id"
},
{
"name": "_id"
},
{
"name": "firstname"
},
{
"name": "lastname"
}
]
}
}
}
这是我的实体
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Core\Annotation as Api;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @Api\ApiResource
* @ORM\Entity(repositoryClass=UserRepository::class)
*/
class User
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $firstname;
/**
* @ORM\Column(type="string", length=255)
*/
private $lastname;
/**
* @ORM\Embedded(class="App\Entity\Remuneration")
*/
private $actualRemuneration;
/**
* @ORM\Embedded(class="App\Entity\Remuneration")
*/
private $expectedRemuneration;
public function __construct()
{
$this->actualRemuneration = new Remuneration();
$this->expectedRemuneration = new Remuneration();
}
public function getId(): ?int
{
return $this->id;
}
public function getFirstname(): ?string
{
return $this->firstname;
}
public function setFirstname(string $firstname): self
{
$this->firstname = $firstname;
return $this;
}
public function getLastname(): ?string
{
return $this->lastname;
}
public function setLastname(string $lastname): self
{
$this->lastname = $lastname;
return $this;
}
public function getActualRemuneration(): ?Remuneration
{
return $this->actualRemuneration;
}
public function setActualRemuneration(Remuneration $actualRemuneration): self
{
$this->actualRemuneration = $actualRemuneration;
return $this;
}
public function getExpectedRemuneration(): ?Remuneration
{
return $this->expectedRemuneration;
}
public function setExpectedRemuneration(Remuneration $expectedRemuneration): self
{
$this->expectedRemuneration = $expectedRemuneration;
return $this;
}
}
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Embeddable
*/
class Remuneration
{
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $fixedSalary;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $fullPackage;
public function getFixedSalary(): ?int
{
return $this->fixedSalary;
}
public function setFixedSalary(?int $fixedSalary): self
{
$this->fixedSalary = $fixedSalary;
return $this;
}
public function getFullPackage(): ?int
{
return $this->fullPackage;
}
public function setFullPackage(?int $fullPackage): self
{
$this->fullPackage = $fullPackage;
return $this;
}
}
我通过创建自定义 GraphQL 类型和规范器找到了破解解决方案
这并不完美,但可以满足基本需求
# config/services.yaml
services:
# ...
App\GraphQL\Type\EmbeddedObjectType:
tags:
- { name: api_platform.graphql.type }
App\GraphQL\Type\TypeConverter:
decorates: api_platform.graphql.type_converter
// src/GraphQL/Type/EmbeddedObjectType.php
<?php
declare(strict_types=1);
namespace App\GraphQL\Type;
use ApiPlatform\Core\GraphQl\Type\Definition\TypeInterface;
use GraphQL\Error\Error;
use GraphQL\Language\AST\ObjectValueNode;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\AST;
use GraphQL\Utils\Utils;
final class EmbeddedObjectType extends ScalarType implements TypeInterface
{
const NAME = 'EmbeddedObject';
public function __construct()
{
$this->name = 'EmbeddedObject';
$this->description = 'The `EmbeddedObject` scalar type represents file metadata data.';
parent::__construct();
}
public function getName(): string
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function serialize($value)
{
if (\is_array($value)) {
return $value;
}
if (!\is_object($value)) {
throw new Error(sprintf('Value must be an instance of %s to be represented by `EmbeddedObject`: %s', 'object', Utils::printSafe($value)));
}
}
/**
* {@inheritdoc}
*/
public function parseValue($value): object
{
if (!\is_object($value)) {
throw new Error(sprintf('`EmbeddedObject` could not parse: %s', Utils::printSafe($value)));
}
return $value;
}
/**
* {@inheritdoc}
*/
public function parseLiteral($valueNode, array $variables = null)
{
if ($valueNode instanceof ObjectValueNode) {
return AST::valueFromASTUntyped($valueNode, $variables);
}
// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
}
}
// src/GraphQL/Type/TypeConverter.php
<?php
declare(strict_types=1);
namespace App\GraphQL\Type;
use ApiPlatform\Core\GraphQl\Type\TypeConverterInterface;
use Doctrine\ORM\EntityManagerInterface;
use GraphQL\Type\Definition\Type as GraphQLType;
use Symfony\Component\PropertyInfo\Type;
final class TypeConverter implements TypeConverterInterface
{
private $typeConverter;
private $entityManager;
public function __construct(
TypeConverterInterface $typeConverter,
EntityManagerInterface $entityManager
) {
$this->typeConverter = $typeConverter;
$this->entityManager = $entityManager;
}
/**
* {@inheritdoc}
*/
public function convertType(Type $type, bool $input, ?string $queryName, ?string $mutationName, string $resourceClass, string $rootResource, ?string $property, int $depth)
{
if (false !== strpos((string) $resourceClass, 'App\Entity\')) {
$metadata = $this->entityManager->getClassMetadata($resourceClass);
if ($metadata->isEmbeddedClass) {
return EmbeddedObjectType::NAME;
}
}
return $this->typeConverter->convertType($type, $input, $queryName, $mutationName, $resourceClass, $rootResource, $property, $depth);
}
public function resolveType(string $type): ?GraphQLType
{
return $this->typeConverter->resolveType($type);
}
}
// src/Serializer/Normalizer/EmbeddedNormalizer.php
<?php
declare(strict_types=1);
namespace App\Serializer\Normalizer;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
class EmbeddedNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
const FORMAT = 'graphql';
private $entityManager;
private $propertyAccessor;
private $metadata;
public function __construct(
EntityManagerInterface $entityManager
) {
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
$this->entityManager = $entityManager;
}
public function normalize($data, $format = null, array $context = [])
{
$array = [];
foreach ($this->metadata->fieldNames as $name) {
$this->propertyAccessor->setValue(
$array,
"[{$name}]",
$this->normalizer->normalize($this->propertyAccessor->getValue($data, $name), $format, $context)
);
}
return $array;
}
public function supportsNormalization($data, $format = null, array $context = [])
{
if (
self::FORMAT === $format && \is_object($data) && false !== strpos(\get_class($data), 'App\Entity\')
) {
$this->metadata = $this->entityManager->getClassMetadata(\get_class($data));
if ($this->metadata->isEmbeddedClass) {
return true;
}
}
return false;
}
}
像我查询的时候那样
{
user (id: "/api/users/1") {
lastname
actualRemuneration
}
}
渲染
{
"data": {
"user": {
"lastname": "lastname 0",
"actualRemuneration": {
"fixedSalary": 190,
"fullPackage": 198
}
}
}
}
架构
你在哪里定义类型?
App\GraphQL\Type\EmbeddedObjectType:
tags:
- { name: api_platform.graphql.type }
App\GraphQL\Type\TypeConverter:
decorates: api_platform.graphql.type_converter
对于那些有困难的人,我需要进行这些更改和设置。
// src/GraphQL/Type/EmbeddedObjectType.php
...
#50 if (\is_array($value)) {
#51 $value = (object) $value;
#52 }
#55 throw new Error(sprintf('`EmbeddedObject` could not parse: %s', Utils::printSafeJson($value)));
...
并添加服务:
// config/services.yaml
...
App\Serializer\Normalizer\EmbeddedNormalizer:
decorates: "api_platform.serializer.normalizer.item"
arguments: ["@doctrine.orm.entity_manager"]
...
如果您使用另一个 path/namespace(不是 App\Entity),例如:App\Embeddable:
// src/GraphQL/Type/TypeConverter.php
...
#30 if (false !== strpos((string) $resourceClass, 'App\Entity\') || false !== strpos((string) $resourceClass, 'App\Embeddable\')) {
...
// src/Serializer/Normalizer/EmbeddedNormalizer.php
...
#48 self::FORMAT === $format && \is_object($data) && (false !== strpos(\get_class($data), 'App\Entity\') || false !== strpos(\get_class($data), 'App\Embeddable\'))
...
我在 Doctrine 和 Api 平台之间有问题
我需要一些帮助来序列化学说embedded/embeddable。
从现在开始我们正在使用 Api 带有 REST 方法的平台,但是我们想用 GraphQL 创建一个项目,但是有些字段没有显示。
我在这里做了一个测试仓库https://github.com/tattali/apip-graphql
当我查询时
{
__type(name: "User") {
name
fields {
name
}
}
}
我只得到未嵌入的字段
{
"data": {
"__type": {
"name": "User",
"fields": [
{
"name": "id"
},
{
"name": "_id"
},
{
"name": "firstname"
},
{
"name": "lastname"
}
]
}
}
}
这是我的实体
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Core\Annotation as Api;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @Api\ApiResource
* @ORM\Entity(repositoryClass=UserRepository::class)
*/
class User
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $firstname;
/**
* @ORM\Column(type="string", length=255)
*/
private $lastname;
/**
* @ORM\Embedded(class="App\Entity\Remuneration")
*/
private $actualRemuneration;
/**
* @ORM\Embedded(class="App\Entity\Remuneration")
*/
private $expectedRemuneration;
public function __construct()
{
$this->actualRemuneration = new Remuneration();
$this->expectedRemuneration = new Remuneration();
}
public function getId(): ?int
{
return $this->id;
}
public function getFirstname(): ?string
{
return $this->firstname;
}
public function setFirstname(string $firstname): self
{
$this->firstname = $firstname;
return $this;
}
public function getLastname(): ?string
{
return $this->lastname;
}
public function setLastname(string $lastname): self
{
$this->lastname = $lastname;
return $this;
}
public function getActualRemuneration(): ?Remuneration
{
return $this->actualRemuneration;
}
public function setActualRemuneration(Remuneration $actualRemuneration): self
{
$this->actualRemuneration = $actualRemuneration;
return $this;
}
public function getExpectedRemuneration(): ?Remuneration
{
return $this->expectedRemuneration;
}
public function setExpectedRemuneration(Remuneration $expectedRemuneration): self
{
$this->expectedRemuneration = $expectedRemuneration;
return $this;
}
}
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Embeddable
*/
class Remuneration
{
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $fixedSalary;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $fullPackage;
public function getFixedSalary(): ?int
{
return $this->fixedSalary;
}
public function setFixedSalary(?int $fixedSalary): self
{
$this->fixedSalary = $fixedSalary;
return $this;
}
public function getFullPackage(): ?int
{
return $this->fullPackage;
}
public function setFullPackage(?int $fullPackage): self
{
$this->fullPackage = $fullPackage;
return $this;
}
}
我通过创建自定义 GraphQL 类型和规范器找到了破解解决方案
这并不完美,但可以满足基本需求
# config/services.yaml
services:
# ...
App\GraphQL\Type\EmbeddedObjectType:
tags:
- { name: api_platform.graphql.type }
App\GraphQL\Type\TypeConverter:
decorates: api_platform.graphql.type_converter
// src/GraphQL/Type/EmbeddedObjectType.php
<?php
declare(strict_types=1);
namespace App\GraphQL\Type;
use ApiPlatform\Core\GraphQl\Type\Definition\TypeInterface;
use GraphQL\Error\Error;
use GraphQL\Language\AST\ObjectValueNode;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\AST;
use GraphQL\Utils\Utils;
final class EmbeddedObjectType extends ScalarType implements TypeInterface
{
const NAME = 'EmbeddedObject';
public function __construct()
{
$this->name = 'EmbeddedObject';
$this->description = 'The `EmbeddedObject` scalar type represents file metadata data.';
parent::__construct();
}
public function getName(): string
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function serialize($value)
{
if (\is_array($value)) {
return $value;
}
if (!\is_object($value)) {
throw new Error(sprintf('Value must be an instance of %s to be represented by `EmbeddedObject`: %s', 'object', Utils::printSafe($value)));
}
}
/**
* {@inheritdoc}
*/
public function parseValue($value): object
{
if (!\is_object($value)) {
throw new Error(sprintf('`EmbeddedObject` could not parse: %s', Utils::printSafe($value)));
}
return $value;
}
/**
* {@inheritdoc}
*/
public function parseLiteral($valueNode, array $variables = null)
{
if ($valueNode instanceof ObjectValueNode) {
return AST::valueFromASTUntyped($valueNode, $variables);
}
// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
}
}
// src/GraphQL/Type/TypeConverter.php
<?php
declare(strict_types=1);
namespace App\GraphQL\Type;
use ApiPlatform\Core\GraphQl\Type\TypeConverterInterface;
use Doctrine\ORM\EntityManagerInterface;
use GraphQL\Type\Definition\Type as GraphQLType;
use Symfony\Component\PropertyInfo\Type;
final class TypeConverter implements TypeConverterInterface
{
private $typeConverter;
private $entityManager;
public function __construct(
TypeConverterInterface $typeConverter,
EntityManagerInterface $entityManager
) {
$this->typeConverter = $typeConverter;
$this->entityManager = $entityManager;
}
/**
* {@inheritdoc}
*/
public function convertType(Type $type, bool $input, ?string $queryName, ?string $mutationName, string $resourceClass, string $rootResource, ?string $property, int $depth)
{
if (false !== strpos((string) $resourceClass, 'App\Entity\')) {
$metadata = $this->entityManager->getClassMetadata($resourceClass);
if ($metadata->isEmbeddedClass) {
return EmbeddedObjectType::NAME;
}
}
return $this->typeConverter->convertType($type, $input, $queryName, $mutationName, $resourceClass, $rootResource, $property, $depth);
}
public function resolveType(string $type): ?GraphQLType
{
return $this->typeConverter->resolveType($type);
}
}
// src/Serializer/Normalizer/EmbeddedNormalizer.php
<?php
declare(strict_types=1);
namespace App\Serializer\Normalizer;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
class EmbeddedNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
const FORMAT = 'graphql';
private $entityManager;
private $propertyAccessor;
private $metadata;
public function __construct(
EntityManagerInterface $entityManager
) {
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
$this->entityManager = $entityManager;
}
public function normalize($data, $format = null, array $context = [])
{
$array = [];
foreach ($this->metadata->fieldNames as $name) {
$this->propertyAccessor->setValue(
$array,
"[{$name}]",
$this->normalizer->normalize($this->propertyAccessor->getValue($data, $name), $format, $context)
);
}
return $array;
}
public function supportsNormalization($data, $format = null, array $context = [])
{
if (
self::FORMAT === $format && \is_object($data) && false !== strpos(\get_class($data), 'App\Entity\')
) {
$this->metadata = $this->entityManager->getClassMetadata(\get_class($data));
if ($this->metadata->isEmbeddedClass) {
return true;
}
}
return false;
}
}
像我查询的时候那样
{
user (id: "/api/users/1") {
lastname
actualRemuneration
}
}
渲染
{
"data": {
"user": {
"lastname": "lastname 0",
"actualRemuneration": {
"fixedSalary": 190,
"fullPackage": 198
}
}
}
}
架构
你在哪里定义类型?
App\GraphQL\Type\EmbeddedObjectType:
tags:
- { name: api_platform.graphql.type }
App\GraphQL\Type\TypeConverter:
decorates: api_platform.graphql.type_converter
对于那些有困难的人,我需要进行这些更改和设置。
// src/GraphQL/Type/EmbeddedObjectType.php
...
#50 if (\is_array($value)) {
#51 $value = (object) $value;
#52 }
#55 throw new Error(sprintf('`EmbeddedObject` could not parse: %s', Utils::printSafeJson($value)));
...
并添加服务:
// config/services.yaml
...
App\Serializer\Normalizer\EmbeddedNormalizer:
decorates: "api_platform.serializer.normalizer.item"
arguments: ["@doctrine.orm.entity_manager"]
...
如果您使用另一个 path/namespace(不是 App\Entity),例如:App\Embeddable:
// src/GraphQL/Type/TypeConverter.php
...
#30 if (false !== strpos((string) $resourceClass, 'App\Entity\') || false !== strpos((string) $resourceClass, 'App\Embeddable\')) {
...
// src/Serializer/Normalizer/EmbeddedNormalizer.php
...
#48 self::FORMAT === $format && \is_object($data) && (false !== strpos(\get_class($data), 'App\Entity\') || false !== strpos(\get_class($data), 'App\Embeddable\'))
...