Ошибка «Невозможно сгенерировать IRI» в платформе Api, когда в абстрактной сущности суперкласса существует отношение «многие ко многим», ссылающиеся на себя - PullRequest
0 голосов
/ 24 апреля 2020

У меня есть абстрактный класс "Node" для сущности со следующим кодом, который является основой для четырех других подклассов сущностей. Я хочу вложить их в любом порядке, чтобы создать свободную иерархию, в которой любой тип узла может быть родительским или дочерним для любого другого типа, а любой узел может иметь несколько родителей.

src / Entity / Node . php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Annotation\ApiSubresource;

/**
 * @ORM\Entity(repositoryClass="App\Repository\NodeRepository")
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="type", type="string")
 * @ORM\DiscriminatorMap({
 *     "ART" = "Article",
 *     "CAT" = "Category",
 *     "LOC" = "Location",
 *     "PJT" = "Project"
 * })
 */
abstract class Node
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToMany(targetEntity="App\Entity\Node", inversedBy="childNodes")
     * @ApiSubresource
     */
    private $parentNodes;

    /**
     * @ORM\ManyToMany(targetEntity="App\Entity\Node", mappedBy="parentNodes")
     * @ApiSubresource
     */
    private $childNodes;

    public function __construct()
    {
        $this->parentNodes = new ArrayCollection();
        $this->childNodes = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * @return Collection|self[]
     */
    public function getParentNodes(): Collection
    {
        return $this->parentNodes;
    }

    public function addParentNode(self $parentNode): self
    {
        if (!$this->parentNodes->contains($parentNode)) {
            $this->parentNodes[] = $parentNode;
        }

        return $this;
    }

    public function removeParentNode(self $parentNode): self
    {
        if ($this->parentNodes->contains($parentNode)) {
            $this->parentNodes->removeElement($parentNode);
        }

        return $this;
    }

    /**
     * @return Collection|self[]
     */
    public function getChildNodes(): Collection
    {
        return $this->childNodes;
    }

    public function addChildNode(self $childNode): self
    {
        if (!$this->childNodes->contains($childNode)) {
            $this->childNodes[] = $childNode;
            $childNode->addParentNode($this);
        }

        return $this;
    }

    public function removeChildNode(self $childNode): self
    {
        if ($this->childNodes->contains($childNode)) {
            $this->childNodes->removeElement($childNode);
            $childNode->removeParentNode($this);
        }

        return $this;
    }
}

Этот класс расширен четырьмя другими объектами, которые представлены как ApiResource с использованием платформы Api в проекте Symfony 5. Все они имеют практически одинаковый код:

src / Entity / Project. php

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource()
 * @ORM\Entity(repositoryClass="App\Repository\ProjectRepository")
 */
class Project extends Node
{
    /**
     * @ORM\Id()
     * @ORM\OneToOne(targetEntity="App\Entity\Node")
     */
    private $id;

    public function getId(): ?int
    {
        return $this->id;
    }
}

Открытые объекты отображаются на панели инструментов платформы Api и может быть успешно запрошен, если абстрактная сущность Node не имеет отношения self parent-child. Другими словами, если я удаляю все методы get / add / removeXXXXNodes и связанные с ними переменные из класса Node, API работает.

Но если взаимосвязь присутствует, возникает ошибка 400:

GET http://localhost/api/projects

{
  "@context": "\/api\/contexts\/Error",
  "@type": "hydra:Error",
  "hydra:title": "An error occurred",
  "hydra:description": "Unable to generate an IRI for \"App\\Entity\\Project\".",
  "trace": [
    {
      "namespace": "",
      "short_class": "",
      "class": "",
      "type": "",
      "function": "",
      "file": "\/var\/www\/html\/app\/vendor\/api-platform\/core\/src\/Bridge\/Symfony\/Routing\/IriConverter.php",
      "line": 155,
      "args": []
    },
    {
      "namespace": "ApiPlatform\\Core\\Bridge\\Symfony\\Routing",
      "short_class": "IriConverter",
      "class": "ApiPlatform\\Core\\Bridge\\Symfony\\Routing\\IriConverter",
      "type": "->",
      "function": "getItemIriFromResourceClass",
      "file": "\/var\/www\/html\/app\/vendor\/api-platform\/core\/src\/Bridge\/Symfony\/Routing\/IriConverter.php",
      "line": 128,
      "args": [
        [
          "string",
          "App\\Entity\\Project"
        ],
        [
          "array",
          [
            [
              "string",
              ""
            ]
          ]
        ],
        [
          "integer",
          1
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Bridge\\Symfony\\Routing",
      "short_class": "IriConverter",
      "class": "ApiPlatform\\Core\\Bridge\\Symfony\\Routing\\IriConverter",
      "type": "->",
      "function": "getIriFromItem",
      "file": "\/var\/www\/html\/app\/vendor\/api-platform\/core\/src\/JsonLd\/Serializer\/ItemNormalizer.php",
      "line": 74,
      "args": [
        [
          "object",
          "App\\Entity\\Project"
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\JsonLd\\Serializer",
      "short_class": "ItemNormalizer",
      "class": "ApiPlatform\\Core\\JsonLd\\Serializer\\ItemNormalizer",
      "type": "->",
      "function": "normalize",
      "file": "\/var\/www\/html\/app\/vendor\/symfony\/serializer\/Serializer.php",
      "line": 146,
      "args": [
        [
          "object",
          "App\\Entity\\Project"
        ],
        [
          "string",
          "jsonld"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "get"
            ],
            "resource_class": [
              "string",
              "App\\Entity\\Project"
            ],
            "input": [
              "null",
              null
            ],
            "output": [
              "null",
              null
            ],
            "request_uri": [
              "string",
              "\/api\/projects"
            ],
            "uri": [
              "string",
              "http:\/\/localhost\/api\/projects"
            ],
            "skip_null_values": [
              "boolean",
              true
            ],
            "resources": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ],
            "exclude_from_cache_key": [
              "array",
              [
                [
                  "string",
                  "resources"
                ],
                [
                  "string",
                  "resources_to_push"
                ]
              ]
            ],
            "resources_to_push": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ],
            "api_sub_level": [
              "boolean",
              true
            ],
            "jsonld_has_context": [
              "boolean",
              true
            ]
          }
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\Serializer",
      "short_class": "Serializer",
      "class": "Symfony\\Component\\Serializer\\Serializer",
      "type": "->",
      "function": "normalize",
      "file": "\/var\/www\/html\/app\/vendor\/api-platform\/core\/src\/Hydra\/Serializer\/CollectionNormalizer.php",
      "line": 87,
      "args": [
        [
          "object",
          "App\\Entity\\Project"
        ],
        [
          "string",
          "jsonld"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "get"
            ],
            "resource_class": [
              "string",
              "App\\Entity\\Project"
            ],
            "input": [
              "null",
              null
            ],
            "output": [
              "null",
              null
            ],
            "request_uri": [
              "string",
              "\/api\/projects"
            ],
            "uri": [
              "string",
              "http:\/\/localhost\/api\/projects"
            ],
            "skip_null_values": [
              "boolean",
              true
            ],
            "resources": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ],
            "exclude_from_cache_key": [
              "array",
              [
                [
                  "string",
                  "resources"
                ],
                [
                  "string",
                  "resources_to_push"
                ]
              ]
            ],
            "resources_to_push": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ],
            "api_sub_level": [
              "boolean",
              true
            ],
            "jsonld_has_context": [
              "boolean",
              true
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Hydra\\Serializer",
      "short_class": "CollectionNormalizer",
      "class": "ApiPlatform\\Core\\Hydra\\Serializer\\CollectionNormalizer",
      "type": "->",
      "function": "normalize",
      "file": "\/var\/www\/html\/app\/vendor\/api-platform\/core\/src\/Hydra\/Serializer\/PartialCollectionViewNormalizer.php",
      "line": 55,
      "args": [
        [
          "object",
          "ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Paginator"
        ],
        [
          "string",
          "jsonld"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "get"
            ],
            "resource_class": [
              "string",
              "App\\Entity\\Project"
            ],
            "input": [
              "null",
              null
            ],
            "output": [
              "null",
              null
            ],
            "request_uri": [
              "string",
              "\/api\/projects"
            ],
            "uri": [
              "string",
              "http:\/\/localhost\/api\/projects"
            ],
            "skip_null_values": [
              "boolean",
              true
            ],
            "resources": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ],
            "exclude_from_cache_key": [
              "array",
              [
                [
                  "string",
                  "resources"
                ],
                [
                  "string",
                  "resources_to_push"
                ]
              ]
            ],
            "resources_to_push": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ],
            "api_sub_level": [
              "boolean",
              true
            ],
            "jsonld_has_context": [
              "boolean",
              true
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Hydra\\Serializer",
      "short_class": "PartialCollectionViewNormalizer",
      "class": "ApiPlatform\\Core\\Hydra\\Serializer\\PartialCollectionViewNormalizer",
      "type": "->",
      "function": "normalize",
      "file": "\/var\/www\/html\/app\/vendor\/api-platform\/core\/src\/Hydra\/Serializer\/CollectionFiltersNormalizer.php",
      "line": 73,
      "args": [
        [
          "object",
          "ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Paginator"
        ],
        [
          "string",
          "jsonld"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "get"
            ],
            "resource_class": [
              "string",
              "App\\Entity\\Project"
            ],
            "input": [
              "null",
              null
            ],
            "output": [
              "null",
              null
            ],
            "request_uri": [
              "string",
              "\/api\/projects"
            ],
            "uri": [
              "string",
              "http:\/\/localhost\/api\/projects"
            ],
            "skip_null_values": [
              "boolean",
              true
            ],
            "resources": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ],
            "exclude_from_cache_key": [
              "array",
              [
                [
                  "string",
                  "resources"
                ],
                [
                  "string",
                  "resources_to_push"
                ]
              ]
            ],
            "resources_to_push": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Hydra\\Serializer",
      "short_class": "CollectionFiltersNormalizer",
      "class": "ApiPlatform\\Core\\Hydra\\Serializer\\CollectionFiltersNormalizer",
      "type": "->",
      "function": "normalize",
      "file": "\/var\/www\/html\/app\/vendor\/symfony\/serializer\/Serializer.php",
      "line": 146,
      "args": [
        [
          "object",
          "ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Paginator"
        ],
        [
          "string",
          "jsonld"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "get"
            ],
            "resource_class": [
              "string",
              "App\\Entity\\Project"
            ],
            "input": [
              "null",
              null
            ],
            "output": [
              "null",
              null
            ],
            "request_uri": [
              "string",
              "\/api\/projects"
            ],
            "uri": [
              "string",
              "http:\/\/localhost\/api\/projects"
            ],
            "skip_null_values": [
              "boolean",
              true
            ],
            "resources": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ],
            "exclude_from_cache_key": [
              "array",
              [
                [
                  "string",
                  "resources"
                ],
                [
                  "string",
                  "resources_to_push"
                ]
              ]
            ],
            "resources_to_push": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\Serializer",
      "short_class": "Serializer",
      "class": "Symfony\\Component\\Serializer\\Serializer",
      "type": "->",
      "function": "normalize",
      "file": "\/var\/www\/html\/app\/vendor\/symfony\/serializer\/Serializer.php",
      "line": 119,
      "args": [
        [
          "object",
          "ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Paginator"
        ],
        [
          "string",
          "jsonld"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "get"
            ],
            "resource_class": [
              "string",
              "App\\Entity\\Project"
            ],
            "input": [
              "null",
              null
            ],
            "output": [
              "null",
              null
            ],
            "request_uri": [
              "string",
              "\/api\/projects"
            ],
            "uri": [
              "string",
              "http:\/\/localhost\/api\/projects"
            ],
            "skip_null_values": [
              "boolean",
              true
            ],
            "resources": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ],
            "exclude_from_cache_key": [
              "array",
              [
                [
                  "string",
                  "resources"
                ],
                [
                  "string",
                  "resources_to_push"
                ]
              ]
            ],
            "resources_to_push": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\Serializer",
      "short_class": "Serializer",
      "class": "Symfony\\Component\\Serializer\\Serializer",
      "type": "->",
      "function": "serialize",
      "file": "\/var\/www\/html\/app\/vendor\/api-platform\/core\/src\/EventListener\/SerializeListener.php",
      "line": 95,
      "args": [
        [
          "object",
          "ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Paginator"
        ],
        [
          "string",
          "jsonld"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "get"
            ],
            "resource_class": [
              "string",
              "App\\Entity\\Project"
            ],
            "input": [
              "null",
              null
            ],
            "output": [
              "null",
              null
            ],
            "request_uri": [
              "string",
              "\/api\/projects"
            ],
            "uri": [
              "string",
              "http:\/\/localhost\/api\/projects"
            ],
            "skip_null_values": [
              "boolean",
              true
            ],
            "resources": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ],
            "exclude_from_cache_key": [
              "array",
              [
                [
                  "string",
                  "resources"
                ],
                [
                  "string",
                  "resources_to_push"
                ]
              ]
            ],
            "resources_to_push": [
              "object",
              "ApiPlatform\\Core\\Serializer\\ResourceList"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\EventListener",
      "short_class": "SerializeListener",
      "class": "ApiPlatform\\Core\\EventListener\\SerializeListener",
      "type": "->",
      "function": "onKernelView",
      "file": "\/var\/www\/html\/app\/vendor\/symfony\/event-dispatcher\/Debug\/WrappedListener.php",
      "line": 117,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\ViewEvent"
        ],
        [
          "string",
          "kernel.view"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher\\Debug",
      "short_class": "WrappedListener",
      "class": "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener",
      "type": "->",
      "function": "__invoke",
      "file": "\/var\/www\/html\/app\/vendor\/symfony\/event-dispatcher\/EventDispatcher.php",
      "line": 230,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\ViewEvent"
        ],
        [
          "string",
          "kernel.view"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher",
      "short_class": "EventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
      "type": "->",
      "function": "callListeners",
      "file": "\/var\/www\/html\/app\/vendor\/symfony\/event-dispatcher\/EventDispatcher.php",
      "line": 59,
      "args": [
        [
          "array",
          [
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ]
          ]
        ],
        [
          "string",
          "kernel.view"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\ViewEvent"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher",
      "short_class": "EventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
      "type": "->",
      "function": "dispatch",
      "file": "\/var\/www\/html\/app\/vendor\/symfony\/event-dispatcher\/Debug\/TraceableEventDispatcher.php",
      "line": 151,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\ViewEvent"
        ],
        [
          "string",
          "kernel.view"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher\\Debug",
      "short_class": "TraceableEventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher",
      "type": "->",
      "function": "dispatch",
      "file": "\/var\/www\/html\/app\/vendor\/symfony\/http-kernel\/HttpKernel.php",
      "line": 162,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\ViewEvent"
        ],
        [
          "string",
          "kernel.view"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "HttpKernel",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->",
      "function": "handleRaw",
      "file": "\/var\/www\/html\/app\/vendor\/symfony\/http-kernel\/HttpKernel.php",
      "line": 79,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ],
        [
          "integer",
          1
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "HttpKernel",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->",
      "function": "handle",
      "file": "\/var\/www\/html\/app\/vendor\/symfony\/http-kernel\/Kernel.php",
      "line": 191,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ],
        [
          "integer",
          1
        ],
        [
          "boolean",
          true
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "Kernel",
      "class": "Symfony\\Component\\HttpKernel\\Kernel",
      "type": "->",
      "function": "handle",
      "file": "\/var\/www\/html\/app\/public\/index.php",
      "line": 25,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ]
      ]
    }
  ]
}

Кажется, проблема в том, что метод getItemIriFromResourceClass () в vendor / api-platform / core / src / Bridge / Symfony / Routing /IriConverter.php получает пустое значение, когда узел не имеет отношения родитель-потомок с другим узлом.

Кто-нибудь знает, как заставить это работать ?. Любая помощь приветствуется. Заранее спасибо.

Ссылки:

...