Skip to content

Commit

Permalink
Implement support for interfaces implementing interfaces
Browse files Browse the repository at this point in the history
Closes #728
  • Loading branch information
Kingdutch committed Nov 27, 2020
1 parent 8795c03 commit 8ead4df
Show file tree
Hide file tree
Showing 22 changed files with 201 additions and 63 deletions.
3 changes: 3 additions & 0 deletions src/Language/AST/InterfaceTypeDefinitionNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class InterfaceTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NodeList<DirectiveNode>|null */
public $directives;

/** @var NodeList<InterfaceTypeDefinitionNode>|null */
public $interfaces;

/** @var NodeList<FieldDefinitionNode>|null */
public $fields;

Expand Down
3 changes: 3 additions & 0 deletions src/Language/AST/InterfaceTypeExtensionNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class InterfaceTypeExtensionNode extends Node implements TypeExtensionNode
/** @var NodeList<DirectiveNode>|null */
public $directives;

/** @var NodeList<InterfaceTypeDefinitionNode>|null */
public $interfaces;

/** @var NodeList<FieldDefinitionNode>|null */
public $fields;
}
4 changes: 4 additions & 0 deletions src/Language/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -1346,12 +1346,14 @@ private function parseInterfaceTypeDefinition() : InterfaceTypeDefinitionNode
$description = $this->parseDescription();
$this->expectKeyword('interface');
$name = $this->parseName();
$interfaces = $this->parseImplementsInterfaces();
$directives = $this->parseDirectives(true);
$fields = $this->parseFieldsDefinition();

