Skip to content

Commit

Permalink
Introduce TypeReference
Browse files Browse the repository at this point in the history
This can be used to construct a schema without having to link every types together via
a central registry or by using callbacks.

It makes the types more independent and only on runtime they will have to all resolve to
the correct type.
  • Loading branch information
ruudk committed May 24, 2022
1 parent 6906c1d commit 03855e1
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/Type/Definition/NonNull.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public function getInnermostType(): NamedType
$type = $type->getWrappedType();
}

if ($type instanceof TypeReference) {
$type = Schema::resolveType($type);
}

assert($type instanceof NamedType, 'known because we unwrapped all the way down');

return $type;
Expand Down
2 changes: 1 addition & 1 deletion src/Type/Definition/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
* ]);
*
* @phpstan-import-type FieldResolver from Executor
* @phpstan-type InterfaceTypeReference InterfaceType|callable(): InterfaceType
* @phpstan-type InterfaceTypeReference TypeReference|InterfaceType|callable(): TypeReference|InterfaceType
* @phpstan-type ObjectConfig array{
* name?: string|null,
* description?: string|null,
Expand Down
32 changes: 32 additions & 0 deletions src/Type/Definition/TypeReference.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace GraphQL\Type\Definition;

use GraphQL\Error\Error;

class TypeReference extends Type implements NullableType, NamedType
{
public string $name;

public function __construct(string $name)
{
$this->name = $name;
}

public function assertValid() : void
{

}

public function isBuiltInType() : bool
{

}

public function toString() : string
{
return $this->name;
}
}
22 changes: 22 additions & 0 deletions src/Type/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use GraphQL\Type\Definition\NamedType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\TypeReference;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Utils\InterfaceImplementations;
use GraphQL\Utils\TypeInfo;
Expand Down Expand Up @@ -210,6 +211,20 @@ private function collectAllTypes(): array
return $typeMap;
}

/**
* @param array<string, Type&NamedType> $typeMap
*/
private function resolveTypeReference(Type $type, array &$typeMap): Type
{
if (!$type instanceof TypeReference) {
return $type;
}

assert(isset($typeMap[$type->name]), sprintf('Type reference %s not found in type map', $type->name));

return $this->resolveTypeReference($typeMap[$type->name], $typeMap);
}

/**
* Returns a list of directives supported by this schema.
*
Expand Down Expand Up @@ -312,6 +327,13 @@ public function getType(string $name): ?Type
return $this->resolvedTypes[$name] = self::resolveType($type);
}

if ($this->resolvedTypes[$name] instanceof TypeReference) {
unset($this->resolvedTypes[$name]);

return $this->getType($name);
}


return $this->resolvedTypes[$name];
}

Expand Down
117 changes: 117 additions & 0 deletions tests/Type/TypeReferenceTypeLoaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php declare(strict_types=1);

namespace GraphQL\Tests\Type;

use GraphQL\Error\InvariantViolation;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\TypeReference;
use GraphQL\Type\Schema;

final class TypeReferenceTypeLoaderTest extends TypeLoaderTest
{
private array $types;

public function setUp(): void
{
parent::setUp();

$this->types = [
'Node' => new InterfaceType([
'name' => 'Node',
'fields' => [
'id' => Type::string(),
],
'resolveType' => static fn() : ?ObjectType => null,
]),
'Content' => new InterfaceType([
'name' => 'Content',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
],
'resolveType' => static fn() : ?ObjectType => null,
]),
'BlogStory' => new ObjectType([
'name' => 'BlogStory',
'interfaces' => [
new TypeReference('Node'),
new TypeReference('Content'),
],
'fields' => [
'id' => Type::string(),
'title' => Type::string(),
'body' => Type::string(),
],
]),
'Query' => new ObjectType([
'name' => 'Query',
'fields' => [
'latestContent' => new TypeReference('Content'),
'node' => new TypeReference('Node'),
],
]),
'Mutation' => new ObjectType([
'name' => 'Mutation',
'fields' => [
'postStory' => [
'type' => new TypeReference('PostStoryMutation'),
'args' => [
'input' => Type::nonNull(new TypeReference('PostStoryMutationInput')),
'clientRequestId' => Type::string(),
],
],
],
]),
'PostStoryMutation' => new ObjectType([
'name' => 'PostStoryMutation',
'fields' => [
'story' => new TypeReference('BlogStory'),
],
]),
'PostStoryMutationInput' => new InputObjectType([
'name' => 'PostStoryMutationInput',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id(),
],
]),
];
}

public function testWorksWithTypeLoader(): void
{
$schema = new Schema([
'query' => $this->types['Query'],
'mutation' => $this->types['Mutation'],
'typeLoader' => fn (string $name) => $this->types[$name] ?? null,
]);

$node = $schema->getType('Node');
self::assertSame($this->types['Node'], $node);

$content = $schema->getType('Content');
self::assertSame($this->types['Content'], $content);

$blogStory = $schema->getType('BlogStory');
self::assertSame($this->types['BlogStory'], $blogStory);
self::assertInstanceOf(ObjectType::class, $blogStory);
self::assertCount(2, $blogStory->getInterfaces());

$postStoryMutation = $schema->getType('PostStoryMutation');
self::assertSame($this->types['PostStoryMutation'], $postStoryMutation);
self::assertInstanceOf(ObjectType::class, $postStoryMutation);
$field = $postStoryMutation->getField('story');
self::assertSame($blogStory, $field->getType());

$input = $schema->getType('PostStoryMutationInput');
self::assertSame($this->types['PostStoryMutationInput'], $input);

$result = $schema->isSubType($node, $schema->getType('BlogStory'));
self::assertTrue($result);
}
}

0 comments on commit 03855e1

Please sign in to comment.