Skip to content

Commit 8ff20bd

Browse files
committed
Implement support for interfaces implementing interfaces
Closes webonyx#728
1 parent 8795c03 commit 8ff20bd

18 files changed

+183
-55
lines changed

src/Language/AST/InterfaceTypeDefinitionNode.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class InterfaceTypeDefinitionNode extends Node implements TypeDefinitionNode
1515
/** @var NodeList<DirectiveNode>|null */
1616
public $directives;
1717

18+
/** @var NodeList<InterfaceTypeDefinitionNode>|null */
19+
public $interfaces;
20+
1821
/** @var NodeList<FieldDefinitionNode>|null */
1922
public $fields;
2023

src/Language/AST/InterfaceTypeExtensionNode.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class InterfaceTypeExtensionNode extends Node implements TypeExtensionNode
1515
/** @var NodeList<DirectiveNode>|null */
1616
public $directives;
1717

18+
/** @var NodeList<InterfaceTypeDefinitionNode>|null */
19+
public $interfaces;
20+
1821
/** @var NodeList<FieldDefinitionNode>|null */
1922
public $fields;
2023
}

src/Language/Parser.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,12 +1346,14 @@ private function parseInterfaceTypeDefinition() : InterfaceTypeDefinitionNode
13461346
$description = $this->parseDescription();
13471347
$this->expectKeyword('interface');
13481348
$name = $this->parseName();
1349+
$interfaces = $this->parseImplementsInterfaces();
13491350
$directives = $this->parseDirectives(true);
13501351
$fields = $this->parseFieldsDefinition();
13511352

