Skip to content

Commit aa4f719

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

22 files changed

+203
-64
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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
{
15+
public function implementsInterface(InterfaceType $interfaceType) : bool;
16+
17+
/**
18+
* @return InterfaceType[]
19+
*/
20+
public function getInterfaces() : array;
21+
}

src/Type/Definition/InterfaceType.php

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
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;
12+
use function array_map;
13+
use function is_array;
1114
use function is_callable;
1215
use function is_string;
1316
use function sprintf;
1417

15-
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
18+
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType, ImplementingType
1619
{
1720
/** @var InterfaceTypeDefinitionNode|null */
1821
public $astNode;
@@ -27,6 +30,20 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
2730
*/
2831
private $fields;
2932

33+
/**
34+
* Lazily initialized.
35+
*
36+
* @var InterfaceType[]
37+
*/
38+
private $interfaces;
39+
40+
/**
41+
* Lazily initialized.
42+
*
43+
* @var InterfaceType[]
44+
*/
45+
private $interfaceMap;
46+
3047
/**
3148
* @param mixed[] $config
3249
*/
@@ -99,6 +116,46 @@ protected function initializeFields() : void
99116
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
100117
}
101118

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

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: 2 additions & 2 deletions
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

@@ -446,7 +446,7 @@ public static function _typeKind()
446446
],
447447
'INTERFACE' => [
448448
'value' => TypeKind::INTERFACE,
449-
'description' => 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.',
449+
'description' => 'Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields.',
450450
],
451451
'UNION' => [
452452
'value' => TypeKind::UNION,

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)