return new InterfaceTypeDefinitionNode([
'name' => $name,
'directives' => $directives,
'interfaces' => $interfaces,
'fields' => $fields,
'loc' => $this->loc($start),
'description' => $description,
Expand Down Expand Up @@ -1622,6 +1624,7 @@ private function parseInterfaceTypeExtension() : InterfaceTypeExtensionNode
$this->expectKeyword('extend');
$this->expectKeyword('interface');
$name = $this->parseName();
$interfaces = $this->parseImplementsInterfaces();
$directives = $this->parseDirectives(true);
$fields = $this->parseFieldsDefinition();
if (count($directives) === 0 &&
Expand All @@ -1633,6 +1636,7 @@ private function parseInterfaceTypeExtension() : InterfaceTypeExtensionNode
return new InterfaceTypeExtensionNode([
'name' => $name,
'directives' => $directives,
'interfaces' => $interfaces,
'fields' => $fields,
'loc' => $this->loc($start),
]);
Expand Down
2 changes: 2 additions & 0 deletions src/Language/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ function (InterfaceTypeDefinitionNode $def) : string {
[
'interface',
$def->name,
$this->wrap('implements ', $this->join($def->interfaces, ' & ')),
$this->join($def->directives, ' '),
$this->block($def->fields),
],
Expand Down Expand Up @@ -401,6 +402,7 @@ function (InterfaceTypeDefinitionNode $def) : string {
[
'extend interface',
$def->name,
$this->wrap('implements ', $this->join($def->interfaces, ' & ')),
$this->join($def->directives, ' '),
$this->block($def->fields),
],
Expand Down
4 changes: 2 additions & 2 deletions src/Language/Visitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,15 @@ class Visitor
NodeKind::OBJECT_TYPE_DEFINITION => ['description', 'name', 'interfaces', 'directives', 'fields'],
NodeKind::FIELD_DEFINITION => ['description', 'name', 'arguments', 'type', 'directives'],
NodeKind::INPUT_VALUE_DEFINITION => ['description', 'name', 'type', 'defaultValue', 'directives'],
NodeKind::INTERFACE_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'],
NodeKind::INTERFACE_TYPE_DEFINITION => ['description', 'name', 'interfaces', 'directives', 'fields'],
NodeKind::UNION_TYPE_DEFINITION => ['description', 'name', 'directives', 'types'],
NodeKind::ENUM_TYPE_DEFINITION => ['description', 'name', 'directives', 'values'],
NodeKind::ENUM_VALUE_DEFINITION => ['description', 'name', 'directives'],
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'],

NodeKind::SCALAR_TYPE_EXTENSION => ['name', 'directives'],
NodeKind::OBJECT_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'],
NodeKind::INTERFACE_TYPE_EXTENSION => ['name', 'directives', 'fields'],
NodeKind::INTERFACE_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'],
NodeKind::UNION_TYPE_EXTENSION => ['name', 'directives', 'types'],
NodeKind::ENUM_TYPE_EXTENSION => ['name', 'directives', 'values'],
NodeKind::INPUT_OBJECT_TYPE_EXTENSION => ['name', 'directives', 'fields'],
Expand Down
21 changes: 21 additions & 0 deletions src/Type/Definition/ImplementingType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace GraphQL\Type\Definition;

/*
export type GraphQLImplementingType =
GraphQLObjectType |
GraphQLInterfaceType;
*/

interface ImplementingType
{
public function implementsInterface(InterfaceType $interfaceType) : bool;

/**
* @return InterfaceType[]
*/
public function getInterfaces() : array;
}
59 changes: 58 additions & 1 deletion src/Type/Definition/InterfaceType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use function array_map;
use function is_array;
use function is_callable;
use function is_string;
use function sprintf;

class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType, ImplementingType
{
/** @var InterfaceTypeDefinitionNode|null */
public $astNode;
Expand All @@ -27,6 +30,20 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
*/
private $fields;

/**
* Lazily initialized.
*
* @var InterfaceType[]
*/
private $interfaces;

/**
* Lazily initialized.
*
* @var InterfaceType[]
*/
private $interfaceMap;

/**
* @param mixed[] $config
*/
Expand Down Expand Up @@ -99,6 +116,46 @@ protected function initializeFields() : void
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
}

public function implementsInterface(InterfaceType $interfaceType) : bool
{
if (! isset($this->interfaceMap)) {
$this->interfaceMap = [];
foreach ($this->getInterfaces() as $interface) {
/** @var Type&InterfaceType $interface */
$interface = Schema::resolveType($interface);
$this->interfaceMap[$interface->name] = $interface;
}
}

return isset($this->interfaceMap[$interfaceType->name]);
}

/**
* @return InterfaceType[]
*/
public function getInterfaces() : array
{
if (! isset($this->interfaces)) {
$interfaces = $this->config['interfaces'] ?? [];
if (is_callable($interfaces)) {
$interfaces = $interfaces();
}

if ($interfaces !== null && ! is_array($interfaces)) {
throw new InvariantViolation(
sprintf('%s interfaces must be an Array or a callable which returns an Array.', $this->name)
);
}

/** @var InterfaceType[] $interfaces */
$interfaces = array_map([Schema::class, 'resolveType'], $interfaces ?? []);

$this->interfaces = $interfaces;
}

return $this->interfaces;
}

/**
* Resolves concrete ObjectType for given object value
*
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 @@ -55,7 +55,7 @@
* }
* ]);
*/
class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType
class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType, ImplementingType
{
/** @var ObjectTypeDefinitionNode|null */
public $astNode;
Expand Down
2 changes: 1 addition & 1 deletion src/Type/Introspection.php
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ static function (FieldDefinition $field) : bool {
'interfaces' => [
'type' => Type::listOf(Type::nonNull(self::_type())),
'resolve' => static function ($type) : ?array {
if ($type instanceof ObjectType) {
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
return $type->getInterfaces();
}

Expand Down
3 changes: 2 additions & 1 deletion src/Type/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ImplementingType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
Expand Down Expand Up @@ -454,7 +455,7 @@ private function getPossibleTypeMap() : array
*
* @api
*/
public function isPossibleType(AbstractType $abstractType, ObjectType $possibleType) : bool
public function isPossibleType(AbstractType $abstractType, ImplementingType $possibleType) : bool
{
if ($abstractType instanceof InterfaceType) {
return $possibleType->implementsInterface($abstractType);
Expand Down

0 comments on commit 8ead4df

Please sign in to comment.