Added DefinitionContainer interface to enable compositional use of user-land types (vs extending ObjectType, InterfaceType, etc). Very similar to IteratorAggregate vs Iterator

This commit is contained in:
vladar 2016-10-23 02:00:37 +07:00
parent c11f25794a
commit 9941a0143a
10 changed files with 78 additions and 12 deletions

View File

@ -19,6 +19,7 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\DefinitionContainer;
use GraphQL\Type\Introspection; use GraphQL\Type\Introspection;
use GraphQL\Utils; use GraphQL\Utils;
@ -744,6 +745,10 @@ class Executor
$runtimeType = $exeContext->schema->getType($runtimeType); $runtimeType = $exeContext->schema->getType($runtimeType);
} }
if ($runtimeType instanceof DefinitionContainer) {
$runtimeType = $runtimeType->getDefinition();
}
if (!($runtimeType instanceof ObjectType)) { if (!($runtimeType instanceof ObjectType)) {
throw new Error( throw new Error(
"Abstract type {$returnType} must resolve to an Object type at runtime " . "Abstract type {$returnType} must resolve to an Object type at runtime " .

View File

@ -9,6 +9,7 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Definition\WrappingType; use GraphQL\Type\Definition\WrappingType;
use GraphQL\Type\DefinitionContainer;
use GraphQL\Type\Introspection; use GraphQL\Type\Introspection;
/** /**
@ -113,6 +114,18 @@ class Schema
'validate' => true 'validate' => true
]; ];
if ($config['query'] instanceof DefinitionContainer) {
$config['query'] = $config['query']->getDefinition();
}
if ($config['mutation'] instanceof DefinitionContainer) {
$config['mutation'] = $config['mutation']->getDefinition();
}
if ($config['subscription'] instanceof DefinitionContainer) {
$config['subscription'] = $config['subscription']->getDefinition();
}
Utils::invariant( Utils::invariant(
$config['query'] instanceof ObjectType, $config['query'] instanceof ObjectType,
"Schema query must be Object Type but got: " . Utils::getVariableType($config['query']) "Schema query must be Object Type but got: " . Utils::getVariableType($config['query'])
@ -299,6 +312,9 @@ class Schema
if (!$type) { if (!$type) {
return $this->typeMap; return $this->typeMap;
} }
if ($type instanceof DefinitionContainer) {
$type = $type->getDefinition();
}
if ($type instanceof WrappingType) { if ($type instanceof WrappingType) {
return $this->extractTypes($type->getWrappedType(true)); return $this->extractTypes($type->getWrappedType(true));

View File

@ -2,6 +2,7 @@
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Type\DefinitionContainer;
use GraphQL\Utils; use GraphQL\Utils;
/** /**
@ -188,6 +189,10 @@ class Config
$err = 'Error in "'.$typeName.'" type definition: ' . "Each entry at '$pathStr' must be an array, but '%s' is '%s'"; $err = 'Error in "'.$typeName.'" type definition: ' . "Each entry at '$pathStr' must be an array, but '%s' is '%s'";
foreach ($value as $arrKey => $arrValue) { foreach ($value as $arrKey => $arrValue) {
if ($arrValue instanceof DefinitionContainer) {
$arrValue = $arrValue->getDefinition();
}
if (is_array($def->definition)) { if (is_array($def->definition)) {
if ($def->flags & self::MAYBE_TYPE && $arrValue instanceof Type) { if ($def->flags & self::MAYBE_TYPE && $arrValue instanceof Type) {
$arrValue = ['type' => $arrValue]; $arrValue = ['type' => $arrValue];
@ -220,6 +225,10 @@ class Config
return ; // Allow nulls for non-required fields return ; // Allow nulls for non-required fields
} }
if ($value instanceof DefinitionContainer) {
$value = $value->getDefinition();
}
switch (true) { switch (true) {
case $def & self::ANY: case $def & self::ANY:
break; break;

View File

@ -52,7 +52,7 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
} }
/** /**
* @return array<FieldDefinition> * @return FieldDefinition[]
*/ */
public function getFields() public function getFields()
{ {

View File

@ -1,6 +1,7 @@
<?php <?php
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Type\DefinitionContainer;
use GraphQL\Utils; use GraphQL\Utils;
/** /**
@ -15,12 +16,12 @@ class ListOfType extends Type implements WrappingType, OutputType, InputType
public $ofType; public $ofType;
/** /**
* @param callable|Type $type * @param callable|Type|DefinitionContainer $type
*/ */
public function __construct($type) public function __construct($type)
{ {
Utils::invariant( Utils::invariant(
$type instanceof Type || is_callable($type), $type instanceof Type || $type instanceof DefinitionContainer || is_callable($type),
'Expecting instance of GraphQL\Type\Definition\Type or callable returning instance of that class' 'Expecting instance of GraphQL\Type\Definition\Type or callable returning instance of that class'
); );

View File

@ -1,6 +1,7 @@
<?php <?php
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Type\DefinitionContainer;
use GraphQL\Type\TypeKind; use GraphQL\Type\TypeKind;
use GraphQL\Utils; use GraphQL\Utils;
@ -16,13 +17,13 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
protected $ofType; protected $ofType;
/** /**
* @param callable|Type $type * @param callable|Type|DefinitionContainer $type
* @throws \Exception * @throws \Exception
*/ */
public function __construct($type) public function __construct($type)
{ {
Utils::invariant( Utils::invariant(
$type instanceof Type || is_callable($type), $type instanceof Type || $type instanceof DefinitionContainer || is_callable($type),
'Expecting instance of GraphQL\Type\Definition\Type or callable returning instance of that class' 'Expecting instance of GraphQL\Type\Definition\Type or callable returning instance of that class'
); );
Utils::invariant( Utils::invariant(

View File

@ -1,5 +1,6 @@
<?php <?php
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Type\DefinitionContainer;
use GraphQL\Utils; use GraphQL\Utils;
@ -139,6 +140,12 @@ class ObjectType extends Type implements OutputType, CompositeType
if (null === $this->interfaces) { if (null === $this->interfaces) {
$interfaces = isset($this->config['interfaces']) ? $this->config['interfaces'] : []; $interfaces = isset($this->config['interfaces']) ? $this->config['interfaces'] : [];
$interfaces = is_callable($interfaces) ? call_user_func($interfaces) : $interfaces; $interfaces = is_callable($interfaces) ? call_user_func($interfaces) : $interfaces;
// TODO: Return some sort of generator to avoid multiple loops
$interfaces = Utils::map($interfaces, function($iface) {
return $iface instanceof DefinitionContainer ? $iface->getDefinition() : $iface;
});
$this->interfaces = $interfaces; $this->interfaces = $interfaces;
} }
return $this->interfaces; return $this->interfaces;

View File

@ -2,6 +2,7 @@
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Type\DefinitionContainer;
use GraphQL\Utils; use GraphQL\Utils;
/* /*
@ -198,6 +199,9 @@ abstract class Type
); );
$type = $type(); $type = $type();
} }
if ($type instanceof DefinitionContainer) {
$type = $type->getDefinition();
}
if (!$type instanceof Type) { if (!$type instanceof Type) {
throw new InvariantViolation(sprintf( throw new InvariantViolation(sprintf(

View File

@ -1,6 +1,7 @@
<?php <?php
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Type\DefinitionContainer;
use GraphQL\Utils; use GraphQL\Utils;
/** /**
@ -42,8 +43,6 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
'description' => Config::STRING 'description' => Config::STRING
]); ]);
Utils::invariant(!empty($config['types']), "");
/** /**
* Optionally provide a custom type resolver function. If one is not provided, * Optionally provide a custom type resolver function. If one is not provided,
* the default implemenation will call `isTypeOf` on each implementing * the default implemenation will call `isTypeOf` on each implementing
@ -51,7 +50,6 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
*/ */
$this->name = $config['name']; $this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null; $this->description = isset($config['description']) ? $config['description'] : null;
$this->types = $config['types'];
$this->resolveTypeFn = isset($config['resolveType']) ? $config['resolveType'] : null; $this->resolveTypeFn = isset($config['resolveType']) ? $config['resolveType'] : null;
$this->config = $config; $this->config = $config;
} }
@ -70,13 +68,23 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType
*/ */
public function getTypes() public function getTypes()
{ {
if ($this->types instanceof \Closure) { if (null === $this->types) {
$this->types = call_user_func($this->types); if ($this->config['types'] instanceof \Closure) {
$types = call_user_func($this->config['types']);
} else {
$types = $this->config['types'];
}
Utils::invariant( Utils::invariant(
is_array($this->types), is_array($types),
'Closure for option "types" of union "%s" is expected to return array of types', 'Option "types" of union "%s" is expected to return array of types (or closure returning array of types)',
$this->name $this->name
); );
// TODO: Return some sort of generator to avoid multiple loops
$this->types = Utils::map($types, function($type) {
return $type instanceof DefinitionContainer ? $type->getDefinition() : $type;
});
} }
return $this->types; return $this->types;
} }

View File

@ -0,0 +1,15 @@
<?php
namespace GraphQL\Type;
use GraphQL\Type\Definition\Type;
/**
* Interface DefinitionContainer
* @package GraphQL\Type
*/
interface DefinitionContainer
{
/**
* @return Type
*/
public function getDefinition();
}