Skip to content

Access to subressource results in error 500: Can't get a way to read the property "id" in class "Doctrine\ORM\PersistentCollection". #6909

@xalopp

Description

@xalopp

API Platform version(s) affected: 4.0.11 and later

Description

Accessing a defined subresource (https://localhost/tags/5/color/1) on doctrine entities results in error 500:
Can't get a way to read the property "id" in class "Doctrine\ORM\PersistentCollection".

How to reproduce

  • Start a new API-Platform Project from API platform template
  • Create the entities Tag and TagColor with docker compose exec -ti php bin/console make:entity
  • Add Subresource related Attributes as described in the documentation.
  • Add some some tag colors and tag in the database via the admin interface
  • Accessing the TagColor collection works curl -k https://localhost/tags/1/color
    {
      "@context": "/contexts/TagColor",
      "@id": "/tags/1/color",
      "@type": "Collection",
      "totalItems": 1,
      "member": [
        {
          "@id": "/tag_colors/1",
          "@type": "TagColor",
          "id": 1,
          "color": "red",
          "tags": [
            "/tags/1",
            "/tags/5"
          ]
        }
      ]
    }
    
  • When accessing a non existing subresource, curl -k https://localhost/tags/5/color/99999, a 404 is returned as expected
  • When accessing a existing resource, curl -k https://localhost/tags/1/color/1, a error 500 is returned with this stack trace:
{
  "@context": "/contexts/Error",
  "@id": "/errors/500",
  "@type": "Error",
  "title": "An error occurred",
  "detail": "Can't get a way to read the property \"id\" in class \"Doctrine\\ORM\\PersistentCollection\".",
  "status": 500,
  "type": "/errors/500",
  "trace": [
    {
      "file": "/app/vendor/symfony/property-access/PropertyAccessor.php",
      "line": 334,
      "function": "readProperty",
      "class": "Symfony\\Component\\PropertyAccess\\PropertyAccessor",
      "type": "->"
    },
    {
      "file": "/app/vendor/symfony/property-access/PropertyAccessor.php",
      "line": 104,
      "function": "readPropertiesUntil",
      "class": "Symfony\\Component\\PropertyAccess\\PropertyAccessor",
      "type": "->"
    },
    {
      "file": "/app/vendor/api-platform/metadata/IdentifiersExtractor.php",
      "line": 106,
      "function": "getValue",
      "class": "Symfony\\Component\\PropertyAccess\\PropertyAccessor",
      "type": "->"
    },
    {
      "file": "/app/vendor/api-platform/metadata/IdentifiersExtractor.php",
      "line": 86,
      "function": "getIdentifierValue",
      "class": "ApiPlatform\\Metadata\\IdentifiersExtractor",
      "type": "->"
    },
    {
      "file": "/app/vendor/api-platform/metadata/IdentifiersExtractor.php",
      "line": 56,
      "function": "getIdentifiersFromOperation",
      "class": "ApiPlatform\\Metadata\\IdentifiersExtractor",
      "type": "->"
    },
    {
      "file": "/app/vendor/api-platform/symfony/Routing/IriConverter.php",
      "line": 187,
      "function": "getIdentifiersFromItem",
      "class": "ApiPlatform\\Metadata\\IdentifiersExtractor",
      "type": "->"
    },
    {
      "file": "/app/vendor/api-platform/symfony/Routing/IriConverter.php",
      "line": 168,
      "function": "generateSymfonyRoute",
      "class": "ApiPlatform\\Symfony\\Routing\\IriConverter",
      "type": "->"
    },
    {
      "file": "/app/vendor/api-platform/jsonld/Serializer/ItemNormalizer.php",
      "line": 127,
      "function": "getIriFromResource",
      "class": "ApiPlatform\\Symfony\\Routing\\IriConverter",
      "type": "->"
    },
    {
      "file": "/app/vendor/symfony/serializer/Debug/TraceableNormalizer.php",
      "line": 58,
      "function": "normalize",
      "class": "ApiPlatform\\JsonLd\\Serializer\\ItemNormalizer",
      "type": "->"
    },
    {
      "file": "/app/vendor/symfony/serializer/Serializer.php",
      "line": 159,
      "function": "normalize",
      "class": "Symfony\\Component\\Serializer\\Debug\\TraceableNormalizer",
      "type": "->"
    },
    {
      "file": "/app/vendor/symfony/serializer/Serializer.php",
      "line": 138,
      "function": "normalize",
      "class": "Symfony\\Component\\Serializer\\Serializer",
      "type": "->"
    },
    {
      "file": "/app/vendor/symfony/serializer/Debug/TraceableSerializer.php",
      "line": 47,
      "function": "serialize",
      "class": "Symfony\\Component\\Serializer\\Serializer",
      "type": "->"
    },
    {
      "file": "/app/vendor/api-platform/state/Processor/SerializeProcessor.php",
      "line": 74,
      "function": "serialize",
      "class": "Symfony\\Component\\Serializer\\Debug\\TraceableSerializer",
      "type": "->"
    },
    {
      "file": "/app/vendor/api-platform/state/Processor/WriteProcessor.php",
      "line": 51,
      "function": "process",
      "class": "ApiPlatform\\State\\Processor\\SerializeProcessor",
      "type": "->"
    },
    {
      "file": "/app/vendor/api-platform/symfony/Controller/MainController.php",
      "line": 112,
      "function": "process",
      "class": "ApiPlatform\\State\\Processor\\WriteProcessor",
      "type": "->"
    },
    {
      "file": "/app/vendor/symfony/http-kernel/HttpKernel.php",
      "line": 181,
      "function": "__invoke",
      "class": "ApiPlatform\\Symfony\\Controller\\MainController",
      "type": "->"
    },
    {
      "file": "/app/vendor/symfony/http-kernel/HttpKernel.php",
      "line": 76,
      "function": "handleRaw",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->"
    },
    {
      "file": "/app/vendor/symfony/http-kernel/Kernel.php",
      "line": 197,
      "function": "handle",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->"
    },
    {
      "file": "/app/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php",
      "line": 35,
      "function": "handle",
      "class": "Symfony\\Component\\HttpKernel\\Kernel",
      "type": "->"
    },
    {
      "file": "/app/vendor/autoload_runtime.php",
      "line": 29,
      "function": "run",
      "class": "Symfony\\Component\\Runtime\\Runner\\Symfony\\HttpKernelRunner",
      "type": "->"
    },
    {
      "file": "/app/public/index.php",
      "line": 5,
      "function": "require_once"
    }
  ],
  "description": "Can't get a way to read the property \"id\" in class \"Doctrine\\ORM\\PersistentCollection\"."
}

My entities looks like this:

Tag.php

<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use App\Repository\TagRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: TagRepository::class)]
#[ApiResource]
class Tag
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 32)]
    private ?string $text = null;

    #[ORM\ManyToOne(inversedBy: 'tags')]
    #[ORM\JoinColumn(nullable: false)]
    #[Link(fromProperty: 'tags')]
    private ?TagColor $tagColor = null;

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

    public function getText(): ?string
    {
        return $this->text;
    }

    public function setText(string $text): static
    {
        $this->text = $text;

        return $this;
    }

    public function getTagColor(): ?TagColor
    {
        return $this->tagColor;
    }

    public function setTagColor(?TagColor $tagColor): static
    {
        $this->tagColor = $tagColor;

        return $this;
    }
}

TagColor.php

<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
use App\Repository\TagColorRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: TagColorRepository::class)]
#[ApiResource]
#[ApiResource(
    uriTemplate: '/tags/{tagId}/color/{id}',
    uriVariables: [
        'tagId' => new Link(fromClass: Tag::class, toProperty: 'tags'),
        'id' => new Link(fromClass: TagColor::class),
    ],
    operations: [ new Get() ]
)]
#[ApiResource(
    uriTemplate: '/tags/{tagId}/color',
    uriVariables: [
        'tagId' => new Link(fromClass: Tag::class, toProperty: 'tags'),
    ],
    operations: [ new GetCollection() ]
)]
class TagColor
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 16)]
    private ?string $color = null;

    /**
     * @var Collection<int, Tag>
     */
    #[ORM\OneToMany(mappedBy: 'tagColor', targetEntity: Tag::class, orphanRemoval: true)]
    private Collection $tags;

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

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

    public function getColor(): ?string
    {
        return $this->color;
    }

    public function setColor(string $color): static
    {
        $this->color = $color;

        return $this;
    }

    /**
     * @return Collection<int, Tag>
     */
    public function getTags(): Collection
    {
        return $this->tags;
    }

    public function addTag(Tag $tag): static
    {
        if (!$this->tags->contains($tag)) {
            $this->tags->add($tag);
            $tag->setTagColor($this);
        }

        return $this;
    }

    public function removeTag(Tag $tag): static
    {
        if ($this->tags->removeElement($tag)) {
            // set the owning side to null (unless already changed)
            if ($tag->getTagColor() === $this) {
                $tag->setTagColor(null);
            }
        }

        return $this;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions