DocQrine Embedded无法与GraphQLApi平台一起显示

问题描述

我在教义和Api平台之间有问题

我希望对序列化嵌入/可嵌入的学说有所帮助。

从现在开始,我们将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,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
      }
    }
  }
}

架构 enter image description here

,

您在哪里定义类型?

  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"]
...

如果您使用其他路径/命名空间(不是 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\\'))
...