13521353
return new InterfaceTypeDefinitionNode([
13531354
'name' => $name,
13541355
'directives' => $directives,
1356+
'interfaces' => $interfaces,
13551357
'fields' => $fields,
13561358
'loc' => $this->loc($start),
13571359
'description' => $description,
@@ -1622,6 +1624,7 @@ private function parseInterfaceTypeExtension() : InterfaceTypeExtensionNode
16221624
$this->expectKeyword('extend');
16231625
$this->expectKeyword('interface');
16241626
$name = $this->parseName();
1627+
$interfaces = $this->parseImplementsInterfaces();
16251628
$directives = $this->parseDirectives(true);
16261629
$fields = $this->parseFieldsDefinition();
16271630
if (count($directives) === 0 &&
@@ -1633,6 +1636,7 @@ private function parseInterfaceTypeExtension() : InterfaceTypeExtensionNode
16331636
return new InterfaceTypeExtensionNode([
16341637
'name' => $name,
16351638
'directives' => $directives,
1639+
'interfaces' => $interfaces,
16361640
'fields' => $fields,
16371641
'loc' => $this->loc($start),
16381642
]);

src/Language/Printer.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ function (InterfaceTypeDefinitionNode $def) : string {
309309
[
310310
'interface',
311311
$def->name,
312+
$this->wrap('implements ', $this->join($def->interfaces, ' & ')),
312313
$this->join($def->directives, ' '),
313314
$this->block($def->fields),
314315
],
@@ -401,6 +402,7 @@ function (InterfaceTypeDefinitionNode $def) : string {
401402
[
402403
'extend interface',
403404
$def->name,
405+
$this->wrap('implements ', $this->join($def->interfaces, ' & ')),
404406
$this->join($def->directives, ' '),
405407
$this->block($def->fields),
406408
],

src/Language/Visitor.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,15 +151,15 @@ class Visitor
151151
NodeKind::OBJECT_TYPE_DEFINITION => ['description', 'name', 'interfaces', 'directives', 'fields'],
152152
NodeKind::FIELD_DEFINITION => ['description', 'name', 'arguments', 'type', 'directives'],
153153
NodeKind::INPUT_VALUE_DEFINITION => ['description', 'name', 'type', 'defaultValue', 'directives'],
154-
NodeKind::INTERFACE_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'],
154+
NodeKind::INTERFACE_TYPE_DEFINITION => ['description', 'name', 'interfaces', 'directives', 'fields'],
155155
NodeKind::UNION_TYPE_DEFINITION => ['description', 'name', 'directives', 'types'],
156156
NodeKind::ENUM_TYPE_DEFINITION => ['description', 'name', 'directives', 'values'],
157157
NodeKind::ENUM_VALUE_DEFINITION => ['description', 'name', 'directives'],
158158
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'],
159159

160160
NodeKind::SCALAR_TYPE_EXTENSION => ['name', 'directives'],
161161
NodeKind::OBJECT_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'],
162-
NodeKind::INTERFACE_TYPE_EXTENSION => ['name', 'directives', 'fields'],
162+
NodeKind::INTERFACE_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'],
163163
NodeKind::UNION_TYPE_EXTENSION => ['name', 'directives', 'types'],
164164
NodeKind::ENUM_TYPE_EXTENSION => ['name', 'directives', 'values'],
165165
NodeKind::INPUT_OBJECT_TYPE_EXTENSION => ['name', 'directives', 'fields'],
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GraphQL\Type\Definition;
6+
7+
/*
8+
export type GraphQLImplementingType =
9+
GraphQLObjectType |
10+
GraphQLInterfaceType;
11+
*/
12+
13+
interface ImplementingType {
14+
public function implementsInterface(InterfaceType $interfaceType) : bool;
15+
16+
/**
17+
* @return InterfaceType[]
18+
*/
19+
public function getInterfaces() : array
20+
}

src/Type/Definition/InterfaceType.php

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
use GraphQL\Error\InvariantViolation;
88
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
99
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
10+
use GraphQL\Type\Schema;
1011
use GraphQL\Utils\Utils;
1112
use function is_callable;
1213
use function is_string;
1314
use function sprintf;
1415

15-
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
16+
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType, ImplementingType
1617
{
1718
/** @var InterfaceTypeDefinitionNode|null */
1819
public $astNode;
@@ -28,6 +29,20 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
2829
private $fields;
2930

3031
/**
32+
* Lazily initialized.
33+
*
34+
* @var InterfaceType[]
35+
*/
36+
private $interfaces;
37+
38+
/**
39+
* Lazily initialized.
40+
*
41+
* @var InterfaceType[]
42+
*/
43+
private $interfaceMap;
44+
45+
/**
3146
* @param mixed[] $config
3247
*/
3348
public function __construct(array $config)
@@ -99,6 +114,46 @@ protected function initializeFields() : void
99114
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
100115
}
101116

117+
public function implementsInterface(InterfaceType $interfaceType) : bool
118+
{
119+
if (! isset($this->interfaceMap)) {
120+
$this->interfaceMap = [];
121+
foreach ($this->getInterfaces() as $interface) {
122+
/** @var Type&InterfaceType $interface */
123+
$interface = Schema::resolveType($interface);
124+
$this->interfaceMap[$interface->name] = $interface;
125+
}
126+
}
127+
128+
return isset($this->interfaceMap[$interfaceType->name]);
129+
}
130+
131+
/**
132+
* @return InterfaceType[]
133+
*/
134+
public function getInterfaces() : array
135+
{
136+
if (! isset($this->interfaces)) {
137+
$interfaces = $this->config['interfaces'] ?? [];
138+
if (is_callable($interfaces)) {
139+
$interfaces = $interfaces();
140+
}
141+
142+
if ($interfaces !== null && ! is_array($interfaces)) {
143+
throw new InvariantViolation(
144+
sprintf('%s interfaces must be an Array or a callable which returns an Array.', $this->name)
145+
);
146+
}
147+
148+
/** @var InterfaceType[] $interfaces */
149+
$interfaces = array_map([Schema::class, 'resolveType'], $interfaces ?? []);
150+
151+
$this->interfaces = $interfaces;
152+
}
153+
154+
return $this->interfaces;
155+
}
156+
102157
/**
103158
* Resolves concrete ObjectType for given object value
104159
*

src/Type/Definition/ObjectType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
* }
5656
* ]);
5757
*/
58-
class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType
58+
class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType, ImplementingType
5959
{
6060
/** @var ObjectTypeDefinitionNode|null */
6161
public $astNode;

src/Type/Introspection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ static function (FieldDefinition $field) : bool {
359359
'interfaces' => [
360360
'type' => Type::listOf(Type::nonNull(self::_type())),
361361
'resolve' => static function ($type) : ?array {
362-
if ($type instanceof ObjectType) {
362+
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
363363
return $type->getInterfaces();
364364
}
365365

src/Type/Schema.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use GraphQL\Language\AST\SchemaTypeExtensionNode;
1313
use GraphQL\Type\Definition\AbstractType;
1414
use GraphQL\Type\Definition\Directive;
15+
use GraphQL\Type\Definition\ImplementingType;
1516
use GraphQL\Type\Definition\InterfaceType;
1617
use GraphQL\Type\Definition\ObjectType;
1718
use GraphQL\Type\Definition\Type;
@@ -454,7 +455,7 @@ private function getPossibleTypeMap() : array
454455
*
455456
* @api
456457
*/
457-
public function isPossibleType(AbstractType $abstractType, ObjectType $possibleType) : bool
458+
public function isPossibleType(AbstractType $abstractType, ImplementingType $possibleType) : bool
458459
{
459460
if ($abstractType instanceof InterfaceType) {
460461
return $possibleType->implementsInterface($abstractType);

0 commit comments

Comments
 (0)