2

我有一个抽象实体“节点”超类,下面的代码是其他四个实体子类的基础。我想以任何顺序嵌套它们以创建一个松散的层次结构,其中任何类型的节点都可以是任何其他类型的父节点或子节点,并且任何节点都可以有多个父节点。

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

此类由其他四个实体扩展,它们在 Symfony 5 项目中使用 Api 平台作为 ApiResource 公开。它们都有几乎相同的代码:

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 Platform Dashboard 中,如果抽象 Node 实体没有自父子关系,可以查询成功。换句话说,如果我从 Node 类中删除所有 get/add/removeXXXXNodes 方法和相关变量,API 就可以工作。

但如果存在关系,则会出现 400 错误:

获取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"
        ]
      ]
    }
  ]
}

问题似乎是 vendor/api-platform/core/src/Bridge/Symfony/Routing/IriConverter.php 中的 getItemIriFromResourceClass() 方法在节点与其他节点没有父子关系时接收空值。

有谁知道如何使这项工作?任何帮助表示赞赏。提前致谢。

参考:

4

1 回答 1

1

从子类中删除属性$id和函数。getId()它对我有用。

于 2020-08-25T00:34:13.960 回答