Skip to content

[GraphQL] Item query crashes with MappingException on resource-typed properties that are not Doctrine associations (regression 4.3.3 → 4.3.10) #8292

@watsab

Description

@watsab

API Platform version(s) affected: 4.3.10 (regression — works fine on 4.3.3)

Description

A GraphQL item query crashes with a Doctrine MappingException when the
queried resource has a public property typed as a resource class but not mapped
as a Doctrine association
(e.g. a transient/runtime-only helper, or a self
reference).

LinkFactory::createLinksFromRelations() builds a relation Link for any
property whose native type resolves to a resource class — it does not check
whether that property is an actual Doctrine association. For a GraphQL root item
query
, Doctrine\Common\State\LinksHandlerTrait::getLinks() keeps links whose
toClass === resourceClass (self references). The ORM
Doctrine\Orm\State\LinksHandlerTrait::handleLinks() then calls
ClassMetadata::getAssociationMapping($link->getFromProperty()) without a
hasAssociation() guard
, which throws:

No mapping found for field 'relatedButNotMapped' on class 'App\Entity\Foo'.

So the metadata layer decides the property is a relation purely from its PHP
type, while the ORM state layer assumes that relation is a mapped association.
The two assumptions are inconsistent, and the mismatch surfaces as an unhandled
exception.

This worked on 4.3.3 and broke after upgrading to 4.3.10 with no changelog entry
describing the change. Prime suspects are #8236 / #8239 (4.3.9, circular reference
/ nested resource link handling) and #8237 (item query provider dispatch), but I
have not bisected the exact patch release.

How to reproduce

Minimal resource — a public getter returning the resource type, not a Doctrine
association:

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\Query;
use Doctrine\ORM\Mapping as ORM;

#[ApiResource(graphQlOperations: [new Query()])]
#[ORM\Entity]
class Foo
{
    #[ORM\Id, ORM\GeneratedValue, ORM\Column]
    public ?int $id = null;

    // NOT persisted, NOT a Doctrine association — just a transient resource-typed property
    private ?Foo $relatedButNotMapped = null;

    public function getRelatedButNotMapped(): ?Foo
    {
        return $this->relatedButNotMapped;
    }

    public function setRelatedButNotMapped(?Foo $foo): void
    {
        $this->relatedButNotMapped = $foo;
    }
}
Run a GraphQL item query:


{
  foo(id: "/foos/1") {
    id
  }
}
Possible Solution

Guard the association lookup in
Doctrine\Orm\State\LinksHandlerTrait::handleLinks(): skip (or raise a clear,
actionable error) when the link's fromProperty is not an actual association:


if ($link->getFromProperty() && !$link->getToProperty()) {
    if (!$fromClassMetadata->hasAssociation($link->getFromProperty())) {
        continue; // resource-typed but not a Doctrine association — nothing to join on
    }
    $associationMapping = $fromClassMetadata->getAssociationMapping($link->getFromProperty());
    // ...
}

Alternatively, LinkFactory::createLinksFromRelations() could skip non-readable
properties, or there could be a documented, first-class way to exclude a
resource-typed property from relation-link generation (currently
#[ApiProperty(readable: false, writable: false)] and #[Ignore] do not
remove the property from the name collection used by the link factory — the only
working workaround is a PropertyMetadataFactory decorator clearing the
nativeType).

Additional Context

PHP 8.5, Symfony 7, Doctrine ORM 3 / DBAL 4, PostgreSQL.
Relevant source (monorepo paths):
src/Metadata/Resource/Factory/LinkFactory.php → createLinksFromRelations() (no association check)
src/Doctrine/Common/State/LinksHandlerTrait.php → getLinks() (keeps toClass === resourceClass self references for GraphQL root item queries)
src/Doctrine/Orm/State/LinksHandlerTrait.php → handleLinks() (unguarded getAssociationMapping())

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions