【问题标题】:Doctrine Embedded not show with GraphQL (Api Platform)GraphQL(Api 平台)不显示嵌入的 Doctrine
【发布时间】:2020-11-10 11:34:33
【问题描述】:

我在 Doctrine 和 Api Platform 之间遇到了问题

我需要一些帮助来序列化嵌入/可嵌入的学说。

从现在开始,我们使用 REST 方法的 Api 平台,但我们想用 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;
    }
}

【问题讨论】:

    标签: symfony doctrine-orm graphql doctrine api-platform.com


    【解决方案1】:

    我通过创建自定义 GraphQL 类型和规范器找到了一个 hack 解决方案

    这并不完美,但可以满足基本需求

    # 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
          }
        }
      }
    }
    

    架构

    【讨论】:

      【解决方案2】:

      你在哪里定义类型?

        App\GraphQL\Type\EmbeddedObjectType:
          tags:
            - { name: api_platform.graphql.type }
      
        App\GraphQL\Type\TypeConverter:
          decorates: api_platform.graphql.type_converter
      

      【讨论】:

        【解决方案3】:

        对于那些有困难的人,我需要进行这些更改和设置。

        // 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\\'))
        ...
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-02
          • 2021-02-19
          • 2019-11-30
          • 1970-01-01
          • 2020-12-10
          • 2022-08-19
          相关资源
          最近更新 更多