title: "Unable to generate an IRI" error in Api Platform when Many-To-Many, Self-referencing relation is present in abstract superclass Entity
body: <p>I have an abstract entity "Node" superclass, with the following code that is the base for four other entities subclasses. I want to nest them in any order to create a loose hierarchy where any kind of node can be parent or child of any other type, and any node can have multiple parents.</p>
  
  <p><strong>src/Entity/Node.php</strong>
      
  
  <pre><code>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-&gt;parentNodes = new ArrayCollection();
          $this-&gt;childNodes = new ArrayCollection();
      }
  
      public function getId(): ?int
      {
          return $this-&gt;id;
      }
  
      /**
       * @return Collection|self[]
       */
      public function getParentNodes(): Collection
      {
          return $this-&gt;parentNodes;
      }
  
      public function addParentNode(self $parentNode): self
      {
          if (!$this-&gt;parentNodes-&gt;contains($parentNode)) {
              $this-&gt;parentNodes[] = $parentNode;
          }
  
          return $this;
      }
  
      public function removeParentNode(self $parentNode): self
      {
          if ($this-&gt;parentNodes-&gt;contains($parentNode)) {
              $this-&gt;parentNodes-&gt;removeElement($parentNode);
          }
  
          return $this;
      }
  
      /**
       * @return Collection|self[]
       */
      public function getChildNodes(): Collection
      {
          return $this-&gt;childNodes;
      }
  
      public function addChildNode(self $childNode): self
      {
          if (!$this-&gt;childNodes-&gt;contains($childNode)) {
              $this-&gt;childNodes[] = $childNode;
              $childNode-&gt;addParentNode($this);
          }
  
          return $this;
      }
  
      public function removeChildNode(self $childNode): self
      {
          if ($this-&gt;childNodes-&gt;contains($childNode)) {
              $this-&gt;childNodes-&gt;removeElement($childNode);
              $childNode-&gt;removeParentNode($this);
          }
  
          return $this;
      }
  }
  </code></pre>
  
  <p>This class is extended by the four other entities, that are exposed as an ApiResource using Api Platform in a Symfony 5 project. All of them have almost the same code:</p>
  
  <p><strong>src/Entity/Project.php</strong></p>
  
  <pre><code>&lt;?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-&gt;id;
      }
  }
  </code></pre>
  
  <p>The exposed entities are shown in the Api Platform dashboard and can be queried successfully if the abstract Node entity does not have a self parent-child relation. In other words, if I remove all get/add/removeXXXXNodes methods and related variables from Node class the API works.</p>
  
  <p>But if the relationship is present, then a 400 error occurs:</p>
  
  <p><strong>GET <a href="http://localhost/api/projects" rel="nofollow noreferrer">http://localhost/api/projects</a></strong></p>
  
  <pre><code>{
    "@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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "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": "-&gt;",
        "function": "handle",
        "file": "\/var\/www\/html\/app\/public\/index.php",
        "line": 25,
        "args": [
          [
            "object",
            "Symfony\\Component\\HttpFoundation\\Request"
          ]
        ]
      }
    ]
  }
  </code></pre>
  
  <p>The problem seems to be that getItemIriFromResourceClass() method in vendor/api-platform/core/src/Bridge/Symfony/Routing/IriConverter.php recives an empty value when a Node does not have a parent-child relation with other node.</p>
  
  <p>Does anybody know how to make this work?. Any help is appreciated. Thanks in advance.</p>
  
  <p>References:</p>
  
  <ul>
  <li><a href="https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/association-mapping.html#many-to-many-self-referencing" rel="nofollow noreferrer">ManyToMany self referencing relation in Doctrine</a></li>
  <li><a href="https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/inheritance-mapping.html" rel="nofollow noreferrer">Inheritance Mapping in Doctrine</a></li>
  <li><a href="https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/tutorials/composite-primary-keys.html#use-case-2-simple-derived-identity" rel="nofollow noreferrer">Simple Derived Identity</a></li>
  </ul>