Skip to content

Commit 2b35b8e

Browse files
authored
Update DefiningSchema.md
1 parent ccb47fc commit 2b35b8e

File tree

1 file changed

+67
-33
lines changed

1 file changed

+67
-33
lines changed

docs/DefiningSchema.md

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ final class Starship extends Type
116116

117117
Fields are defined using `getFieldDefinition` function. This is (apart from secondary performance advantages) done because of a possible cyclic dependency across fields. Fields are therefore loaded lazily using this method, instead of passing FieldSet directly to constructor.
118118

119-
The `validateNonNullValue` function allows the programmer to check if the parent resolver passed a correct value for this type. When false an `InvalidValue` is thrown.
119+
The `validateNonNullValue` function allows the programmer to check if the parent resolver passed a correct value for this type. The argument is any value resolved from parent resolver, except `null` which has a special meaning for the GraphQL. When the function returns `false` an `InvalidValue` is thrown.
120120

121121
#### Implementing interface
122122

@@ -150,6 +150,8 @@ interface Character {
150150

151151
namespace App\Type;
152152

153+
use App\Dto\Human as HumanDto;
154+
use App\Dto\Droid as DroidDto;
153155
use App\Type\Episode;
154156
use Graphpinator\Typesystem\Attribute\Description;
155157
use Graphpinator\Typesystem\Container;
@@ -160,40 +162,53 @@ use Graphpinator\Typesystem\InterfaceType;
160162
#[Description('My Character interface')]
161163
final class Character extends InterfaceType
162164
{
163-
protected const NAME = 'Character';
164-
165-
public function __construct(
166-
private Episode $episode,
167-
)
168-
{
169-
parent::__construct();
170-
}
171-
172-
protected function getFieldDefinition() : FieldSet
173-
{
174-
return new FieldSet([
175-
Field::create(
176-
'id',
177-
Container::ID()->notNull(),
178-
),
179-
Field::create(
180-
'name',
181-
Container::String()->notNull(),
182-
),
183-
Field::create(
184-
'friends',
185-
$this->list(), // nullable list with nullable contents
186-
),
187-
Field::create(
188-
'appearsIn',
189-
$this->episode->list()->notNull(), // not-null list with nullable contents
190-
),
191-
]);
192-
}
165+
protected const NAME = 'Character';
166+
167+
public function __construct(
168+
private Episode $episode,
169+
private CharacterAccessor $characterAccessor,
170+
)
171+
{
172+
parent::__construct();
173+
}
174+
175+
protected function getFieldDefinition() : FieldSet
176+
{
177+
return new FieldSet([
178+
Field::create(
179+
'id',
180+
Container::ID()->notNull(),
181+
),
182+
Field::create(
183+
'name',
184+
Container::String()->notNull(),
185+
),
186+
Field::create(
187+
'friends',
188+
$this->list(), // nullable list with nullable contents
189+
),
190+
Field::create(
191+
'appearsIn',
192+
$this->episode->list()->notNull(), // not-null list with nullable contents
193+
),
194+
]);
195+
}
196+
197+
public function createResolvedValue(mixed $rawValue) : TypeIntermediateValue
198+
{
199+
return match ($rawValue::class) {
200+
HumanDto::class => new TypeIntermediateValue($this->characterAccessor->getHumanType(), $rawValue),
201+
DroidDto::class => new TypeIntermediateValue($this->characterAccessor->getDroidType(), $rawValue),
202+
};
203+
}
193204
}
194205
```
195206

196-
Fields are defined using `getFieldDefinition` function using the same concept as for defining Types. The only difference is the absence of a resolve function, because Interfaces cannot be resolved directly. Field definitions are used to validate contract with Types implementing this interface.
207+
Fields are defined using `getFieldDefinition` function using the same concept as for defining `Type`. The only difference is the absence of a resolve function, because Interfaces cannot be resolved directly. Field definitions are used to validate contract with Types implementing this interface.
208+
209+
Additionally, the `createResolvedValue` function must be implemented to decide which concrete type the resolved value belongs to. The argument is any value resolved from parent resolver, except `null` which has a special meaning for the GraphQL. The result of this method is a structure of the concrete type and the undelying value which will be passed into it.
210+
211+
This may cause trouble as the cyclic dependency appears, the contrete types need the interface in order to implement it and the interface need the conrete types in order to resolve the value. This is a common scenario in the GraphQL, as the types reference each other and can resolve in cycles. In this example we worked around it by passing an accessor as a constructor dependency instead of the types directly. The implementation of the accessor depends on which framework and/or DI solution you use.
197212

198213
Interfaces can also implement other interfaces using the same procedure as types - passing `InterfaceSet` into parent constructor.
199214
In this case the fields from parent interface are automatically included and there is no need to repeat the field definitions in the child, unless you wish to be more specific - but keep in mind that covariance/contravariance rules must be applied.
@@ -211,25 +226,44 @@ union SearchResult = Human | Droid | Starship
211226

212227
namespace App\Type;
213228

229+
use App\Dto\Human as HumanDto;
230+
use App\Dto\Droid as DroidDto;
231+
use App\Dto\Starship as StarshipDto;
214232
use App\Type\Human;
215233
use App\Type\Droid;
216234
use App\Type\Starship;
217235
use Graphpinator\Typesystem\Attribute\Description;
218236
use Graphpinator\Typesystem\TypeSet;
219237
use Graphpinator\Typesystem\UnionType;
238+
use Graphpinator\Value\TypeIntermediateValue;
220239

221240
#[Description('My SearchResult union')]
222241
final class SearchResult extends UnionType
223242
{
224243
protected const NAME = 'SearchResult';
225244

226-
public function __construct(Human $human, Droid $droid, Starship $starship)
245+
public function __construct(
246+
private Human $human,
247+
private Droid $droid,
248+
private Starship $starship,
249+
)
227250
{
228251
parent::__construct(new TypeSet([$human, $droid, $starship]));
229252
}
253+
254+
public function createResolvedValue(mixed $rawValue) : TypeIntermediateValue
255+
{
256+
return match ($rawValue::class) {
257+
HumanDto::class => new TypeIntermediateValue($this->human, $rawValue),
258+
DroidDto::class => new TypeIntermediateValue($this->droid, $rawValue),
259+
StarshipDto::class => new TypeIntermediateValue($this->starship, $rawValue),
260+
};
261+
}
230262
}
231263
```
232264

265+
In similar fashion as for the `Interface`, the `createResolvedValue` function must be implemented to decide which type the resolved value belongs to.
266+
233267
### Scalar
234268

235269
### Enum

0 commit comments

Comments
 (0)