У меня есть абстрактный класс "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 получает пустое значение, когда узел не имеет отношения родитель-потомок с другим узлом.
Кто-нибудь знает, как заставить это работать ?. Любая помощь приветствуется. Заранее спасибо.
Ссылки: