-
-
Notifications
You must be signed in to change notification settings - Fork 932
Closed
Description
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
andTagColor
withdocker 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 workscurl -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
Labels
No labels