Skip to content

[GraphQl] Error in query resolver with Inheritance  #5750

@charlieandroid55

Description

@charlieandroid55

API Platform version(s) affected: 3.1.12

Description
I have in my data model the User entity which presents the InheritanceType annotation in order to apply inheritance, then I have a child class called Patient which extends from User, when I perform a graphql query to the users table with a patient id it gives me the following error "Resolve only handles items of class User but retrieved item is of class Patient.".

How to reproduce
Step 1: Create model

#[ApiResource(
    normalizationContext: ['groups' => ['user_read', 'read'], 'enabled_max_depth' => true],
    denormalizationContext: ['groups' => ['user_write']],
    forceEager: false,
    paginationType: 'page',
)]
#[ORM\Entity]
#[ORM\InheritanceType('JOINED')]
#[ORM\DiscriminatorColumn(name: 'discr', type: 'string')]
#[ORM\DiscriminatorMap(['user' => User::class, 'patient' => Patient::class])]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: Types::INTEGER)]
    protected ?int $id = null;

    #[ORM\Column(type: Types::STRING, length: 50, nullable: true)]
    #[Groups(['user_read', 'user_write'])]
     private ?string $name;
   
    #[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
    #[Groups(['user_write'])]
    private ?string $password;

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

    public function getPassword(): ?string
    {
        return $this->password;
    }

    public function setPassword(?string $password): self
    {
        $this->password = $password;

        return $this;
    }
    public function eraseCredentials(): void
    {
    }
    public function getRoles(): array
    {
            return  ['ROLE_USER'];
    }
}

#[ApiResource(
    normalizationContext: ['groups' => ['patient_read', 'read'], 'enabled_max_depth' => true],
    denormalizationContext: ['groups' => ['patient_write']],
     forceEager: false,
    paginationType: 'page',
)]
#[ORM\Entity]
class Patient extends User
{
    ...patient fields
    public function getRoles(): array
    {
            return  ['ROLE_PATIENT'];
    }
}

Step 2: Create one user record & one patient record in BD. As we can see, there is a relationship between the patient and user ids.

Step 3: Attempting to get user data given a patient id, in this case the id = 2 belongs to the created patient.

query findUser {
  user(id: "/api/users/2") {
    id
    name
  }
}

Possible Solution
ApiPlatform\GraphQl\Resolver\Factory\ItemResolverFactor:109
Current:

if ($resourceClass !== $itemClass) {
           throw new \UnexpectedValueException(sprintf($errorMessage, (new \ReflectionClass($resourceClass))->getShortName(), (new \ReflectionClass($itemClass))->getShortName()));
        }

Solution:

//if the classes of the item and the resource differ then we check that the found item is also not an instance of $resourceClass
 if ($resourceClass !== $itemClass && !$item instanceof $resourceClass) {
            throw new \UnexpectedValueException(sprintf($errorMessage, (new \ReflectionClass($resourceClass))->getShortName(), (new \ReflectionClass($itemClass))->getShortName()));
        }

Additional Context
Error example:
Screenshot 2023-08-15 144506

More info:
In my case, I could search directly in the patient table, but since I only want to show the typical user data, not the patient-specific ones, it is more convenient to search in their source, in addition to making the logic more complex in case of having more instances that inherit from the User class.

Benefits: I can obtain an existing user instance given a user IRI with the id of a patient as we see in the Step 3, and I can also search for a user with the IRI of a patient class. In the end patient is also a user instance.

Example

query findUser {
  user(id: "/api/patients/2") {
    id
    name
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions