Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/attributes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,45 @@ itself is resolved as its nearest exposed parent class. A subclass which is
exposed is resolved as itself, with its own entity attribute, wherever its
rows appear.

Each exposed class has its own type, and the types are not related: a
subclass's type is not an implementation of an interface of its parent's.
A connection of the parent therefore has the parent's type, and a client
cannot ask it for a subclass's own fields, not even with a fragment on the
subclass's type. Expose a subclass's fields through a `computed field
<computed-fields.html#entity-types>`_ of the subclass's type on the parent,
which returns the entity when it is of the subclass:

.. code-block:: php

#[GraphQL\Entity]
#[ORM\Entity]
#[ORM\InheritanceType('SINGLE_TABLE')]
class Animal
{
#[GraphQL\ComputedField(type: Dog::class)]
public function getAsDog(): Dog|null
{
return $this instanceof Dog ? $this : null;
}
}

#[GraphQL\Entity]
#[ORM\Entity]
class Dog extends Animal
{
#[GraphQL\Field]
#[ORM\Column(type: 'string')]
private string $breed;
}

.. code-block:: graphql

{ animals { edges { node { name asDog { breed } } } } }

``asDog`` is null for an animal which is not a dog. The subclass must be
exposed in the group. A subclass inherits the computed field, so a dog's
``asDog`` is the dog itself.


Field
=====
Expand Down
3 changes: 2 additions & 1 deletion docs/computed-fields.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ returns a list of them, such as an array or a Doctrine ``Collection``.
}

This exposes a relation which is not mapped as an association, or which is
derived from one. The entity may be of the same class as the field's own
derived from one, or a subclass's own fields through its parent's type; see
`Inheritance <attributes.html#inheritance>`_. The entity may be of the same class as the field's own
entity, and two entities may have computed fields of each other's types. An
entity of a class which is not exposed in the group throws
``ApiSkeletons\Doctrine\ORM\GraphQL\Exception\Metadata`` when the metadata
Expand Down
10 changes: 10 additions & 0 deletions test/Entity/InheritanceAnimal.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,14 @@ public function getToys(): Collection
{
return $this->toys;
}

/**
* A subclass's own fields, through a field of the root's type: the
* animal as a dog, or null
*/
#[GraphQL\ComputedField(type: InheritanceDog::class, group: 'InheritanceSubclass')]
public function getAsDog(): InheritanceDog|null
{
return $this instanceof InheritanceDog ? $this : null;
}
}
28 changes: 28 additions & 0 deletions test/Feature/Resolve/InheritanceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,34 @@ public function testExposedSubclassIsResolvedAsItself(): void
], $data['dogs']['edges']);
}

/**
* A computed field of the subclass's type gives a subclass's own fields
* through the root's type, where a row is of the root's type
*/
#[DataProvider('extractionProvider')]
public function testSubclassFieldsThroughAComputedField(bool $batchAssociations, bool $extractByValue): void
{
$data = $this->execute(
['group' => 'InheritanceSubclass', 'batchAssociations' => $batchAssociations, 'extractByValue' => $extractByValue],
['animals' => InheritanceAnimal::class, 'toys' => InheritanceToy::class],
'{
animals { edges { node { name asDog { breed } } } }
toys { edges { node { name owner { name asDog { breed } } } } }
}',
);

$this->assertSame([
['node' => ['name' => 'Generic', 'asDog' => null]],
['node' => ['name' => 'Rex', 'asDog' => ['breed' => 'Labrador']]],
], $data['animals']['edges']);

$this->assertSame([
['node' => ['name' => 'Ball', 'owner' => ['name' => 'Generic', 'asDog' => null]]],
['node' => ['name' => 'Bone', 'owner' => ['name' => 'Rex', 'asDog' => ['breed' => 'Labrador']]]],
['node' => ['name' => 'Rope', 'owner' => ['name' => 'Rex', 'asDog' => ['breed' => 'Labrador']]]],
], $data['toys']['edges']);
}

public function testExposedClass(): void
{
$dog = new InheritanceDog('Rex', 'Labrador');
Expand Down
Loading