Merge pull request #331 from simPod/cs-type

Fix CS in src/Type
This commit is contained in:
Vladimir Razuvaev 2018-08-28 17:23:42 +07:00 committed by GitHub
commit ef93557a5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2373 additions and 2122 deletions

View File

@ -52,35 +52,15 @@ class Values
/** @var InputType|Type $varType */ /** @var InputType|Type $varType */
$varType = TypeInfo::typeFromAST($schema, $varDefNode->type); $varType = TypeInfo::typeFromAST($schema, $varDefNode->type);
if (! Type::isInputType($varType)) { if (Type::isInputType($varType)) {
$errors[] = new Error( if (array_key_exists($varName, $inputs)) {
sprintf(
'Variable "$%s" expected value of type "%s" which cannot be used as an input type.',
$varName,
Printer::doPrint($varDefNode->type)
),
[$varDefNode->type]
);
} else {
if (! array_key_exists($varName, $inputs)) {
if ($varType instanceof NonNull) {
$errors[] = new Error(
sprintf(
'Variable "$%s" of required type "%s" was not provided.',
$varName,
$varType
),
[$varDefNode]
);
} elseif ($varDefNode->defaultValue) {
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
}
} else {
$value = $inputs[$varName]; $value = $inputs[$varName];
$coerced = Value::coerceValue($value, $varType, $varDefNode); $coerced = Value::coerceValue($value, $varType, $varDefNode);
/** @var Error[] $coercionErrors */ /** @var Error[] $coercionErrors */
$coercionErrors = $coerced['errors']; $coercionErrors = $coerced['errors'];
if (! empty($coercionErrors)) { if (empty($coercionErrors)) {
$coercedValues[$varName] = $coerced['value'];
} else {
$messagePrelude = sprintf( $messagePrelude = sprintf(
'Variable "$%s" got invalid value %s; ', 'Variable "$%s" got invalid value %s; ',
$varName, $varName,
@ -98,10 +78,30 @@ class Values
$error->getExtensions() $error->getExtensions()
); );
} }
}
} else { } else {
$coercedValues[$varName] = $coerced['value']; if ($varType instanceof NonNull) {
$errors[] = new Error(
sprintf(
'Variable "$%s" of required type "%s" was not provided.',
$varName,
$varType
),
[$varDefNode]
);
} elseif ($varDefNode->defaultValue) {
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
} }
} }
} else {
$errors[] = new Error(
sprintf(
'Variable "$%s" expected value of type "%s" which cannot be used as an input type.',
$varName,
Printer::doPrint($varDefNode->type)
),
[$varDefNode->type]
);
} }
} }

View File

@ -470,7 +470,7 @@ class Printer
Utils::filter( Utils::filter(
$maybeArray, $maybeArray,
function ($x) { function ($x) {
return ! ! $x; return (bool) $x;
} }
) )
) )

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
/* /*
@ -6,14 +9,14 @@ export type GraphQLAbstractType =
GraphQLInterfaceType | GraphQLInterfaceType |
GraphQLUnionType; GraphQLUnionType;
*/ */
interface AbstractType interface AbstractType
{ {
/** /**
* Resolves concrete ObjectType for given object value * Resolves concrete ObjectType for given object value
* *
* @param $objectValue * @param object $objectValue
* @param $context * @param mixed[] $context
* @param ResolveInfo $info
* @return mixed * @return mixed
*/ */
public function resolveType($objectValue, $context, ResolveInfo $info); public function resolveType($objectValue, $context, ResolveInfo $info);

View File

@ -1,25 +1,24 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_bool;
/** /**
* Class BooleanType * Class BooleanType
* @package GraphQL\Type\Definition
*/ */
class BooleanType extends ScalarType class BooleanType extends ScalarType
{ {
/** /** @var string */
* @var string
*/
public $name = Type::BOOLEAN; public $name = Type::BOOLEAN;
/** /** @var string */
* @var string
*/
public $description = 'The `Boolean` scalar type represents `true` or `false`.'; public $description = 'The `Boolean` scalar type represents `true` or `false`.';
/** /**
@ -28,7 +27,7 @@ class BooleanType extends ScalarType
*/ */
public function serialize($value) public function serialize($value)
{ {
return !!$value; return (bool) $value;
} }
/** /**
@ -42,16 +41,16 @@ class BooleanType extends ScalarType
return $value; return $value;
} }
throw new Error("Cannot represent value as boolean: " . Utils::printSafe($value)); throw new Error('Cannot represent value as boolean: ' . Utils::printSafe($value));
} }
/** /**
* @param Node $valueNode * @param Node $valueNode
* @param array|null $variables * @param mixed[]|null $variables
* @return bool|null * @return bool|null
* @throws \Exception * @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, ?array $variables = null)
{ {
if ($valueNode instanceof BooleanValueNode) { if ($valueNode instanceof BooleanValueNode) {
return (bool) $valueNode->value; return (bool) $valueNode->value;

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
/* /*
@ -7,6 +10,7 @@ GraphQLObjectType |
GraphQLInterfaceType | GraphQLInterfaceType |
GraphQLUnionType; GraphQLUnionType;
*/ */
interface CompositeType interface CompositeType
{ {
} }

View File

@ -1,13 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Utils\AST; use GraphQL\Utils\AST;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function call_user_func;
use function is_callable;
use function sprintf;
/** /**
* Class CustomScalarType * Class CustomScalarType
* @package GraphQL\Type\Definition
*/ */
class CustomScalarType extends ScalarType class CustomScalarType extends ScalarType
{ {
@ -28,24 +33,26 @@ class CustomScalarType extends ScalarType
{ {
if (isset($this->config['parseValue'])) { if (isset($this->config['parseValue'])) {
return call_user_func($this->config['parseValue'], $value); return call_user_func($this->config['parseValue'], $value);
} else {
return $value;
} }
return $value;
} }
/** /**
* @param Node $valueNode * @param Node $valueNode
* @param array|null $variables * @param mixed[]|null $variables
* @return mixed * @return mixed
* @throws \Exception * @throws \Exception
*/ */
public function parseLiteral(/* GraphQL\Language\AST\ValueNode */ $valueNode, array $variables = null) public function parseLiteral(/* GraphQL\Language\AST\ValueNode */
{ $valueNode,
?array $variables = null
) {
if (isset($this->config['parseLiteral'])) { if (isset($this->config['parseLiteral'])) {
return call_user_func($this->config['parseLiteral'], $valueNode, $variables); return call_user_func($this->config['parseLiteral'], $valueNode, $variables);
} else {
return AST::valueFromASTUntyped($valueNode, $variables);
} }
return AST::valueFromASTUntyped($valueNode, $variables);
} }
public function assertValid() public function assertValid()
@ -54,16 +61,18 @@ class CustomScalarType extends ScalarType
Utils::invariant( Utils::invariant(
isset($this->config['serialize']) && is_callable($this->config['serialize']), isset($this->config['serialize']) && is_callable($this->config['serialize']),
"{$this->name} must provide \"serialize\" function. If this custom Scalar " . sprintf('%s must provide "serialize" function. If this custom Scalar ', $this->name) .
'is also used as an input type, ensure "parseValue" and "parseLiteral" ' . 'is also used as an input type, ensure "parseValue" and "parseLiteral" ' .
'functions are also provided.' 'functions are also provided.'
); );
if (isset($this->config['parseValue']) || isset($this->config['parseLiteral'])) { if (! isset($this->config['parseValue']) && ! isset($this->config['parseLiteral'])) {
return;
}
Utils::invariant( Utils::invariant(
isset($this->config['parseValue']) && isset($this->config['parseLiteral']) && isset($this->config['parseValue']) && isset($this->config['parseLiteral']) &&
is_callable($this->config['parseValue']) && is_callable($this->config['parseLiteral']), is_callable($this->config['parseValue']) && is_callable($this->config['parseLiteral']),
"{$this->name} must provide both \"parseValue\" and \"parseLiteral\" functions." sprintf('%s must provide both "parseValue" and "parseLiteral" functions.', $this->name)
); );
} }
}
} }

View File

@ -1,159 +1,48 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\DirectiveLocation; use GraphQL\Language\DirectiveLocation;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function array_key_exists;
use function is_array;
/** /**
* Class Directive * Class Directive
* @package GraphQL\Type\Definition
*/ */
class Directive class Directive
{ {
const DEFAULT_DEPRECATION_REASON = 'No longer supported'; public const DEFAULT_DEPRECATION_REASON = 'No longer supported';
/** /** @var Directive[] */
* @var array
*/
public static $internalDirectives; public static $internalDirectives;
// Schema Definitions // Schema Definitions
/** /** @var string */
* @return Directive
*/
public static function includeDirective()
{
$internal = self::getInternalDirectives();
return $internal['include'];
}
/**
* @return Directive
*/
public static function skipDirective()
{
$internal = self::getInternalDirectives();
return $internal['skip'];
}
/**
* @return Directive
*/
public static function deprecatedDirective()
{
$internal = self::getInternalDirectives();
return $internal['deprecated'];
}
/**
* @param Directive $directive
* @return bool
*/
public static function isSpecifiedDirective(Directive $directive)
{
return in_array($directive->name, array_keys(self::getInternalDirectives()));
}
/**
* @return array
*/
public static function getInternalDirectives()
{
if (!self::$internalDirectives) {
self::$internalDirectives = [
'include' => new self([
'name' => 'include',
'description' => 'Directs the executor to include this field or fragment only when the `if` argument is true.',
'locations' => [
DirectiveLocation::FIELD,
DirectiveLocation::FRAGMENT_SPREAD,
DirectiveLocation::INLINE_FRAGMENT,
],
'args' => [
new FieldArgument([
'name' => 'if',
'type' => Type::nonNull(Type::boolean()),
'description' => 'Included when true.'
])
],
]),
'skip' => new self([
'name' => 'skip',
'description' => 'Directs the executor to skip this field or fragment when the `if` argument is true.',
'locations' => [
DirectiveLocation::FIELD,
DirectiveLocation::FRAGMENT_SPREAD,
DirectiveLocation::INLINE_FRAGMENT
],
'args' => [
new FieldArgument([
'name' => 'if',
'type' => Type::nonNull(Type::boolean()),
'description' => 'Skipped when true.'
])
]
]),
'deprecated' => new self([
'name' => 'deprecated',
'description' => 'Marks an element of a GraphQL schema as no longer supported.',
'locations' => [
DirectiveLocation::FIELD_DEFINITION,
DirectiveLocation::ENUM_VALUE
],
'args' => [
new FieldArgument([
'name' => 'reason',
'type' => Type::string(),
'description' =>
'Explains why this element was deprecated, usually also including a ' .
'suggestion for how to access supported similar data. Formatted ' .
'in [Markdown](https://daringfireball.net/projects/markdown/).',
'defaultValue' => self::DEFAULT_DEPRECATION_REASON
])
]
])
];
}
return self::$internalDirectives;
}
/**
* @var string
*/
public $name; public $name;
/** /** @var string|null */
* @var string|null
*/
public $description; public $description;
/** /** @var string[] */
* Values from self::$locationMap
*
* @var array
*/
public $locations; public $locations;
/** /** @var FieldArgument[] */
* @var FieldArgument[]
*/
public $args; public $args;
/** /** @var DirectiveDefinitionNode|null */
* @var DirectiveDefinitionNode|null
*/
public $astNode; public $astNode;
/** /** @var mixed[] */
* @var array
*/
public $config; public $config;
/** /**
* Directive constructor. *
* @param array $config * @param mixed[] $config
*/ */
public function __construct(array $config) public function __construct(array $config)
{ {
@ -177,4 +66,103 @@ class Directive
Utils::invariant(is_array($this->locations), 'Must provide locations for directive.'); Utils::invariant(is_array($this->locations), 'Must provide locations for directive.');
$this->config = $config; $this->config = $config;
} }
/**
* @return Directive
*/
public static function includeDirective()
{
$internal = self::getInternalDirectives();
return $internal['include'];
}
/**
* @return Directive[]
*/
public static function getInternalDirectives()
{
if (! self::$internalDirectives) {
self::$internalDirectives = [
'include' => new self([
'name' => 'include',
'description' => 'Directs the executor to include this field or fragment only when the `if` argument is true.',
'locations' => [
DirectiveLocation::FIELD,
DirectiveLocation::FRAGMENT_SPREAD,
DirectiveLocation::INLINE_FRAGMENT,
],
'args' => [new FieldArgument([
'name' => 'if',
'type' => Type::nonNull(Type::boolean()),
'description' => 'Included when true.',
]),
],
]),
'skip' => new self([
'name' => 'skip',
'description' => 'Directs the executor to skip this field or fragment when the `if` argument is true.',
'locations' => [
DirectiveLocation::FIELD,
DirectiveLocation::FRAGMENT_SPREAD,
DirectiveLocation::INLINE_FRAGMENT,
],
'args' => [new FieldArgument([
'name' => 'if',
'type' => Type::nonNull(Type::boolean()),
'description' => 'Skipped when true.',
]),
],
]),
'deprecated' => new self([
'name' => 'deprecated',
'description' => 'Marks an element of a GraphQL schema as no longer supported.',
'locations' => [
DirectiveLocation::FIELD_DEFINITION,
DirectiveLocation::ENUM_VALUE,
],
'args' => [new FieldArgument([
'name' => 'reason',
'type' => Type::string(),
'description' =>
'Explains why this element was deprecated, usually also including a ' .
'suggestion for how to access supported similar data. Formatted ' .
'in [Markdown](https://daringfireball.net/projects/markdown/).',
'defaultValue' => self::DEFAULT_DEPRECATION_REASON,
]),
],
]),
];
}
return self::$internalDirectives;
}
/**
* @return Directive
*/
public static function skipDirective()
{
$internal = self::getInternalDirectives();
return $internal['skip'];
}
/**
* @return Directive
*/
public static function deprecatedDirective()
{
$internal = self::getInternalDirectives();
return $internal['deprecated'];
}
/**
* @return bool
*/
public static function isSpecifiedDirective(Directive $directive)
{
return array_key_exists($directive->name, self::getInternalDirectives());
}
} }

View File

@ -1,53 +1,83 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\EnumTypeDefinitionNode; use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumValueNode; use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Utils\MixedStore; use GraphQL\Utils\MixedStore;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_array;
use function is_int;
use function is_string;
use function sprintf;
/** /**
* Class EnumType * Class EnumType
* @package GraphQL\Type\Definition
*/ */
class EnumType extends Type implements InputType, OutputType, LeafType, NamedType class EnumType extends Type implements InputType, OutputType, LeafType, NamedType
{ {
/** /** @var EnumTypeDefinitionNode|null */
* @var EnumTypeDefinitionNode|null
*/
public $astNode; public $astNode;
/** /** @var EnumValueDefinition[] */
* @var EnumValueDefinition[]
*/
private $values; private $values;
/** /** @var MixedStore<mixed, EnumValueDefinition> */
* @var MixedStore<mixed, EnumValueDefinition>
*/
private $valueLookup; private $valueLookup;
/** /** @var \ArrayObject<string, EnumValueDefinition> */
* @var \ArrayObject<string, EnumValueDefinition>
*/
private $nameLookup; private $nameLookup;
public function __construct($config) public function __construct($config)
{ {
if (!isset($config['name'])) { if (! isset($config['name'])) {
$config['name'] = $this->tryInferName(); $config['name'] = $this->tryInferName();
} }
Utils::invariant(is_string($config['name']), 'Must provide name.'); Utils::invariant(is_string($config['name']), 'Must provide name.');
$this->name = $config['name']; $this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null; $this->description = $config['description'] ?? null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null; $this->astNode = $config['astNode'] ?? null;
$this->config = $config; $this->config = $config;
} }
/**
* @param string|mixed[] $name
* @return EnumValueDefinition|null
*/
public function getValue($name)
{
$lookup = $this->getNameLookup();
if (! is_string($name)) {
return null;
}
return $lookup[$name] ?? null;
}
/**
* @return \ArrayObject<string, EnumValueDefinition>
*/
private function getNameLookup()
{
if (! $this->nameLookup) {
$lookup = new \ArrayObject();
foreach ($this->getValues() as $value) {
$lookup[$value->name] = $value;
}
$this->nameLookup = $lookup;
}
return $this->nameLookup;
}
/** /**
* @return EnumValueDefinition[] * @return EnumValueDefinition[]
*/ */
@ -58,20 +88,25 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
$config = $this->config; $config = $this->config;
if (isset($config['values'])) { if (isset($config['values'])) {
if (!is_array($config['values'])) { if (! is_array($config['values'])) {
throw new InvariantViolation("{$this->name} values must be an array"); throw new InvariantViolation(sprintf('%s values must be an array', $this->name));
} }
foreach ($config['values'] as $name => $value) { foreach ($config['values'] as $name => $value) {
if (is_string($name)) { if (is_string($name)) {
if (!is_array($value)) { if (is_array($value)) {
$value = ['name' => $name, 'value' => $value];
} else {
$value += ['name' => $name, 'value' => $name]; $value += ['name' => $name, 'value' => $name];
} else {
$value = ['name' => $name, 'value' => $value];
} }
} else if (is_int($name) && is_string($value)) { } elseif (is_int($name) && is_string($value)) {
$value = ['name' => $value, 'value' => $value]; $value = ['name' => $value, 'value' => $value];
} else { } else {
throw new InvariantViolation("{$this->name} values must be an array with value names as keys."); throw new InvariantViolation(
sprintf(
'%s values must be an array with value names as keys.',
$this->name
)
);
} }
$this->values[] = new EnumValueDefinition($value); $this->values[] = new EnumValueDefinition($value);
} }
@ -82,17 +117,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
} }
/** /**
* @param $name * @param mixed $value
* @return EnumValueDefinition|null
*/
public function getValue($name)
{
$lookup = $this->getNameLookup();
return is_scalar($name) && isset($lookup[$name]) ? $lookup[$name] : null;
}
/**
* @param $value
* @return mixed * @return mixed
* @throws Error * @throws Error
*/ */
@ -103,11 +128,27 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
return $lookup[$value]->name; return $lookup[$value]->name;
} }
throw new Error("Cannot serialize value as enum: " . Utils::printSafe($value)); throw new Error('Cannot serialize value as enum: ' . Utils::printSafe($value));
} }
/** /**
* @param $value * @return MixedStore<mixed, EnumValueDefinition>
*/
private function getValueLookup()
{
if ($this->valueLookup === null) {
$this->valueLookup = new MixedStore();
foreach ($this->getValues() as $valueName => $value) {
$this->valueLookup->offsetSet($value->value, $value);
}
}
return $this->valueLookup;
}
/**
* @param mixed $value
* @return mixed * @return mixed
* @throws Error * @throws Error
*/ */
@ -118,16 +159,16 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
return $lookup[$value]->value; return $lookup[$value]->value;
} }
throw new Error("Cannot represent value as enum: " . Utils::printSafe($value)); throw new Error('Cannot represent value as enum: ' . Utils::printSafe($value));
} }
/** /**
* @param $valueNode * @param Node $valueNode
* @param array|null $variables * @param mixed[]|null $variables
* @return null * @return null
* @throws \Exception * @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, ?array $variables = null)
{ {
if ($valueNode instanceof EnumValueNode) { if ($valueNode instanceof EnumValueNode) {
$lookup = $this->getNameLookup(); $lookup = $this->getNameLookup();
@ -143,37 +184,6 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
throw new \Exception(); throw new \Exception();
} }
/**
* @return MixedStore<mixed, EnumValueDefinition>
*/
private function getValueLookup()
{
if (null === $this->valueLookup) {
$this->valueLookup = new MixedStore();
foreach ($this->getValues() as $valueName => $value) {
$this->valueLookup->offsetSet($value->value, $value);
}
}
return $this->valueLookup;
}
/**
* @return \ArrayObject<string, EnumValueDefinition>
*/
private function getNameLookup()
{
if (!$this->nameLookup) {
$lookup = new \ArrayObject();
foreach ($this->getValues() as $value) {
$lookup[$value->name] = $value;
}
$this->nameLookup = $lookup;
}
return $this->nameLookup;
}
/** /**
* @throws InvariantViolation * @throws InvariantViolation
*/ */
@ -183,14 +193,18 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
Utils::invariant( Utils::invariant(
isset($this->config['values']), isset($this->config['values']),
"{$this->name} values must be an array." sprintf('%s values must be an array.', $this->name)
); );
$values = $this->getValues(); $values = $this->getValues();
foreach ($values as $value) { foreach ($values as $value) {
Utils::invariant( Utils::invariant(
!isset($value->config['isDeprecated']), ! isset($value->config['isDeprecated']),
"{$this->name}.{$value->name} should provide \"deprecationReason\" instead of \"isDeprecated\"." sprintf(
'%s.%s should provide "deprecationReason" instead of "isDeprecated".',
$this->name,
$value->name
)
); );
} }
} }

View File

@ -1,51 +1,44 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\EnumValueDefinitionNode; use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Utils\Utils;
/** /**
* Class EnumValueDefinition * Class EnumValueDefinition
* @package GraphQL\Type\Definition
*/ */
class EnumValueDefinition class EnumValueDefinition
{ {
/** /** @var string */
* @var string
*/
public $name; public $name;
/** /** @var mixed */
* @var mixed
*/
public $value; public $value;
/** /** @var string|null */
* @var string|null
*/
public $deprecationReason; public $deprecationReason;
/** /** @var string|null */
* @var string|null
*/
public $description; public $description;
/** /** @var EnumValueDefinitionNode|null */
* @var EnumValueDefinitionNode|null
*/
public $astNode; public $astNode;
/** /** @var mixed[] */
* @var array
*/
public $config; public $config;
/**
* @param mixed[] $config
*/
public function __construct(array $config) public function __construct(array $config)
{ {
$this->name = isset($config['name']) ? $config['name'] : null; $this->name = $config['name'] ?? null;
$this->value = isset($config['value']) ? $config['value'] : null; $this->value = $config['value'] ?? null;
$this->deprecationReason = isset($config['deprecationReason']) ? $config['deprecationReason'] : null; $this->deprecationReason = $config['deprecationReason'] ?? null;
$this->description = isset($config['description']) ? $config['description'] : null; $this->description = $config['description'] ?? null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null; $this->astNode = $config['astNode'] ?? null;
$this->config = $config; $this->config = $config;
} }
@ -55,6 +48,6 @@ class EnumValueDefinition
*/ */
public function isDeprecated() public function isDeprecated()
{ {
return !!$this->deprecationReason; return (bool) $this->deprecationReason;
} }
} }

View File

@ -1,73 +1,45 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\InputValueDefinitionNode; use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_array;
use function is_string;
use function sprintf;
/** /**
* Class FieldArgument * Class FieldArgument
*
* @package GraphQL\Type\Definition
*/ */
class FieldArgument class FieldArgument
{ {
/** /** @var string */
* @var string
*/
public $name; public $name;
/** /** @var mixed */
* @var mixed
*/
public $defaultValue; public $defaultValue;
/** /** @var string|null */
* @var string|null
*/
public $description; public $description;
/** /** @var InputValueDefinitionNode|null */
* @var InputValueDefinitionNode|null
*/
public $astNode; public $astNode;
/** /** @var mixed[] */
* @var array
*/
public $config; public $config;
/** /** @var InputType */
* @var InputType
*/
private $type; private $type;
/** /** @var bool */
* @var bool
*/
private $defaultValueExists = false; private $defaultValueExists = false;
/** /**
* @param array $config *
* @return array * @param mixed[] $def
*/
public static function createMap(array $config)
{
$map = [];
foreach ($config as $name => $argConfig) {
if (!is_array($argConfig)) {
$argConfig = ['type' => $argConfig];
}
$map[] = new self($argConfig + ['name' => $name]);
}
return $map;
}
/**
* FieldArgument constructor.
* @param array $def
*/ */
public function __construct(array $def) public function __construct(array $def)
{ {
@ -94,6 +66,23 @@ class FieldArgument
$this->config = $def; $this->config = $def;
} }
/**
* @param mixed[] $config
* @return FieldArgument[]
*/
public static function createMap(array $config)
{
$map = [];
foreach ($config as $name => $argConfig) {
if (! is_array($argConfig)) {
$argConfig = ['type' => $argConfig];
}
$map[] = new self($argConfig + ['name' => $name]);
}
return $map;
}
/** /**
* @return InputType * @return InputType
*/ */
@ -116,8 +105,8 @@ class FieldArgument
Utils::assertValidName($this->name); Utils::assertValidName($this->name);
} catch (InvariantViolation $e) { } catch (InvariantViolation $e) {
throw new InvariantViolation( throw new InvariantViolation(
"{$parentType->name}.{$parentField->name}({$this->name}:) {$e->getMessage()}") sprintf('%s.%s(%s:) %s', $parentType->name, $parentField->name, $this->name, $e->getMessage())
; );
} }
$type = $this->type; $type = $this->type;
if ($type instanceof WrappingType) { if ($type instanceof WrappingType) {
@ -125,13 +114,23 @@ class FieldArgument
} }
Utils::invariant( Utils::invariant(
$type instanceof InputType, $type instanceof InputType,
"{$parentType->name}.{$parentField->name}({$this->name}): argument type must be " . sprintf(
"Input Type but got: " . Utils::printSafe($this->type) '%s.%s(%s): argument type must be Input Type but got: %s',
$parentType->name,
$parentField->name,
$this->name,
Utils::printSafe($this->type)
)
); );
Utils::invariant( Utils::invariant(
$this->description === null || is_string($this->description), $this->description === null || is_string($this->description),
"{$parentType->name}.{$parentField->name}({$this->name}): argument description type must be " . sprintf(
"string but got: " . Utils::printSafe($this->description) '%s.%s(%s): argument description type must be string but got: %s',
$parentType->name,
$parentField->name,
$this->name,
Utils::printSafe($this->description)
)
); );
} }
} }

View File

@ -1,28 +1,29 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\FieldDefinitionNode; use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\TypeDefinitionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_array;
use function is_callable;
use function is_string;
use function sprintf;
/** /**
* Class FieldDefinition
* @package GraphQL\Type\Definition
* @todo Move complexity-related code to it's own place * @todo Move complexity-related code to it's own place
*/ */
class FieldDefinition class FieldDefinition
{ {
const DEFAULT_COMPLEXITY_FN = 'GraphQL\Type\Definition\FieldDefinition::defaultComplexity'; public const DEFAULT_COMPLEXITY_FN = 'GraphQL\Type\Definition\FieldDefinition::defaultComplexity';
/** /** @var string */
* @var string
*/
public $name; public $name;
/** /** @var FieldArgument[] */
* @var FieldArgument[]
*/
public $args; public $args;
/** /**
@ -41,113 +42,122 @@ class FieldDefinition
*/ */
public $mapFn; public $mapFn;
/** /** @var string|null */
* @var string|null
*/
public $description; public $description;
/** /** @var string|null */
* @var string|null
*/
public $deprecationReason; public $deprecationReason;
/** /** @var FieldDefinitionNode|null */
* @var FieldDefinitionNode|null
*/
public $astNode; public $astNode;
/** /**
* Original field definition config * Original field definition config
* *
* @var array * @var mixed[]
*/ */
public $config; public $config;
/** /** @var OutputType */
* @var OutputType
*/
private $type; private $type;
private static $def; /** @var callable|string */
private $complexityFn;
/**
*
* @param mixed[] $config
*/
protected function __construct(array $config)
{
$this->name = $config['name'];
$this->type = $config['type'];
$this->resolveFn = $config['resolve'] ?? null;
$this->mapFn = $config['map'] ?? null;
$this->args = isset($config['args']) ? FieldArgument::createMap($config['args']) : [];
$this->description = $config['description'] ?? null;
$this->deprecationReason = $config['deprecationReason'] ?? null;
$this->astNode = $config['astNode'] ?? null;
$this->config = $config;
$this->complexityFn = $config['complexity'] ?? static::DEFAULT_COMPLEXITY_FN;
}
public static function defineFieldMap(Type $type, $fields) public static function defineFieldMap(Type $type, $fields)
{ {
if (is_callable($fields)) { if (is_callable($fields)) {
$fields = $fields(); $fields = $fields();
} }
if (!is_array($fields)) { if (! is_array($fields)) {
throw new InvariantViolation( throw new InvariantViolation(
"{$type->name} fields must be an array or a callable which returns such an array." sprintf('%s fields must be an array or a callable which returns such an array.', $type->name)
); );
} }
$map = []; $map = [];
foreach ($fields as $name => $field) { foreach ($fields as $name => $field) {
if (is_array($field)) { if (is_array($field)) {
if (!isset($field['name'])) { if (! isset($field['name'])) {
if (is_string($name)) { if (! is_string($name)) {
$field['name'] = $name;
} else {
throw new InvariantViolation( throw new InvariantViolation(
"{$type->name} fields must be an associative array with field names as keys or a " . sprintf(
"function which returns such an array." '%s fields must be an associative array with field names as keys or a function which returns such an array.',
$type->name
)
); );
} }
$field['name'] = $name;
} }
if (isset($field['args']) && !is_array($field['args'])) { if (isset($field['args']) && ! is_array($field['args'])) {
throw new InvariantViolation( throw new InvariantViolation(
"{$type->name}.{$name} args must be an array." sprintf('%s.%s args must be an array.', $type->name, $name)
); );
} }
$fieldDef = self::create($field); $fieldDef = self::create($field);
} else if ($field instanceof FieldDefinition) { } elseif ($field instanceof self) {
$fieldDef = $field; $fieldDef = $field;
} else { } else {
if (is_string($name) && $field) { if (! is_string($name) || ! $field) {
$fieldDef = self::create(['name' => $name, 'type' => $field]);
} else {
throw new InvariantViolation( throw new InvariantViolation(
"{$type->name}.$name field config must be an array, but got: " . Utils::printSafe($field) sprintf(
'%s.%s field config must be an array, but got: %s',
$type->name,
$name,
Utils::printSafe($field)
)
); );
} }
$fieldDef = self::create(['name' => $name, 'type' => $field]);
} }
$map[$fieldDef->name] = $fieldDef; $map[$fieldDef->name] = $fieldDef;
} }
return $map; return $map;
} }
/** /**
* @param array|Config $field * @param mixed[] $field
* @param string $typeName
* @return FieldDefinition * @return FieldDefinition
*/ */
public static function create($field, $typeName = null) public static function create($field)
{ {
return new self($field); return new self($field);
} }
/** /**
* FieldDefinition constructor. * @param int $childrenComplexity
* @param array $config * @return mixed
*/ */
protected function __construct(array $config) public static function defaultComplexity($childrenComplexity)
{ {
$this->name = $config['name']; return $childrenComplexity + 1;
$this->type = $config['type'];
$this->resolveFn = isset($config['resolve']) ? $config['resolve'] : null;
$this->mapFn = isset($config['map']) ? $config['map'] : null;
$this->args = isset($config['args']) ? FieldArgument::createMap($config['args']) : [];
$this->description = isset($config['description']) ? $config['description'] : null;
$this->deprecationReason = isset($config['deprecationReason']) ? $config['deprecationReason'] : null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->config = $config;
$this->complexityFn = isset($config['complexity']) ? $config['complexity'] : static::DEFAULT_COMPLEXITY_FN;
} }
/** /**
* @param $name * @param string $name
* @return FieldArgument|null * @return FieldArgument|null
*/ */
public function getArg($name) public function getArg($name)
@ -158,6 +168,7 @@ class FieldDefinition
return $arg; return $arg;
} }
} }
return null; return null;
} }
@ -174,7 +185,7 @@ class FieldDefinition
*/ */
public function isDeprecated() public function isDeprecated()
{ {
return !!$this->deprecationReason; return (bool) $this->deprecationReason;
} }
/** /**
@ -186,7 +197,6 @@ class FieldDefinition
} }
/** /**
* @param Type $parentType
* @throws InvariantViolation * @throws InvariantViolation
*/ */
public function assertValid(Type $parentType) public function assertValid(Type $parentType)
@ -194,11 +204,15 @@ class FieldDefinition
try { try {
Utils::assertValidName($this->name); Utils::assertValidName($this->name);
} catch (Error $e) { } catch (Error $e) {
throw new InvariantViolation("{$parentType->name}.{$this->name}: {$e->getMessage()}"); throw new InvariantViolation(sprintf('%s.%s: %s', $parentType->name, $this->name, $e->getMessage()));
} }
Utils::invariant( Utils::invariant(
!isset($this->config['isDeprecated']), ! isset($this->config['isDeprecated']),
"{$parentType->name}.{$this->name} should provide \"deprecationReason\" instead of \"isDeprecated\"." sprintf(
'%s.%s should provide "deprecationReason" instead of "isDeprecated".',
$parentType->name,
$this->name
)
); );
$type = $this->type; $type = $this->type;
@ -207,21 +221,21 @@ class FieldDefinition
} }
Utils::invariant( Utils::invariant(
$type instanceof OutputType, $type instanceof OutputType,
"{$parentType->name}.{$this->name} field type must be Output Type but got: " . Utils::printSafe($this->type) sprintf(
'%s.%s field type must be Output Type but got: %s',
$parentType->name,
$this->name,
Utils::printSafe($this->type)
)
); );
Utils::invariant( Utils::invariant(
$this->resolveFn === null || is_callable($this->resolveFn), $this->resolveFn === null || is_callable($this->resolveFn),
"{$parentType->name}.{$this->name} field resolver must be a function if provided, but got: %s", sprintf(
'%s.%s field resolver must be a function if provided, but got: %s',
$parentType->name,
$this->name,
Utils::printSafe($this->resolveFn) Utils::printSafe($this->resolveFn)
)
); );
} }
/**
* @param $childrenComplexity
* @return mixed
*/
public static function defaultComplexity($childrenComplexity)
{
return $childrenComplexity + 1;
}
} }

View File

@ -1,27 +1,27 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\FloatValueNode; use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\IntValueNode; use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_numeric;
/** /**
* Class FloatType * Class FloatType
* @package GraphQL\Type\Definition
*/ */
class FloatType extends ScalarType class FloatType extends ScalarType
{ {
/** /** @var string */
* @var string
*/
public $name = Type::FLOAT; public $name = Type::FLOAT;
/** /** @var string */
* @var string
*/
public $description = public $description =
'The `Float` scalar type represents signed double-precision fractional 'The `Float` scalar type represents signed double-precision fractional
values as specified by values as specified by
[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). '; [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). ';
@ -35,6 +35,24 @@ values as specified by
return $this->coerceFloat($value); return $this->coerceFloat($value);
} }
private function coerceFloat($value)
{
if ($value === '') {
throw new Error(
'Float cannot represent non numeric value: (empty string)'
);
}
if (! is_numeric($value) && $value !== true && $value !== false) {
throw new Error(
'Float cannot represent non numeric value: ' .
Utils::printSafe($value)
);
}
return (float) $value;
}
/** /**
* @param mixed $value * @param mixed $value
* @return float|null * @return float|null
@ -46,12 +64,12 @@ values as specified by
} }
/** /**
* @param $valueNode * @param Node $valueNode
* @param array|null $variables * @param mixed[]|null $variables
* @return float|null * @return float|null
* @throws \Exception * @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, ?array $variables = null)
{ {
if ($valueNode instanceof FloatValueNode || $valueNode instanceof IntValueNode) { if ($valueNode instanceof FloatValueNode || $valueNode instanceof IntValueNode) {
return (float) $valueNode->value; return (float) $valueNode->value;
@ -60,21 +78,4 @@ values as specified by
// Intentionally without message, as all information already in wrapped Exception // Intentionally without message, as all information already in wrapped Exception
throw new \Exception(); throw new \Exception();
} }
private function coerceFloat($value) {
if ($value === '') {
throw new Error(
'Float cannot represent non numeric value: (empty string)'
);
}
if (!is_numeric($value) && $value !== true && $value !== false) {
throw new Error(
'Float cannot represent non numeric value: ' .
Utils::printSafe($value)
);
}
return (float) $value;
}
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -6,23 +9,20 @@ use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\AST\StringValueNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_int;
use function is_object;
use function is_scalar;
use function is_string;
use function method_exists;
/**
* Class IDType
* @package GraphQL\Type\Definition
*/
class IDType extends ScalarType class IDType extends ScalarType
{ {
/** /** @var string */
* @var string
*/
public $name = 'ID'; public $name = 'ID';
/** /** @var string */
* @var string
*/
public $description = public $description =
'The `ID` scalar type represents a unique identifier, often used to 'The `ID` scalar type represents a unique identifier, often used to
refetch an object or as key for a cache. The ID type appears in a JSON refetch an object or as key for a cache. The ID type appears in a JSON
response as a String; however, it is not intended to be human-readable. response as a String; however, it is not intended to be human-readable.
When expected as an input type, any string (such as `"4"`) or integer When expected as an input type, any string (such as `"4"`) or integer
@ -44,9 +44,10 @@ When expected as an input type, any string (such as `"4"`) or integer
if ($value === null) { if ($value === null) {
return 'null'; return 'null';
} }
if (!is_scalar($value) && (!is_object($value) || !method_exists($value, '__toString'))) { if (! is_scalar($value) && (! is_object($value) || ! method_exists($value, '__toString'))) {
throw new Error("ID type cannot represent non scalar value: " . Utils::printSafe($value)); throw new Error('ID type cannot represent non scalar value: ' . Utils::printSafe($value));
} }
return (string) $value; return (string) $value;
} }
@ -61,16 +62,16 @@ When expected as an input type, any string (such as `"4"`) or integer
return (string) $value; return (string) $value;
} }
throw new Error("Cannot represent value as ID: " . Utils::printSafe($value)); throw new Error('Cannot represent value as ID: ' . Utils::printSafe($value));
} }
/** /**
* @param Node $valueNode * @param Node $valueNode
* @param array|null $variables * @param mixed[]|null $variables
* @return null|string * @return null|string
* @throws \Exception * @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, ?array $variables = null)
{ {
if ($valueNode instanceof StringValueNode || $valueNode instanceof IntValueNode) { if ($valueNode instanceof StringValueNode || $valueNode instanceof IntValueNode) {
return $valueNode->value; return $valueNode->value;

View File

@ -1,44 +1,33 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InputValueDefinitionNode; use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function sprintf;
/**
* Class InputObjectField
* @package GraphQL\Type\Definition
*/
class InputObjectField class InputObjectField
{ {
/** /** @var string */
* @var string
*/
public $name; public $name;
/** /** @var mixed|null */
* @var mixed|null
*/
public $defaultValue; public $defaultValue;
/** /** @var string|null */
* @var string|null
*/
public $description; public $description;
/** /** @var callback|InputType */
* @var callback|InputType
*/
public $type; public $type;
/** /** @var InputValueDefinitionNode|null */
* @var InputValueDefinitionNode|null
*/
public $astNode; public $astNode;
/** /** @var mixed[] */
* @var array
*/
public $config; public $config;
/** /**
@ -49,8 +38,8 @@ class InputObjectField
private $defaultValueExists = false; private $defaultValueExists = false;
/** /**
* InputObjectField constructor. *
* @param array $opts * @param mixed[] $opts
*/ */
public function __construct(array $opts) public function __construct(array $opts)
{ {
@ -86,7 +75,6 @@ class InputObjectField
} }
/** /**
* @param Type $parentType
* @throws InvariantViolation * @throws InvariantViolation
*/ */
public function assertValid(Type $parentType) public function assertValid(Type $parentType)
@ -94,7 +82,7 @@ class InputObjectField
try { try {
Utils::assertValidName($this->name); Utils::assertValidName($this->name);
} catch (Error $e) { } catch (Error $e) {
throw new InvariantViolation("{$parentType->name}.{$this->name}: {$e->getMessage()}"); throw new InvariantViolation(sprintf('%s.%s: %s', $parentType->name, $this->name, $e->getMessage()));
} }
$type = $this->type; $type = $this->type;
if ($type instanceof WrappingType) { if ($type instanceof WrappingType) {
@ -102,12 +90,20 @@ class InputObjectField
} }
Utils::invariant( Utils::invariant(
$type instanceof InputType, $type instanceof InputType,
"{$parentType->name}.{$this->name} field type must be Input Type but got: " . Utils::printSafe($this->type) sprintf(
'%s.%s field type must be Input Type but got: %s',
$parentType->name,
$this->name,
Utils::printSafe($this->type)
)
); );
Utils::invariant( Utils::invariant(
empty($this->config['resolve']), empty($this->config['resolve']),
"{$parentType->name}.{$this->name} field type has a resolve property, " . sprintf(
'but Input Types cannot define resolvers.' '%s.%s field type has a resolve property, but Input Types cannot define resolvers.',
$parentType->name,
$this->name
)
); );
} }
} }

View File

@ -1,33 +1,37 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function call_user_func;
use function is_array;
use function is_callable;
use function is_string;
use function sprintf;
use function spritnf;
/** /**
* Class InputObjectType * Class InputObjectType
* @package GraphQL\Type\Definition
*/ */
class InputObjectType extends Type implements InputType, NamedType class InputObjectType extends Type implements InputType, NamedType
{ {
/** /** @var InputObjectTypeDefinitionNode|null */
* @var InputObjectField[] public $astNode;
*/
/** @var InputObjectField[] */
private $fields; private $fields;
/** /**
* @var InputObjectTypeDefinitionNode|null *
*/ * @param mixed[] $config
public $astNode;
/**
* InputObjectType constructor.
* @param array $config
*/ */
public function __construct(array $config) public function __construct(array $config)
{ {
if (!isset($config['name'])) { if (! isset($config['name'])) {
$config['name'] = $this->tryInferName(); $config['name'] = $this->tryInferName();
} }
@ -35,8 +39,23 @@ class InputObjectType extends Type implements InputType, NamedType
$this->config = $config; $this->config = $config;
$this->name = $config['name']; $this->name = $config['name'];
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null; $this->astNode = $config['astNode'] ?? null;
$this->description = isset($config['description']) ? $config['description'] : null; $this->description = $config['description'] ?? null;
}
/**
* @param string $name
* @return InputObjectField
* @throws \Exception
*/
public function getField($name)
{
if ($this->fields === null) {
$this->getFields();
}
Utils::invariant(isset($this->fields[$name]), "Field '%s' is not defined for type '%s'", $name, $this->name);
return $this->fields[$name];
} }
/** /**
@ -44,14 +63,14 @@ class InputObjectType extends Type implements InputType, NamedType
*/ */
public function getFields() public function getFields()
{ {
if (null === $this->fields) { if ($this->fields === null) {
$this->fields = []; $this->fields = [];
$fields = isset($this->config['fields']) ? $this->config['fields'] : []; $fields = $this->config['fields'] ?? [];
$fields = is_callable($fields) ? call_user_func($fields) : $fields; $fields = is_callable($fields) ? call_user_func($fields) : $fields;
if (!is_array($fields)) { if (! is_array($fields)) {
throw new InvariantViolation( throw new InvariantViolation(
"{$this->name} fields must be an array or a callable which returns such an array." spritnf('%s fields must be an array or a callable which returns such an array.', $this->name)
); );
} }
@ -67,20 +86,6 @@ class InputObjectType extends Type implements InputType, NamedType
return $this->fields; return $this->fields;
} }
/**
* @param string $name
* @return InputObjectField
* @throws \Exception
*/
public function getField($name)
{
if (null === $this->fields) {
$this->getFields();
}
Utils::invariant(isset($this->fields[$name]), "Field '%s' is not defined for type '%s'", $name, $this->name);
return $this->fields[$name];
}
/** /**
* Validates type config and throws if one of type options is invalid. * Validates type config and throws if one of type options is invalid.
* Note: this method is shallow, it won't validate object fields and their arguments. * Note: this method is shallow, it won't validate object fields and their arguments.
@ -92,9 +97,11 @@ class InputObjectType extends Type implements InputType, NamedType
parent::assertValid(); parent::assertValid();
Utils::invariant( Utils::invariant(
!empty($this->getFields()), ! empty($this->getFields()),
"{$this->name} fields must be an associative array with field names as keys or a " . sprintf(
"callable which returns such an array." '%s fields must be an associative array with field names as keys or a callable which returns such an array.',
$this->name
)
); );
foreach ($this->getFields() as $field) { foreach ($this->getFields() as $field) {

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
/* /*
@ -14,6 +17,7 @@ export type GraphQLInputType =
| GraphQLList<GraphQLInputType>, | GraphQLList<GraphQLInputType>,
>; >;
*/ */
interface InputType interface InputType
{ {
} }

View File

@ -1,13 +1,20 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\IntValueNode; use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function floatval;
use function intval;
use function is_bool;
use function is_numeric;
/** /**
* Class IntType * Class IntType
* @package GraphQL\Type\Definition
*/ */
class IntType extends ScalarType class IntType extends ScalarType
{ {
@ -16,19 +23,15 @@ class IntType extends ScalarType
// //
// n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because // n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because
// they are internally represented as IEEE 754 doubles. // they are internally represented as IEEE 754 doubles.
const MAX_INT = 2147483647; private const MAX_INT = 2147483647;
const MIN_INT = -2147483648; private const MIN_INT = -2147483648;
/** /** @var string */
* @var string
*/
public $name = Type::INT; public $name = Type::INT;
/** /** @var string */
* @var string
*/
public $description = public $description =
'The `Int` scalar type represents non-fractional signed whole numeric 'The `Int` scalar type represents non-fractional signed whole numeric
values. Int can represent values between -(2^31) and 2^31 - 1. '; values. Int can represent values between -(2^31) and 2^31 - 1. ';
/** /**
@ -41,6 +44,38 @@ values. Int can represent values between -(2^31) and 2^31 - 1. ';
return $this->coerceInt($value); return $this->coerceInt($value);
} }
/**
* @param mixed $value
* @return int
*/
private function coerceInt($value)
{
if ($value === '') {
throw new Error(
'Int cannot represent non 32-bit signed integer value: (empty string)'
);
}
$num = floatval($value);
if ((! is_numeric($value) && ! is_bool($value)) || $num > self::MAX_INT || $num < self::MIN_INT) {
throw new Error(
'Int cannot represent non 32-bit signed integer value: ' .
Utils::printSafe($value)
);
}
$int = intval($num);
// int cast with == used for performance reasons
// @codingStandardsIgnoreLine
if ($int != $num) {
throw new Error(
'Int cannot represent non-integer value: ' .
Utils::printSafe($value)
);
}
return $int;
}
/** /**
* @param mixed $value * @param mixed $value
* @return int|null * @return int|null
@ -52,12 +87,12 @@ values. Int can represent values between -(2^31) and 2^31 - 1. ';
} }
/** /**
* @param $valueNode * @param Node $valueNode
* @param array|null $variables * @param mixed[]|null $variables
* @return int|null * @return int|null
* @throws \Exception * @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, ?array $variables = null)
{ {
if ($valueNode instanceof IntValueNode) { if ($valueNode instanceof IntValueNode) {
$val = (int) $valueNode->value; $val = (int) $valueNode->value;
@ -69,28 +104,4 @@ values. Int can represent values between -(2^31) and 2^31 - 1. ';
// Intentionally without message, as all information already in wrapped Exception // Intentionally without message, as all information already in wrapped Exception
throw new \Exception(); throw new \Exception();
} }
private function coerceInt($value) {
if ($value === '') {
throw new Error(
'Int cannot represent non 32-bit signed integer value: (empty string)'
);
}
$num = floatval($value);
if (!is_numeric($value) && !is_bool($value) || $num > self::MAX_INT || $num < self::MIN_INT) {
throw new Error(
'Int cannot represent non 32-bit signed integer value: ' .
Utils::printSafe($value)
);
}
$int = intval($num);
if ($int != $num) {
throw new Error(
'Int cannot represent non-integer value: ' .
Utils::printSafe($value)
);
}
return $int;
}
} }

View File

@ -1,17 +1,50 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode; use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeExtensionNode; use GraphQL\Language\AST\InterfaceTypeExtensionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_callable;
use function is_string;
use function sprintf;
/** /**
* Class InterfaceType * Class InterfaceType
* @package GraphQL\Type\Definition
*/ */
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NamedType class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NamedType
{ {
/** @var InterfaceTypeDefinitionNode|null */
public $astNode;
/** @var InterfaceTypeExtensionNode[] */
public $extensionASTNodes;
/** @var FieldDefinition[] */
private $fields;
/**
*
* @param mixed[] $config
*/
public function __construct(array $config)
{
if (! isset($config['name'])) {
$config['name'] = $this->tryInferName();
}
Utils::invariant(is_string($config['name']), 'Must provide name.');
$this->name = $config['name'];
$this->description = $config['description'] ?? null;
$this->astNode = $config['astNode'] ?? null;
$this->extensionASTNodes = $config['extensionASTNodes'] ?? null;
$this->config = $config;
}
/** /**
* @param mixed $type * @param mixed $type
* @return self * @return self
@ -27,37 +60,17 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
} }
/** /**
* @var FieldDefinition[] * @param string $name
* @return FieldDefinition
*/ */
private $fields; public function getField($name)
/**
* @var InterfaceTypeDefinitionNode|null
*/
public $astNode;
/**
* @var InterfaceTypeExtensionNode[]
*/
public $extensionASTNodes;
/**
* InterfaceType constructor.
* @param array $config
*/
public function __construct(array $config)
{ {
if (!isset($config['name'])) { if ($this->fields === null) {
$config['name'] = $this->tryInferName(); $this->getFields();
} }
Utils::invariant(isset($this->fields[$name]), 'Field "%s" is not defined for type "%s"', $name, $this->name);
Utils::invariant(is_string($config['name']), 'Must provide name.'); return $this->fields[$name];
$this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->extensionASTNodes = isset($config['extensionASTNodes']) ? $config['extensionASTNodes'] : null;
$this->config = $config;
} }
/** /**
@ -65,41 +78,29 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
*/ */
public function getFields() public function getFields()
{ {
if (null === $this->fields) { if ($this->fields === null) {
$fields = isset($this->config['fields']) ? $this->config['fields'] : []; $fields = $this->config['fields'] ?? [];
$this->fields = FieldDefinition::defineFieldMap($this, $fields); $this->fields = FieldDefinition::defineFieldMap($this, $fields);
} }
return $this->fields;
}
/** return $this->fields;
* @param $name
* @return FieldDefinition
* @throws \Exception
*/
public function getField($name)
{
if (null === $this->fields) {
$this->getFields();
}
Utils::invariant(isset($this->fields[$name]), 'Field "%s" is not defined for type "%s"', $name, $this->name);
return $this->fields[$name];
} }
/** /**
* Resolves concrete ObjectType for given object value * Resolves concrete ObjectType for given object value
* *
* @param $objectValue * @param object $objectValue
* @param $context * @param mixed[] $context
* @param ResolveInfo $info
* @return callable|null * @return callable|null
*/ */
public function resolveType($objectValue, $context, ResolveInfo $info) public function resolveType($objectValue, $context, ResolveInfo $info)
{ {
if (isset($this->config['resolveType'])) { if (isset($this->config['resolveType'])) {
$fn = $this->config['resolveType']; $fn = $this->config['resolveType'];
return $fn($objectValue, $context, $info); return $fn($objectValue, $context, $info);
} }
return null; return null;
} }
@ -113,8 +114,12 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
$resolveType = $this->config['resolveType'] ?? null; $resolveType = $this->config['resolveType'] ?? null;
Utils::invariant( Utils::invariant(
!isset($resolveType) || is_callable($resolveType), ! isset($resolveType) || is_callable($resolveType),
"{$this->name} must provide \"resolveType\" as a function, but got: " . Utils::printSafe($resolveType) sprintf(
'%s must provide "resolveType" as a function, but got: %s',
$this->name,
Utils::printSafe($resolveType)
)
); );
} }
} }

View File

@ -1,14 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use \GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
/* /*
export type GraphQLLeafType = export type GraphQLLeafType =
GraphQLScalarType | GraphQLScalarType |
GraphQLEnumType; GraphQLEnumType;
*/ */
interface LeafType interface LeafType
{ {
/** /**
@ -37,9 +41,9 @@ interface LeafType
* In the case of an invalid node or value this method must throw an Exception * In the case of an invalid node or value this method must throw an Exception
* *
* @param Node $valueNode * @param Node $valueNode
* @param array|null $variables * @param mixed[]|null $variables
* @return mixed * @return mixed
* @throws \Exception * @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null); public function parseLiteral($valueNode, ?array $variables = null);
} }

View File

@ -1,18 +1,15 @@
<?php <?php
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; declare(strict_types=1);
use GraphQL\Utils\Utils;
namespace GraphQL\Type\Definition;
/** /**
* Class ListOfType * Class ListOfType
* @package GraphQL\Type\Definition
*/ */
class ListOfType extends Type implements WrappingType, OutputType, InputType class ListOfType extends Type implements WrappingType, OutputType, InputType
{ {
/** /** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */
* @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType
*/
public $ofType; public $ofType;
/** /**
@ -30,6 +27,7 @@ class ListOfType extends Type implements WrappingType, OutputType, InputType
{ {
$type = $this->ofType; $type = $this->ofType;
$str = $type instanceof Type ? $type->toString() : (string) $type; $str = $type instanceof Type ? $type->toString() : (string) $type;
return '[' . $str . ']'; return '[' . $str . ']';
} }
@ -40,6 +38,7 @@ class ListOfType extends Type implements WrappingType, OutputType, InputType
public function getWrappedType($recurse = false) public function getWrappedType($recurse = false)
{ {
$type = $this->ofType; $type = $this->ofType;
return ($recurse && $type instanceof WrappingType) ? $type->getWrappedType($recurse) : $type; return ($recurse && $type instanceof WrappingType) ? $type->getWrappedType($recurse) : $type;
} }
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
/* /*
@ -10,6 +13,7 @@ export type GraphQLNamedType =
| GraphQLEnumType | GraphQLEnumType
| GraphQLInputObjectType; | GraphQLInputObjectType;
*/ */
interface NamedType interface NamedType
{ {
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
@ -6,10 +9,35 @@ use GraphQL\Utils\Utils;
/** /**
* Class NonNull * Class NonNull
* @package GraphQL\Type\Definition
*/ */
class NonNull extends Type implements WrappingType, OutputType, InputType class NonNull extends Type implements WrappingType, OutputType, InputType
{ {
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType */
private $ofType;
/**
* @param callable|Type $type
* @throws \Exception
*/
public function __construct($type)
{
$this->ofType = self::assertNullableType($type);
}
/**
* @param mixed $type
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
*/
public static function assertNullableType($type)
{
Utils::invariant(
Type::isType($type) && ! $type instanceof self,
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL nullable type.'
);
return $type;
}
/** /**
* @param mixed $type * @param mixed $type
* @return self * @return self
@ -25,31 +53,11 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
} }
/** /**
* @param mixed $type * @return string
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
*/ */
public static function assertNullableType($type) public function toString()
{ {
Utils::invariant( return $this->getWrappedType()->toString() . '!';
Type::isType($type) && !$type instanceof self,
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL nullable type.'
);
return $type;
}
/**
* @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
*/
private $ofType;
/**
* @param callable|Type $type
* @throws \Exception
*/
public function __construct($type)
{
$this->ofType = self::assertNullableType($type);
} }
/** /**
@ -60,14 +68,7 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
public function getWrappedType($recurse = false) public function getWrappedType($recurse = false)
{ {
$type = $this->ofType; $type = $this->ofType;
return ($recurse && $type instanceof WrappingType) ? $type->getWrappedType($recurse) : $type; return ($recurse && $type instanceof WrappingType) ? $type->getWrappedType($recurse) : $type;
} }
/**
* @return string
*/
public function toString()
{
return $this->getWrappedType()->toString() . '!';
}
} }

View File

@ -1,11 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ObjectTypeExtensionNode; use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function call_user_func;
use function is_array;
use function is_callable;
use function is_string;
use function sprintf;
/** /**
* Object Type Definition * Object Type Definition
@ -49,6 +56,44 @@ use GraphQL\Utils\Utils;
*/ */
class ObjectType extends Type implements OutputType, CompositeType, NamedType class ObjectType extends Type implements OutputType, CompositeType, NamedType
{ {
/** @var ObjectTypeDefinitionNode|null */
public $astNode;
/** @var ObjectTypeExtensionNode[] */
public $extensionASTNodes;
/** @var callable */
public $resolveFieldFn;
/** @var FieldDefinition[] */
private $fields;
/** @var InterfaceType[] */
private $interfaces;
/** @var InterfaceType[]|null */
private $interfaceMap;
/**
*
* @param mixed[] $config
*/
public function __construct(array $config)
{
if (! isset($config['name'])) {
$config['name'] = $this->tryInferName();
}
Utils::invariant(is_string($config['name']), 'Must provide name.');
$this->name = $config['name'];
$this->description = $config['description'] ?? null;
$this->resolveFieldFn = $config['resolveField'] ?? null;
$this->astNode = $config['astNode'] ?? null;
$this->extensionASTNodes = $config['extensionASTNodes'] ?? [];
$this->config = $config;
}
/** /**
* @param mixed $type * @param mixed $type
* @return self * @return self
@ -64,53 +109,18 @@ class ObjectType extends Type implements OutputType, CompositeType, NamedType
} }
/** /**
* @var FieldDefinition[] * @param string $name
* @return FieldDefinition
* @throws \Exception
*/ */
private $fields; public function getField($name)
/**
* @var InterfaceType[]
*/
private $interfaces;
/**
* @var array
*/
private $interfaceMap;
/**
* @var ObjectTypeDefinitionNode|null
*/
public $astNode;
/**
* @var ObjectTypeExtensionNode[]
*/
public $extensionASTNodes;
/**
* @var callable
*/
public $resolveFieldFn;
/**
* ObjectType constructor.
* @param array $config
*/
public function __construct(array $config)
{ {
if (!isset($config['name'])) { if ($this->fields === null) {
$config['name'] = $this->tryInferName(); $this->getFields();
} }
Utils::invariant(isset($this->fields[$name]), 'Field "%s" is not defined for type "%s"', $name, $this->name);
Utils::invariant(is_string($config['name']), 'Must provide name.'); return $this->fields[$name];
$this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null;
$this->resolveFieldFn = isset($config['resolveField']) ? $config['resolveField'] : null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null;
$this->extensionASTNodes = isset($config['extensionASTNodes']) ? $config['extensionASTNodes'] : [];
$this->config = $config;
} }
/** /**
@ -119,58 +129,14 @@ class ObjectType extends Type implements OutputType, CompositeType, NamedType
*/ */
public function getFields() public function getFields()
{ {
if (null === $this->fields) { if ($this->fields === null) {
$fields = isset($this->config['fields']) ? $this->config['fields'] : []; $fields = $this->config['fields'] ?? [];
$this->fields = FieldDefinition::defineFieldMap($this, $fields); $this->fields = FieldDefinition::defineFieldMap($this, $fields);
} }
return $this->fields; return $this->fields;
} }
/**
* @param string $name
* @return FieldDefinition
* @throws \Exception
*/
public function getField($name)
{
if (null === $this->fields) {
$this->getFields();
}
Utils::invariant(isset($this->fields[$name]), 'Field "%s" is not defined for type "%s"', $name, $this->name);
return $this->fields[$name];
}
/**
* @return InterfaceType[]
*/
public function getInterfaces()
{
if (null === $this->interfaces) {
$interfaces = isset($this->config['interfaces']) ? $this->config['interfaces'] : [];
$interfaces = is_callable($interfaces) ? call_user_func($interfaces) : $interfaces;
if ($interfaces && !is_array($interfaces)) {
throw new InvariantViolation(
"{$this->name} interfaces must be an Array or a callable which returns an Array."
);
}
$this->interfaces = $interfaces ?: [];
}
return $this->interfaces;
}
private function getInterfaceMap()
{
if (!$this->interfaceMap) {
$this->interfaceMap = [];
foreach ($this->getInterfaces() as $interface) {
$this->interfaceMap[$interface->name] = $interface;
}
}
return $this->interfaceMap;
}
/** /**
* @param InterfaceType $iface * @param InterfaceType $iface
* @return bool * @return bool
@ -178,18 +144,56 @@ class ObjectType extends Type implements OutputType, CompositeType, NamedType
public function implementsInterface($iface) public function implementsInterface($iface)
{ {
$map = $this->getInterfaceMap(); $map = $this->getInterfaceMap();
return isset($map[$iface->name]); return isset($map[$iface->name]);
} }
private function getInterfaceMap()
{
if (! $this->interfaceMap) {
$this->interfaceMap = [];
foreach ($this->getInterfaces() as $interface) {
$this->interfaceMap[$interface->name] = $interface;
}
}
return $this->interfaceMap;
}
/** /**
* @param $value * @return InterfaceType[]
* @param $context */
* @param ResolveInfo $info public function getInterfaces()
{
if ($this->interfaces === null) {
$interfaces = $this->config['interfaces'] ?? [];
$interfaces = is_callable($interfaces) ? call_user_func($interfaces) : $interfaces;
if ($interfaces && ! is_array($interfaces)) {
throw new InvariantViolation(
sprintf('%s interfaces must be an Array or a callable which returns an Array.', $this->name)
);
}
$this->interfaces = $interfaces ?: [];
}
return $this->interfaces;
}
/**
* @param mixed[] $value
* @param mixed[]|null $context
* @return bool|null * @return bool|null
*/ */
public function isTypeOf($value, $context, ResolveInfo $info) public function isTypeOf($value, $context, ResolveInfo $info)
{ {
return isset($this->config['isTypeOf']) ? call_user_func($this->config['isTypeOf'], $value, $context, $info) : null; return isset($this->config['isTypeOf']) ? call_user_func(
$this->config['isTypeOf'],
$value,
$context,
$info
) : null;
} }
/** /**
@ -203,15 +207,19 @@ class ObjectType extends Type implements OutputType, CompositeType, NamedType
parent::assertValid(); parent::assertValid();
Utils::invariant( Utils::invariant(
null === $this->description || is_string($this->description), $this->description === null || is_string($this->description),
"{$this->name} description must be string if set, but it is: " . Utils::printSafe($this->description) sprintf(
'%s description must be string if set, but it is: %s',
$this->name,
Utils::printSafe($this->description)
)
); );
$isTypeOf = $this->config['isTypeOf'] ?? null; $isTypeOf = $this->config['isTypeOf'] ?? null;
Utils::invariant( Utils::invariant(
!isset($isTypeOf) || is_callable($isTypeOf), $isTypeOf === null || is_callable($isTypeOf),
"{$this->name} must provide \"isTypeOf\" as a function, but got: " . Utils::printSafe($isTypeOf) sprintf('%s must provide "isTypeOf" as a function, but got: %s', $this->name, Utils::printSafe($isTypeOf))
); );
foreach ($this->getFields() as $field) { foreach ($this->getFields() as $field) {

View File

@ -1,6 +1,8 @@
<?php <?php
namespace GraphQL\Type\Definition;
declare(strict_types=1);
namespace GraphQL\Type\Definition;
/* /*
GraphQLScalarType | GraphQLScalarType |
@ -11,6 +13,7 @@ GraphQLEnumType |
GraphQLList | GraphQLList |
GraphQLNonNull; GraphQLNonNull;
*/ */
interface OutputType interface OutputType
{ {
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FieldNode;
@ -9,6 +12,7 @@ use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function array_merge_recursive;
/** /**
* Structure containing information useful for field resolution process. * Structure containing information useful for field resolution process.
@ -20,7 +24,7 @@ class ResolveInfo
* The name of the field being resolved * The name of the field being resolved
* *
* @api * @api
* @var string * @var string|null
*/ */
public $fieldName; public $fieldName;
@ -28,7 +32,7 @@ class ResolveInfo
* AST of all nodes referencing this field in the query. * AST of all nodes referencing this field in the query.
* *
* @api * @api
* @var FieldNode[] * @var FieldNode[]|null
*/ */
public $fieldNodes; public $fieldNodes;
@ -44,7 +48,7 @@ class ResolveInfo
* Parent type of the field being resolved * Parent type of the field being resolved
* *
* @api * @api
* @var ObjectType * @var ObjectType|null
*/ */
public $parentType; public $parentType;
@ -52,7 +56,7 @@ class ResolveInfo
* Path to this field from the very root value * Path to this field from the very root value
* *
* @api * @api
* @var array * @var mixed[]|null
*/ */
public $path; public $path;
@ -60,7 +64,7 @@ class ResolveInfo
* Instance of a schema used for execution * Instance of a schema used for execution
* *
* @api * @api
* @var Schema * @var Schema|null
*/ */
public $schema; public $schema;
@ -68,7 +72,7 @@ class ResolveInfo
* AST of all fragments defined in query * AST of all fragments defined in query
* *
* @api * @api
* @var FragmentDefinitionNode[] * @var FragmentDefinitionNode[]|null
*/ */
public $fragments; public $fragments;
@ -76,7 +80,7 @@ class ResolveInfo
* Root value passed to query execution * Root value passed to query execution
* *
* @api * @api
* @var mixed * @var mixed|null
*/ */
public $rootValue; public $rootValue;
@ -84,7 +88,7 @@ class ResolveInfo
* AST of operation definition node (query, mutation) * AST of operation definition node (query, mutation)
* *
* @api * @api
* @var OperationDefinitionNode * @var OperationDefinitionNode|null
*/ */
public $operation; public $operation;
@ -92,10 +96,13 @@ class ResolveInfo
* Array of variables passed to query execution * Array of variables passed to query execution
* *
* @api * @api
* @var array * @var mixed[]|null
*/ */
public $variableValues; public $variableValues;
/**
* @param mixed[] $values
*/
public function __construct(array $values) public function __construct(array $values)
{ {
Utils::assign($this, $values); Utils::assign($this, $values);
@ -134,7 +141,7 @@ class ResolveInfo
* *
* @api * @api
* @param int $depth How many levels to include in output * @param int $depth How many levels to include in output
* @return array * @return bool[]
*/ */
public function getFieldSelection($depth = 0) public function getFieldSelection($depth = 0)
{ {
@ -148,24 +155,33 @@ class ResolveInfo
return $fields; return $fields;
} }
private function foldSelectionSet(SelectionSetNode $selectionSet, $descend) /**
* @return bool[]
*/
private function foldSelectionSet(SelectionSetNode $selectionSet, int $descend) : array
{ {
$fields = []; $fields = [];
foreach ($selectionSet->selections as $selectionNode) { foreach ($selectionSet->selections as $selectionNode) {
if ($selectionNode instanceof FieldNode) { if ($selectionNode instanceof FieldNode) {
$fields[$selectionNode->name->value] = $descend > 0 && !empty($selectionNode->selectionSet) $fields[$selectionNode->name->value] = $descend > 0 && ! empty($selectionNode->selectionSet)
? $this->foldSelectionSet($selectionNode->selectionSet, $descend - 1) ? $this->foldSelectionSet($selectionNode->selectionSet, $descend - 1)
: true; : true;
} else if ($selectionNode instanceof FragmentSpreadNode) { } elseif ($selectionNode instanceof FragmentSpreadNode) {
$spreadName = $selectionNode->name->value; $spreadName = $selectionNode->name->value;
if (isset($this->fragments[$spreadName])) { if (isset($this->fragments[$spreadName])) {
/** @var FragmentDefinitionNode $fragment */ /** @var FragmentDefinitionNode $fragment */
$fragment = $this->fragments[$spreadName]; $fragment = $this->fragments[$spreadName];
$fields = array_merge_recursive($this->foldSelectionSet($fragment->selectionSet, $descend), $fields); $fields = array_merge_recursive(
$this->foldSelectionSet($fragment->selectionSet, $descend),
$fields
);
} }
} else if ($selectionNode instanceof InlineFragmentNode) { } elseif ($selectionNode instanceof InlineFragmentNode) {
$fields = array_merge_recursive($this->foldSelectionSet($selectionNode->selectionSet, $descend), $fields); $fields = array_merge_recursive(
$this->foldSelectionSet($selectionNode->selectionSet, $descend),
$fields
);
} }
} }

View File

@ -1,8 +1,12 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\ScalarTypeDefinitionNode; use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_string;
/** /**
* Scalar Type Definition * Scalar Type Definition
@ -24,16 +28,17 @@ use GraphQL\Utils\Utils;
*/ */
abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NamedType abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NamedType
{ {
/** /** @var ScalarTypeDefinitionNode|null */
* @var ScalarTypeDefinitionNode|null
*/
public $astNode; public $astNode;
function __construct(array $config = []) /**
* @param mixed[] $config
*/
public function __construct(array $config = [])
{ {
$this->name = isset($config['name']) ? $config['name'] : $this->tryInferName(); $this->name = $config['name'] ?? $this->tryInferName();
$this->description = isset($config['description']) ? $config['description'] : $this->description; $this->description = $config['description'] ?? $this->description;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null; $this->astNode = $config['astNode'] ?? null;
$this->config = $config; $this->config = $config;
Utils::invariant(is_string($this->name), 'Must provide name.'); Utils::invariant(is_string($this->name), 'Must provide name.');

View File

@ -1,26 +1,29 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\AST\StringValueNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_array;
use function is_object;
use function is_scalar;
use function method_exists;
/** /**
* Class StringType * Class StringType
* @package GraphQL\Type\Definition
*/ */
class StringType extends ScalarType class StringType extends ScalarType
{ {
/** /** @var string */
* @var string
*/
public $name = Type::STRING; public $name = Type::STRING;
/** /** @var string */
* @var string
*/
public $description = public $description =
'The `String` scalar type represents textual data, represented as UTF-8 'The `String` scalar type represents textual data, represented as UTF-8
character sequences. The String type is most often used by GraphQL to character sequences. The String type is most often used by GraphQL to
represent free-form human-readable text.'; represent free-form human-readable text.';
@ -43,12 +46,25 @@ represent free-form human-readable text.';
if (is_object($value) && method_exists($value, '__toString')) { if (is_object($value) && method_exists($value, '__toString')) {
return (string) $value; return (string) $value;
} }
if (!is_scalar($value)) { if (! is_scalar($value)) {
throw new Error("String cannot represent non scalar value: " . Utils::printSafe($value)); throw new Error('String cannot represent non scalar value: ' . Utils::printSafe($value));
} }
return $this->coerceString($value); return $this->coerceString($value);
} }
private function coerceString($value)
{
if (is_array($value)) {
throw new Error(
'String cannot represent an array value: ' .
Utils::printSafe($value)
);
}
return (string) $value;
}
/** /**
* @param mixed $value * @param mixed $value
* @return string * @return string
@ -60,12 +76,12 @@ represent free-form human-readable text.';
} }
/** /**
* @param $valueNode * @param Node $valueNode
* @param array|null $variables * @param mixed[]|null $variables
* @return null|string * @return null|string
* @throws \Exception * @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, ?array $variables = null)
{ {
if ($valueNode instanceof StringValueNode) { if ($valueNode instanceof StringValueNode) {
return $valueNode->value; return $valueNode->value;
@ -74,15 +90,4 @@ represent free-form human-readable text.';
// Intentionally without message, as all information already in wrapped Exception // Intentionally without message, as all information already in wrapped Exception
throw new \Exception(); throw new \Exception();
} }
private function coerceString($value) {
if (is_array($value)) {
throw new Error(
'String cannot represent an array value: ' .
Utils::printSafe($value)
);
}
return (string) $value;
}
} }

View File

@ -1,35 +1,48 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\TypeDefinitionNode; use GraphQL\Language\AST\TypeDefinitionNode;
use GraphQL\Type\Introspection; use GraphQL\Type\Introspection;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function array_keys;
use function array_merge;
use function in_array;
use function preg_replace;
/** /**
* Registry of standard GraphQL types * Registry of standard GraphQL types
* and a base class for all other types. * and a base class for all other types.
*
* @package GraphQL\Type\Definition
*/ */
abstract class Type implements \JsonSerializable abstract class Type implements \JsonSerializable
{ {
const STRING = 'String'; public const STRING = 'String';
const INT = 'Int'; public const INT = 'Int';
const BOOLEAN = 'Boolean'; public const BOOLEAN = 'Boolean';
const FLOAT = 'Float'; public const FLOAT = 'Float';
const ID = 'ID'; public const ID = 'ID';
/** /** @var Type[] */
* @var array
*/
private static $internalTypes; private static $internalTypes;
/** /** @var Type[] */
* @var array
*/
private static $builtInTypes; private static $builtInTypes;
/** @var string */
public $name;
/** @var string|null */
public $description;
/** @var TypeDefinitionNode|null */
public $astNode;
/** @var mixed[] */
public $config;
/** /**
* @api * @api
* @return IDType * @return IDType
@ -39,6 +52,25 @@ abstract class Type implements \JsonSerializable
return self::getInternalType(self::ID); return self::getInternalType(self::ID);
} }
/**
* @param string $name
* @return (IDType|StringType|FloatType|IntType|BooleanType)[]|IDType|StringType|FloatType|IntType|BooleanType
*/
private static function getInternalType($name = null)
{
if (self::$internalTypes === null) {
self::$internalTypes = [
self::ID => new IDType(),
self::STRING => new StringType(),
self::FLOAT => new FloatType(),
self::INT => new IntType(),
self::BOOLEAN => new BooleanType(),
];
}
return $name ? self::$internalTypes[$name] : self::$internalTypes;
}
/** /**
* @api * @api
* @return StringType * @return StringType
@ -96,21 +128,31 @@ abstract class Type implements \JsonSerializable
} }
/** /**
* @param $name * Checks if the type is a builtin type
* @return array|IDType|StringType|FloatType|IntType|BooleanType *
* @return bool
*/ */
private static function getInternalType($name = null) public static function isBuiltInType(Type $type)
{ {
if (null === self::$internalTypes) { return in_array($type->name, array_keys(self::getAllBuiltInTypes()));
self::$internalTypes = [
self::ID => new IDType(),
self::STRING => new StringType(),
self::FLOAT => new FloatType(),
self::INT => new IntType(),
self::BOOLEAN => new BooleanType()
];
} }
return $name ? self::$internalTypes[$name] : self::$internalTypes;
/**
* Returns all builtin in types including base scalar and
* introspection types
*
* @return Type[]
*/
public static function getAllBuiltInTypes()
{
if (self::$builtInTypes === null) {
self::$builtInTypes = array_merge(
Introspection::getTypes(),
self::getInternalTypes()
);
}
return self::$builtInTypes;
} }
/** /**
@ -123,34 +165,6 @@ abstract class Type implements \JsonSerializable
return self::getInternalType(); return self::getInternalType();
} }
/**
* Returns all builtin in types including base scalar and
* introspection types
*
* @return Type[]
*/
public static function getAllBuiltInTypes()
{
if (null === self::$builtInTypes) {
self::$builtInTypes = array_merge(
Introspection::getTypes(),
self::getInternalTypes()
);
}
return self::$builtInTypes;
}
/**
* Checks if the type is a builtin type
*
* @param Type $type
* @return bool
*/
public static function isBuiltInType(Type $type)
{
return in_array($type->name, array_keys(self::getAllBuiltInTypes()));
}
/** /**
* @api * @api
* @param Type $type * @param Type $type
@ -160,11 +174,28 @@ abstract class Type implements \JsonSerializable
{ {
return $type instanceof InputType && return $type instanceof InputType &&
( (
!$type instanceof WrappingType || ! $type instanceof WrappingType ||
self::getNamedType($type) instanceof InputType self::getNamedType($type) instanceof InputType
); );
} }
/**
* @api
* @param Type $type
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType
*/
public static function getNamedType($type)
{
if ($type === null) {
return null;
}
while ($type instanceof WrappingType) {
$type = $type->getWrappedType();
}
return $type;
}
/** /**
* @api * @api
* @param Type $type * @param Type $type
@ -174,14 +205,14 @@ abstract class Type implements \JsonSerializable
{ {
return $type instanceof OutputType && return $type instanceof OutputType &&
( (
!$type instanceof WrappingType || ! $type instanceof WrappingType ||
self::getNamedType($type) instanceof OutputType self::getNamedType($type) instanceof OutputType
); );
} }
/** /**
* @api * @api
* @param $type * @param Type $type
* @return bool * @return bool
*/ */
public static function isLeafType($type) public static function isLeafType($type)
@ -209,16 +240,6 @@ abstract class Type implements \JsonSerializable
return $type instanceof AbstractType; return $type instanceof AbstractType;
} }
/**
* @api
* @param Type $type
* @return bool
*/
public static function isType($type)
{
return $type instanceof Type;
}
/** /**
* @param mixed $type * @param mixed $type
* @return mixed * @return mixed
@ -233,6 +254,16 @@ abstract class Type implements \JsonSerializable
return $type; return $type;
} }
/**
* @api
* @param Type $type
* @return bool
*/
public static function isType($type)
{
return $type instanceof Type;
}
/** /**
* @api * @api
* @param Type $type * @param Type $type
@ -244,40 +275,42 @@ abstract class Type implements \JsonSerializable
} }
/** /**
* @api * @throws InvariantViolation
* @param Type $type
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType
*/ */
public static function getNamedType($type) public function assertValid()
{ {
if (null === $type) { Utils::assertValidName($this->name);
return null;
}
while ($type instanceof WrappingType) {
$type = $type->getWrappedType();
}
return $type;
} }
/** /**
* @var string * @return string
*/ */
public $name; public function jsonSerialize()
{
return $this->toString();
}
/** /**
* @var string|null * @return string
*/ */
public $description; public function toString()
{
return $this->name;
}
/** /**
* @var TypeDefinitionNode|null * @return string
*/ */
public $astNode; public function __toString()
{
/** try {
* @var array return $this->toString();
*/ } catch (\Exception $e) {
public $config; echo $e;
} catch (\Throwable $e) {
echo $e;
}
}
/** /**
* @return null|string * @return null|string
@ -297,44 +330,7 @@ abstract class Type implements \JsonSerializable
if ($tmp->getNamespaceName() !== __NAMESPACE__) { if ($tmp->getNamespaceName() !== __NAMESPACE__) {
return preg_replace('~Type$~', '', $name); return preg_replace('~Type$~', '', $name);
} }
return null; return null;
} }
/**
* @throws InvariantViolation
*/
public function assertValid()
{
Utils::assertValidName($this->name);
}
/**
* @return string
*/
public function toString()
{
return $this->name;
}
/**
* @return string
*/
public function jsonSerialize()
{
return $this->toString();
}
/**
* @return string
*/
public function __toString()
{
try {
return $this->toString();
} catch (\Exception $e) {
echo $e;
} catch (\Throwable $e) {
echo $e;
}
}
} }

View File

@ -1,38 +1,35 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function call_user_func;
use function is_array;
use function is_callable;
use function is_string;
use function sprintf;
/** /**
* Class UnionType * Class UnionType
* @package GraphQL\Type\Definition
*/ */
class UnionType extends Type implements AbstractType, OutputType, CompositeType, NamedType class UnionType extends Type implements AbstractType, OutputType, CompositeType, NamedType
{ {
/** /** @var UnionTypeDefinitionNode */
* @var UnionTypeDefinitionNode
*/
public $astNode; public $astNode;
/** /** @var ObjectType[] */
* @var ObjectType[]
*/
private $types; private $types;
/** /** @var ObjectType[] */
* @var ObjectType[]
*/
private $possibleTypeNames; private $possibleTypeNames;
/**
* UnionType constructor.
* @param $config
*/
public function __construct($config) public function __construct($config)
{ {
if (!isset($config['name'])) { if (! isset($config['name'])) {
$config['name'] = $this->tryInferName(); $config['name'] = $this->tryInferName();
} }
@ -44,70 +41,74 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType,
* Object type. * Object type.
*/ */
$this->name = $config['name']; $this->name = $config['name'];
$this->description = isset($config['description']) ? $config['description'] : null; $this->description = $config['description'] ?? null;
$this->astNode = isset($config['astNode']) ? $config['astNode'] : null; $this->astNode = $config['astNode'] ?? null;
$this->config = $config; $this->config = $config;
} }
/**
* @return mixed
*/
public function isPossibleType(Type $type)
{
if (! $type instanceof ObjectType) {
return false;
}
if ($this->possibleTypeNames === null) {
$this->possibleTypeNames = [];
foreach ($this->getTypes() as $possibleType) {
$this->possibleTypeNames[$possibleType->name] = true;
}
}
return isset($this->possibleTypeNames[$type->name]);
}
/** /**
* @return ObjectType[] * @return ObjectType[]
*/ */
public function getTypes() public function getTypes()
{ {
if (null === $this->types) { if ($this->types === null) {
if (!isset($this->config['types'])) { if (! isset($this->config['types'])) {
$types = null; $types = null;
} else if (is_callable($this->config['types'])) { } elseif (is_callable($this->config['types'])) {
$types = call_user_func($this->config['types']); $types = call_user_func($this->config['types']);
} else { } else {
$types = $this->config['types']; $types = $this->config['types'];
} }
if (!is_array($types)) { if (! is_array($types)) {
throw new InvariantViolation( throw new InvariantViolation(
"Must provide Array of types or a callable which returns " . sprintf(
"such an array for Union {$this->name}" 'Must provide Array of types or a callable which returns such an array for Union %s',
$this->name
)
); );
} }
$this->types = $types; $this->types = $types;
} }
return $this->types; return $this->types;
} }
/**
* @param Type $type
* @return mixed
*/
public function isPossibleType(Type $type)
{
if (!$type instanceof ObjectType) {
return false;
}
if (null === $this->possibleTypeNames) {
$this->possibleTypeNames = [];
foreach ($this->getTypes() as $possibleType) {
$this->possibleTypeNames[$possibleType->name] = true;
}
}
return isset($this->possibleTypeNames[$type->name]);
}
/** /**
* Resolves concrete ObjectType for given object value * Resolves concrete ObjectType for given object value
* *
* @param $objectValue * @param object $objectValue
* @param $context * @param mixed $context
* @param ResolveInfo $info
* @return callable|null * @return callable|null
*/ */
public function resolveType($objectValue, $context, ResolveInfo $info) public function resolveType($objectValue, $context, ResolveInfo $info)
{ {
if (isset($this->config['resolveType'])) { if (isset($this->config['resolveType'])) {
$fn = $this->config['resolveType']; $fn = $this->config['resolveType'];
return $fn($objectValue, $context, $info); return $fn($objectValue, $context, $info);
} }
return null; return null;
} }
@ -118,11 +119,17 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType,
{ {
parent::assertValid(); parent::assertValid();
if (isset($this->config['resolveType'])) { if (! isset($this->config['resolveType'])) {
return;
}
Utils::invariant( Utils::invariant(
is_callable($this->config['resolveType']), is_callable($this->config['resolveType']),
"{$this->name} must provide \"resolveType\" as a function, but got: " . Utils::printSafe($this->config['resolveType']) sprintf(
'%s must provide "resolveType" as a function, but got: %s',
$this->name,
Utils::printSafe($this->config['resolveType'])
)
); );
} }
}
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
/* /*
@ -10,6 +13,7 @@ GraphQLUnionType |
GraphQLEnumType | GraphQLEnumType |
GraphQLInputObjectType; GraphQLInputObjectType;
*/ */
interface UnmodifiedType interface UnmodifiedType
{ {
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
interface WrappingType interface WrappingType

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type; namespace GraphQL\Type;
use GraphQL\Type\Definition\AbstractType; use GraphQL\Type\Definition\AbstractType;
@ -12,24 +15,17 @@ use GraphQL\Utils\Utils;
/** /**
* EXPERIMENTAL! * EXPERIMENTAL!
* This class can be removed or changed in future versions without a prior notice. * This class can be removed or changed in future versions without a prior notice.
*
* Class EagerResolution
* @package GraphQL\Type
*/ */
class EagerResolution implements Resolution class EagerResolution implements Resolution
{ {
/** /** @var Type[] */
* @var Type[]
*/
private $typeMap = []; private $typeMap = [];
/** /** @var array<string, ObjectType[]> */
* @var array<string, ObjectType[]>
*/
private $implementations = []; private $implementations = [];
/** /**
* EagerResolution constructor. *
* @param Type[] $initialTypes * @param Type[] $initialTypes
*/ */
public function __construct(array $initialTypes) public function __construct(array $initialTypes)
@ -42,20 +38,22 @@ class EagerResolution implements Resolution
// Keep track of all possible types for abstract types // Keep track of all possible types for abstract types
foreach ($this->typeMap as $typeName => $type) { foreach ($this->typeMap as $typeName => $type) {
if ($type instanceof ObjectType) { if (! ($type instanceof ObjectType)) {
continue;
}
foreach ($type->getInterfaces() as $iface) { foreach ($type->getInterfaces() as $iface) {
$this->implementations[$iface->name][] = $type; $this->implementations[$iface->name][] = $type;
} }
} }
} }
}
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function resolveType($name) public function resolveType($name)
{ {
return isset($this->typeMap[$name]) ? $this->typeMap[$name] : null; return $this->typeMap[$name] ?? null;
} }
/** /**
@ -63,7 +61,7 @@ class EagerResolution implements Resolution
*/ */
public function resolvePossibleTypes(AbstractType $abstractType) public function resolvePossibleTypes(AbstractType $abstractType)
{ {
if (!isset($this->typeMap[$abstractType->name])) { if (! isset($this->typeMap[$abstractType->name])) {
return []; return [];
} }
@ -73,21 +71,14 @@ class EagerResolution implements Resolution
/** @var InterfaceType $abstractType */ /** @var InterfaceType $abstractType */
Utils::invariant($abstractType instanceof InterfaceType); Utils::invariant($abstractType instanceof InterfaceType);
return isset($this->implementations[$abstractType->name]) ? $this->implementations[$abstractType->name] : [];
}
/** return $this->implementations[$abstractType->name] ?? [];
* @return Type[]
*/
public function getTypeMap()
{
return $this->typeMap;
} }
/** /**
* Returns serializable schema representation suitable for GraphQL\Type\LazyResolution * Returns serializable schema representation suitable for GraphQL\Type\LazyResolution
* *
* @return array * @return mixed[]
*/ */
public function getDescriptor() public function getDescriptor()
{ {
@ -98,17 +89,26 @@ class EagerResolution implements Resolution
foreach ($type->getTypes() as $innerType) { foreach ($type->getTypes() as $innerType) {
$possibleTypesMap[$type->name][$innerType->name] = 1; $possibleTypesMap[$type->name][$innerType->name] = 1;
} }
} else if ($type instanceof InterfaceType) { } elseif ($type instanceof InterfaceType) {
foreach ($this->implementations[$type->name] as $obj) { foreach ($this->implementations[$type->name] as $obj) {
$possibleTypesMap[$type->name][$obj->name] = 1; $possibleTypesMap[$type->name][$obj->name] = 1;
} }
} }
$typeMap[$type->name] = 1; $typeMap[$type->name] = 1;
} }
return [ return [
'version' => '1.0', 'version' => '1.0',
'typeMap' => $typeMap, 'typeMap' => $typeMap,
'possibleTypeMap' => $possibleTypesMap 'possibleTypeMap' => $possibleTypesMap,
]; ];
} }
/**
* @return Type[]
*/
public function getTypeMap()
{
return $this->typeMap;
}
} }

View File

@ -1,9 +1,12 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type; namespace GraphQL\Type;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Language\Printer; use GraphQL\Language\Printer;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\FieldArgument; use GraphQL\Type\Definition\FieldArgument;
use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\FieldDefinition;
@ -18,22 +21,20 @@ use GraphQL\Type\Definition\ScalarType;
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\Utils\Utils;
use GraphQL\Utils\AST; use GraphQL\Utils\AST;
use GraphQL\Utils\Utils;
class TypeKind { use function array_filter;
const SCALAR = 0; use function array_key_exists;
const OBJECT = 1; use function array_values;
const INTERFACE_KIND = 2; use function in_array;
const UNION = 3; use function is_bool;
const ENUM = 4; use function method_exists;
const INPUT_OBJECT = 5; use function trigger_error;
const LIST_KIND = 6; use const E_USER_DEPRECATED;
const NON_NULL = 7;
}
class Introspection class Introspection
{ {
/** @var Type[] */
private static $map = []; private static $map = [];
/** /**
@ -42,20 +43,20 @@ class Introspection
* Whether to include descriptions in the introspection result. * Whether to include descriptions in the introspection result.
* Default: true * Default: true
* *
* @param array $options * @param bool[]|bool $options
* @return string * @return string
*/ */
public static function getIntrospectionQuery($options = []) public static function getIntrospectionQuery($options = [])
{ {
if (is_bool($options)) { if (is_bool($options)) {
trigger_error( trigger_error(
'Calling Introspection::getIntrospectionQuery(boolean) is deprecated. '. 'Calling Introspection::getIntrospectionQuery(boolean) is deprecated. ' .
'Please use Introspection::getIntrospectionQuery(["descriptions" => boolean]).', 'Please use Introspection::getIntrospectionQuery(["descriptions" => boolean]).',
E_USER_DEPRECATED E_USER_DEPRECATED
); );
$descriptions = $options; $descriptions = $options;
} else { } else {
$descriptions = !array_key_exists('descriptions', $options) || $options['descriptions'] === true; $descriptions = ! array_key_exists('descriptions', $options) || $options['descriptions'] === true;
} }
$descriptionField = $descriptions ? 'description' : ''; $descriptionField = $descriptions ? 'description' : '';
@ -154,6 +155,15 @@ class Introspection
EOD; EOD;
} }
/**
* @param Type $type
* @return bool
*/
public static function isIntrospectionType($type)
{
return array_key_exists($type->name, self::getTypes());
}
public static function getTypes() public static function getTypes()
{ {
return [ return [
@ -168,18 +178,9 @@ EOD;
]; ];
} }
/**
* @param Type $type
* @return bool
*/
public static function isIntrospectionType($type)
{
return in_array($type->name, array_keys(self::getTypes()));
}
public static function _schema() public static function _schema()
{ {
if (!isset(self::$map['__Schema'])) { if (! isset(self::$map['__Schema'])) {
self::$map['__Schema'] = new ObjectType([ self::$map['__Schema'] = new ObjectType([
'name' => '__Schema', 'name' => '__Schema',
'isIntrospection' => true, 'isIntrospection' => true,
@ -194,14 +195,14 @@ EOD;
'type' => new NonNull(new ListOfType(new NonNull(self::_type()))), 'type' => new NonNull(new ListOfType(new NonNull(self::_type()))),
'resolve' => function (Schema $schema) { 'resolve' => function (Schema $schema) {
return array_values($schema->getTypeMap()); return array_values($schema->getTypeMap());
} },
], ],
'queryType' => [ 'queryType' => [
'description' => 'The type that query operations will be rooted at.', 'description' => 'The type that query operations will be rooted at.',
'type' => new NonNull(self::_type()), 'type' => new NonNull(self::_type()),
'resolve' => function (Schema $schema) { 'resolve' => function (Schema $schema) {
return $schema->getQueryType(); return $schema->getQueryType();
} },
], ],
'mutationType' => [ 'mutationType' => [
'description' => 'description' =>
@ -210,7 +211,7 @@ EOD;
'type' => self::_type(), 'type' => self::_type(),
'resolve' => function (Schema $schema) { 'resolve' => function (Schema $schema) {
return $schema->getMutationType(); return $schema->getMutationType();
} },
], ],
'subscriptionType' => [ 'subscriptionType' => [
'description' => 'If this server support subscription, the type that subscription operations will be rooted at.', 'description' => 'If this server support subscription, the type that subscription operations will be rooted at.',
@ -222,182 +223,34 @@ EOD;
'directives' => [ 'directives' => [
'description' => 'A list of all directives supported by this server.', 'description' => 'A list of all directives supported by this server.',
'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_directive()))), 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_directive()))),
'resolve' => function(Schema $schema) { 'resolve' => function (Schema $schema) {
return $schema->getDirectives(); return $schema->getDirectives();
} },
] ],
] ],
]); ]);
} }
return self::$map['__Schema']; return self::$map['__Schema'];
} }
public static function _directive()
{
if (!isset(self::$map['__Directive'])) {
self::$map['__Directive'] = new ObjectType([
'name' => '__Directive',
'isIntrospection' => true,
'description' => 'A Directive provides a way to describe alternate runtime execution and ' .
'type validation behavior in a GraphQL document.' .
"\n\nIn some cases, you need to provide options to alter GraphQL's " .
'execution behavior in ways field arguments will not suffice, such as ' .
'conditionally including or skipping a field. Directives provide this by ' .
'describing additional information to the executor.',
'fields' => [
'name' => ['type' => Type::nonNull(Type::string())],
'description' => ['type' => Type::string()],
'locations' => [
'type' => Type::nonNull(Type::listOf(Type::nonNull(
self::_directiveLocation()
)))
],
'args' => [
'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))),
'resolve' => function (Directive $directive) {
return $directive->args ?: [];
}
],
// NOTE: the following three fields are deprecated and are no longer part
// of the GraphQL specification.
'onOperation' => [
'deprecationReason' => 'Use `locations`.',
'type' => Type::nonNull(Type::boolean()),
'resolve' => function($d) {
return in_array(DirectiveLocation::QUERY, $d->locations) ||
in_array(DirectiveLocation::MUTATION, $d->locations) ||
in_array(DirectiveLocation::SUBSCRIPTION, $d->locations);
}
],
'onFragment' => [
'deprecationReason' => 'Use `locations`.',
'type' => Type::nonNull(Type::boolean()),
'resolve' => function($d) {
return in_array(DirectiveLocation::FRAGMENT_SPREAD, $d->locations) ||
in_array(DirectiveLocation::INLINE_FRAGMENT, $d->locations) ||
in_array(DirectiveLocation::FRAGMENT_DEFINITION, $d->locations);
}
],
'onField' => [
'deprecationReason' => 'Use `locations`.',
'type' => Type::nonNull(Type::boolean()),
'resolve' => function($d) {
return in_array(DirectiveLocation::FIELD, $d->locations);
}
]
]
]);
}
return self::$map['__Directive'];
}
public static function _directiveLocation()
{
if (!isset(self::$map['__DirectiveLocation'])) {
self::$map['__DirectiveLocation'] = new EnumType([
'name' => '__DirectiveLocation',
'isIntrospection' => true,
'description' =>
'A Directive can be adjacent to many parts of the GraphQL language, a ' .
'__DirectiveLocation describes one such possible adjacencies.',
'values' => [
'QUERY' => [
'value' => DirectiveLocation::QUERY,
'description' => 'Location adjacent to a query operation.'
],
'MUTATION' => [
'value' => DirectiveLocation::MUTATION,
'description' => 'Location adjacent to a mutation operation.'
],
'SUBSCRIPTION' => [
'value' => DirectiveLocation::SUBSCRIPTION,
'description' => 'Location adjacent to a subscription operation.'
],
'FIELD' => [
'value' => DirectiveLocation::FIELD,
'description' => 'Location adjacent to a field.'
],
'FRAGMENT_DEFINITION' => [
'value' => DirectiveLocation::FRAGMENT_DEFINITION,
'description' => 'Location adjacent to a fragment definition.'
],
'FRAGMENT_SPREAD' => [
'value' => DirectiveLocation::FRAGMENT_SPREAD,
'description' => 'Location adjacent to a fragment spread.'
],
'INLINE_FRAGMENT' => [
'value' => DirectiveLocation::INLINE_FRAGMENT,
'description' => 'Location adjacent to an inline fragment.'
],
'SCHEMA' => [
'value' => DirectiveLocation::SCHEMA,
'description' => 'Location adjacent to a schema definition.'
],
'SCALAR' => [
'value' => DirectiveLocation::SCALAR,
'description' => 'Location adjacent to a scalar definition.'
],
'OBJECT' => [
'value' => DirectiveLocation::OBJECT,
'description' => 'Location adjacent to an object type definition.'
],
'FIELD_DEFINITION' => [
'value' => DirectiveLocation::FIELD_DEFINITION,
'description' => 'Location adjacent to a field definition.'
],
'ARGUMENT_DEFINITION' => [
'value' => DirectiveLocation::ARGUMENT_DEFINITION,
'description' => 'Location adjacent to an argument definition.'
],
'INTERFACE' => [
'value' => DirectiveLocation::IFACE,
'description' => 'Location adjacent to an interface definition.'
],
'UNION' => [
'value' => DirectiveLocation::UNION,
'description' => 'Location adjacent to a union definition.'
],
'ENUM' => [
'value' => DirectiveLocation::ENUM,
'description' => 'Location adjacent to an enum definition.'
],
'ENUM_VALUE' => [
'value' => DirectiveLocation::ENUM_VALUE,
'description' => 'Location adjacent to an enum value definition.'
],
'INPUT_OBJECT' => [
'value' => DirectiveLocation::INPUT_OBJECT,
'description' => 'Location adjacent to an input object type definition.'
],
'INPUT_FIELD_DEFINITION' => [
'value' => DirectiveLocation::INPUT_FIELD_DEFINITION,
'description' => 'Location adjacent to an input object field definition.'
]
]
]);
}
return self::$map['__DirectiveLocation'];
}
public static function _type() public static function _type()
{ {
if (!isset(self::$map['__Type'])) { if (! isset(self::$map['__Type'])) {
self::$map['__Type'] = new ObjectType([ self::$map['__Type'] = new ObjectType([
'name' => '__Type', 'name' => '__Type',
'isIntrospection' => true, 'isIntrospection' => true,
'description' => 'description' =>
'The fundamental unit of any GraphQL Schema is the type. There are ' . 'The fundamental unit of any GraphQL Schema is the type. There are ' .
'many kinds of types in GraphQL as represented by the `__TypeKind` enum.' . 'many kinds of types in GraphQL as represented by the `__TypeKind` enum.' .
"\n\n". "\n\n" .
'Depending on the kind of a type, certain fields describe ' . 'Depending on the kind of a type, certain fields describe ' .
'information about that type. Scalar types provide no information ' . 'information about that type. Scalar types provide no information ' .
'beyond a name and description, while Enum types provide their values. ' . 'beyond a name and description, while Enum types provide their values. ' .
'Object and Interface types provide the fields they describe. Abstract ' . 'Object and Interface types provide the fields they describe. Abstract ' .
'types, Union and Interface, provide the Object types possible ' . 'types, Union and Interface, provide the Object types possible ' .
'at runtime. List and NonNull types compose other types.', 'at runtime. List and NonNull types compose other types.',
'fields' => function() { 'fields' => function () {
return [ return [
'kind' => [ 'kind' => [
'type' => Type::nonNull(self::_typeKind()), 'type' => Type::nonNull(self::_typeKind()),
@ -420,30 +273,35 @@ EOD;
case $type instanceof UnionType: case $type instanceof UnionType:
return TypeKind::UNION; return TypeKind::UNION;
default: default:
throw new \Exception("Unknown kind of type: " . Utils::printSafe($type)); throw new \Exception('Unknown kind of type: ' . Utils::printSafe($type));
}
} }
},
], ],
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'description' => ['type' => Type::string()], 'description' => ['type' => Type::string()],
'fields' => [ 'fields' => [
'type' => Type::listOf(Type::nonNull(self::_field())), 'type' => Type::listOf(Type::nonNull(self::_field())),
'args' => [ 'args' => [
'includeDeprecated' => ['type' => Type::boolean(), 'defaultValue' => false] 'includeDeprecated' => ['type' => Type::boolean(), 'defaultValue' => false],
], ],
'resolve' => function (Type $type, $args) { 'resolve' => function (Type $type, $args) {
if ($type instanceof ObjectType || $type instanceof InterfaceType) { if ($type instanceof ObjectType || $type instanceof InterfaceType) {
$fields = $type->getFields(); $fields = $type->getFields();
if (empty($args['includeDeprecated'])) { if (empty($args['includeDeprecated'])) {
$fields = array_filter($fields, function (FieldDefinition $field) { $fields = array_filter(
return !$field->deprecationReason; $fields,
}); function (FieldDefinition $field) {
return ! $field->deprecationReason;
} }
);
}
return array_values($fields); return array_values($fields);
} }
return null; return null;
} },
], ],
'interfaces' => [ 'interfaces' => [
'type' => Type::listOf(Type::nonNull(self::_type())), 'type' => Type::listOf(Type::nonNull(self::_type())),
@ -451,8 +309,9 @@ EOD;
if ($type instanceof ObjectType) { if ($type instanceof ObjectType) {
return $type->getInterfaces(); return $type->getInterfaces();
} }
return null; return null;
} },
], ],
'possibleTypes' => [ 'possibleTypes' => [
'type' => Type::listOf(Type::nonNull(self::_type())), 'type' => Type::listOf(Type::nonNull(self::_type())),
@ -460,28 +319,33 @@ EOD;
if ($type instanceof InterfaceType || $type instanceof UnionType) { if ($type instanceof InterfaceType || $type instanceof UnionType) {
return $info->schema->getPossibleTypes($type); return $info->schema->getPossibleTypes($type);
} }
return null; return null;
} },
], ],
'enumValues' => [ 'enumValues' => [
'type' => Type::listOf(Type::nonNull(self::_enumValue())), 'type' => Type::listOf(Type::nonNull(self::_enumValue())),
'args' => [ 'args' => [
'includeDeprecated' => ['type' => Type::boolean(), 'defaultValue' => false] 'includeDeprecated' => ['type' => Type::boolean(), 'defaultValue' => false],
], ],
'resolve' => function ($type, $args) { 'resolve' => function ($type, $args) {
if ($type instanceof EnumType) { if ($type instanceof EnumType) {
$values = array_values($type->getValues()); $values = array_values($type->getValues());
if (empty($args['includeDeprecated'])) { if (empty($args['includeDeprecated'])) {
$values = array_filter($values, function ($value) { $values = array_filter(
return !$value->deprecationReason; $values,
}); function ($value) {
return ! $value->deprecationReason;
}
);
} }
return $values; return $values;
} }
return null; return null;
} },
], ],
'inputFields' => [ 'inputFields' => [
'type' => Type::listOf(Type::nonNull(self::_inputValue())), 'type' => Type::listOf(Type::nonNull(self::_inputValue())),
@ -489,8 +353,9 @@ EOD;
if ($type instanceof InputObjectType) { if ($type instanceof InputObjectType) {
return array_values($type->getFields()); return array_values($type->getFields());
} }
return null; return null;
} },
], ],
'ofType' => [ 'ofType' => [
'type' => self::_type(), 'type' => self::_type(),
@ -498,27 +363,75 @@ EOD;
if ($type instanceof WrappingType) { if ($type instanceof WrappingType) {
return $type->getWrappedType(); return $type->getWrappedType();
} }
return null; return null;
} },
] ],
]; ];
} },
]); ]);
} }
return self::$map['__Type']; return self::$map['__Type'];
} }
public static function _typeKind()
{
if (! isset(self::$map['__TypeKind'])) {
self::$map['__TypeKind'] = new EnumType([
'name' => '__TypeKind',
'isIntrospection' => true,
'description' => 'An enum describing what kind of type a given `__Type` is.',
'values' => [
'SCALAR' => [
'value' => TypeKind::SCALAR,
'description' => 'Indicates this type is a scalar.',
],
'OBJECT' => [
'value' => TypeKind::OBJECT,
'description' => 'Indicates this type is an object. `fields` and `interfaces` are valid fields.',
],
'INTERFACE' => [
'value' => TypeKind::INTERFACE_KIND,
'description' => 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.',
],
'UNION' => [
'value' => TypeKind::UNION,
'description' => 'Indicates this type is a union. `possibleTypes` is a valid field.',
],
'ENUM' => [
'value' => TypeKind::ENUM,
'description' => 'Indicates this type is an enum. `enumValues` is a valid field.',
],
'INPUT_OBJECT' => [
'value' => TypeKind::INPUT_OBJECT,
'description' => 'Indicates this type is an input object. `inputFields` is a valid field.',
],
'LIST' => [
'value' => TypeKind::LIST_KIND,
'description' => 'Indicates this type is a list. `ofType` is a valid field.',
],
'NON_NULL' => [
'value' => TypeKind::NON_NULL,
'description' => 'Indicates this type is a non-null. `ofType` is a valid field.',
],
],
]);
}
return self::$map['__TypeKind'];
}
public static function _field() public static function _field()
{ {
if (!isset(self::$map['__Field'])) { if (! isset(self::$map['__Field'])) {
self::$map['__Field'] = new ObjectType([ self::$map['__Field'] = new ObjectType([
'name' => '__Field', 'name' => '__Field',
'isIntrospection' => true, 'isIntrospection' => true,
'description' => 'description' =>
'Object and Interface types are described by a list of Fields, each of ' . 'Object and Interface types are described by a list of Fields, each of ' .
'which has a name, potentially a list of arguments, and a return type.', 'which has a name, potentially a list of arguments, and a return type.',
'fields' => function() { 'fields' => function () {
return [ return [
'name' => ['type' => Type::nonNull(Type::string())], 'name' => ['type' => Type::nonNull(Type::string())],
'description' => ['type' => Type::string()], 'description' => ['type' => Type::string()],
@ -526,33 +439,34 @@ EOD;
'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))), 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))),
'resolve' => function (FieldDefinition $field) { 'resolve' => function (FieldDefinition $field) {
return empty($field->args) ? [] : $field->args; return empty($field->args) ? [] : $field->args;
} },
], ],
'type' => [ 'type' => [
'type' => Type::nonNull(self::_type()), 'type' => Type::nonNull(self::_type()),
'resolve' => function (FieldDefinition $field) { 'resolve' => function (FieldDefinition $field) {
return $field->getType(); return $field->getType();
} },
], ],
'isDeprecated' => [ 'isDeprecated' => [
'type' => Type::nonNull(Type::boolean()), 'type' => Type::nonNull(Type::boolean()),
'resolve' => function (FieldDefinition $field) { 'resolve' => function (FieldDefinition $field) {
return !!$field->deprecationReason; return (bool) $field->deprecationReason;
} },
], ],
'deprecationReason' => [ 'deprecationReason' => [
'type' => Type::string() 'type' => Type::string(),
] ],
]; ];
} },
]); ]);
} }
return self::$map['__Field']; return self::$map['__Field'];
} }
public static function _inputValue() public static function _inputValue()
{ {
if (!isset(self::$map['__InputValue'])) { if (! isset(self::$map['__InputValue'])) {
self::$map['__InputValue'] = new ObjectType([ self::$map['__InputValue'] = new ObjectType([
'name' => '__InputValue', 'name' => '__InputValue',
'isIntrospection' => true, 'isIntrospection' => true,
@ -560,7 +474,7 @@ EOD;
'Arguments provided to Fields or Directives and the input fields of an ' . 'Arguments provided to Fields or Directives and the input fields of an ' .
'InputObject are represented as Input Values which describe their type ' . 'InputObject are represented as Input Values which describe their type ' .
'and optionally a default value.', 'and optionally a default value.',
'fields' => function() { 'fields' => function () {
return [ return [
'name' => ['type' => Type::nonNull(Type::string())], 'name' => ['type' => Type::nonNull(Type::string())],
'description' => ['type' => Type::string()], 'description' => ['type' => Type::string()],
@ -568,7 +482,7 @@ EOD;
'type' => Type::nonNull(self::_type()), 'type' => Type::nonNull(self::_type()),
'resolve' => function ($value) { 'resolve' => function ($value) {
return method_exists($value, 'getType') ? $value->getType() : $value->type; return method_exists($value, 'getType') ? $value->getType() : $value->type;
} },
], ],
'defaultValue' => [ 'defaultValue' => [
'type' => Type::string(), 'type' => Type::string(),
@ -576,21 +490,25 @@ EOD;
'A GraphQL-formatted string representing the default value for this input value.', 'A GraphQL-formatted string representing the default value for this input value.',
'resolve' => function ($inputValue) { 'resolve' => function ($inputValue) {
/** @var FieldArgument|InputObjectField $inputValue */ /** @var FieldArgument|InputObjectField $inputValue */
return !$inputValue->defaultValueExists() return ! $inputValue->defaultValueExists()
? null ? null
: Printer::doPrint(AST::astFromValue($inputValue->defaultValue, $inputValue->getType())); : Printer::doPrint(AST::astFromValue(
} $inputValue->defaultValue,
] $inputValue->getType()
));
},
],
]; ];
} },
]); ]);
} }
return self::$map['__InputValue']; return self::$map['__InputValue'];
} }
public static function _enumValue() public static function _enumValue()
{ {
if (!isset(self::$map['__EnumValue'])) { if (! isset(self::$map['__EnumValue'])) {
self::$map['__EnumValue'] = new ObjectType([ self::$map['__EnumValue'] = new ObjectType([
'name' => '__EnumValue', 'name' => '__EnumValue',
'isIntrospection' => true, 'isIntrospection' => true,
@ -604,67 +522,173 @@ EOD;
'isDeprecated' => [ 'isDeprecated' => [
'type' => Type::nonNull(Type::boolean()), 'type' => Type::nonNull(Type::boolean()),
'resolve' => function ($enumValue) { 'resolve' => function ($enumValue) {
return !!$enumValue->deprecationReason; return (bool) $enumValue->deprecationReason;
} },
], ],
'deprecationReason' => [ 'deprecationReason' => [
'type' => Type::string() 'type' => Type::string(),
] ],
] ],
]); ]);
} }
return self::$map['__EnumValue']; return self::$map['__EnumValue'];
} }
public static function _typeKind() public static function _directive()
{ {
if (!isset(self::$map['__TypeKind'])) { if (! isset(self::$map['__Directive'])) {
self::$map['__TypeKind'] = new EnumType([ self::$map['__Directive'] = new ObjectType([
'name' => '__TypeKind', 'name' => '__Directive',
'isIntrospection' => true, 'isIntrospection' => true,
'description' => 'An enum describing what kind of type a given `__Type` is.', 'description' => 'A Directive provides a way to describe alternate runtime execution and ' .
'values' => [ 'type validation behavior in a GraphQL document.' .
'SCALAR' => [ "\n\nIn some cases, you need to provide options to alter GraphQL's " .
'value' => TypeKind::SCALAR, 'execution behavior in ways field arguments will not suffice, such as ' .
'description' => 'Indicates this type is a scalar.' 'conditionally including or skipping a field. Directives provide this by ' .
'describing additional information to the executor.',
'fields' => [
'name' => ['type' => Type::nonNull(Type::string())],
'description' => ['type' => Type::string()],
'locations' => [
'type' => Type::nonNull(Type::listOf(Type::nonNull(
self::_directiveLocation()
))),
], ],
'OBJECT' => [ 'args' => [
'value' => TypeKind::OBJECT, 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))),
'description' => 'Indicates this type is an object. `fields` and `interfaces` are valid fields.' 'resolve' => function (Directive $directive) {
return $directive->args ?: [];
},
], ],
'INTERFACE' => [
'value' => TypeKind::INTERFACE_KIND, // NOTE: the following three fields are deprecated and are no longer part
'description' => 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.' // of the GraphQL specification.
'onOperation' => [
'deprecationReason' => 'Use `locations`.',
'type' => Type::nonNull(Type::boolean()),
'resolve' => function ($d) {
return in_array(DirectiveLocation::QUERY, $d->locations) ||
in_array(DirectiveLocation::MUTATION, $d->locations) ||
in_array(DirectiveLocation::SUBSCRIPTION, $d->locations);
},
], ],
'UNION' => [ 'onFragment' => [
'value' => TypeKind::UNION, 'deprecationReason' => 'Use `locations`.',
'description' => 'Indicates this type is a union. `possibleTypes` is a valid field.' 'type' => Type::nonNull(Type::boolean()),
'resolve' => function ($d) {
return in_array(DirectiveLocation::FRAGMENT_SPREAD, $d->locations) ||
in_array(DirectiveLocation::INLINE_FRAGMENT, $d->locations) ||
in_array(DirectiveLocation::FRAGMENT_DEFINITION, $d->locations);
},
], ],
'ENUM' => [ 'onField' => [
'value' => TypeKind::ENUM, 'deprecationReason' => 'Use `locations`.',
'description' => 'Indicates this type is an enum. `enumValues` is a valid field.' 'type' => Type::nonNull(Type::boolean()),
'resolve' => function ($d) {
return in_array(DirectiveLocation::FIELD, $d->locations);
},
], ],
'INPUT_OBJECT' => [
'value' => TypeKind::INPUT_OBJECT,
'description' => 'Indicates this type is an input object. `inputFields` is a valid field.'
], ],
'LIST' => [
'value' => TypeKind::LIST_KIND,
'description' => 'Indicates this type is a list. `ofType` is a valid field.'
],
'NON_NULL' => [
'value' => TypeKind::NON_NULL,
'description' => 'Indicates this type is a non-null. `ofType` is a valid field.'
]
]
]); ]);
} }
return self::$map['__TypeKind'];
return self::$map['__Directive'];
}
public static function _directiveLocation()
{
if (! isset(self::$map['__DirectiveLocation'])) {
self::$map['__DirectiveLocation'] = new EnumType([
'name' => '__DirectiveLocation',
'isIntrospection' => true,
'description' =>
'A Directive can be adjacent to many parts of the GraphQL language, a ' .
'__DirectiveLocation describes one such possible adjacencies.',
'values' => [
'QUERY' => [
'value' => DirectiveLocation::QUERY,
'description' => 'Location adjacent to a query operation.',
],
'MUTATION' => [
'value' => DirectiveLocation::MUTATION,
'description' => 'Location adjacent to a mutation operation.',
],
'SUBSCRIPTION' => [
'value' => DirectiveLocation::SUBSCRIPTION,
'description' => 'Location adjacent to a subscription operation.',
],
'FIELD' => [
'value' => DirectiveLocation::FIELD,
'description' => 'Location adjacent to a field.',
],
'FRAGMENT_DEFINITION' => [
'value' => DirectiveLocation::FRAGMENT_DEFINITION,
'description' => 'Location adjacent to a fragment definition.',
],
'FRAGMENT_SPREAD' => [
'value' => DirectiveLocation::FRAGMENT_SPREAD,
'description' => 'Location adjacent to a fragment spread.',
],
'INLINE_FRAGMENT' => [
'value' => DirectiveLocation::INLINE_FRAGMENT,
'description' => 'Location adjacent to an inline fragment.',
],
'SCHEMA' => [
'value' => DirectiveLocation::SCHEMA,
'description' => 'Location adjacent to a schema definition.',
],
'SCALAR' => [
'value' => DirectiveLocation::SCALAR,
'description' => 'Location adjacent to a scalar definition.',
],
'OBJECT' => [
'value' => DirectiveLocation::OBJECT,
'description' => 'Location adjacent to an object type definition.',
],
'FIELD_DEFINITION' => [
'value' => DirectiveLocation::FIELD_DEFINITION,
'description' => 'Location adjacent to a field definition.',
],
'ARGUMENT_DEFINITION' => [
'value' => DirectiveLocation::ARGUMENT_DEFINITION,
'description' => 'Location adjacent to an argument definition.',
],
'INTERFACE' => [
'value' => DirectiveLocation::IFACE,
'description' => 'Location adjacent to an interface definition.',
],
'UNION' => [
'value' => DirectiveLocation::UNION,
'description' => 'Location adjacent to a union definition.',
],
'ENUM' => [
'value' => DirectiveLocation::ENUM,
'description' => 'Location adjacent to an enum definition.',
],
'ENUM_VALUE' => [
'value' => DirectiveLocation::ENUM_VALUE,
'description' => 'Location adjacent to an enum value definition.',
],
'INPUT_OBJECT' => [
'value' => DirectiveLocation::INPUT_OBJECT,
'description' => 'Location adjacent to an input object type definition.',
],
'INPUT_FIELD_DEFINITION' => [
'value' => DirectiveLocation::INPUT_FIELD_DEFINITION,
'description' => 'Location adjacent to an input object field definition.',
],
],
]);
}
return self::$map['__DirectiveLocation'];
} }
public static function schemaMetaFieldDef() public static function schemaMetaFieldDef()
{ {
if (!isset(self::$map['__schema'])) { if (! isset(self::$map['__schema'])) {
self::$map['__schema'] = FieldDefinition::create([ self::$map['__schema'] = FieldDefinition::create([
'name' => '__schema', 'name' => '__schema',
'type' => Type::nonNull(self::_schema()), 'type' => Type::nonNull(self::_schema()),
@ -677,33 +701,35 @@ EOD;
ResolveInfo $info ResolveInfo $info
) { ) {
return $info->schema; return $info->schema;
} },
]); ]);
} }
return self::$map['__schema']; return self::$map['__schema'];
} }
public static function typeMetaFieldDef() public static function typeMetaFieldDef()
{ {
if (!isset(self::$map['__type'])) { if (! isset(self::$map['__type'])) {
self::$map['__type'] = FieldDefinition::create([ self::$map['__type'] = FieldDefinition::create([
'name' => '__type', 'name' => '__type',
'type' => self::_type(), 'type' => self::_type(),
'description' => 'Request the type information of a single type.', 'description' => 'Request the type information of a single type.',
'args' => [ 'args' => [
['name' => 'name', 'type' => Type::nonNull(Type::string())] ['name' => 'name', 'type' => Type::nonNull(Type::string())],
], ],
'resolve' => function ($source, $args, $context, ResolveInfo $info) { 'resolve' => function ($source, $args, $context, ResolveInfo $info) {
return $info->schema->getType($args['name']); return $info->schema->getType($args['name']);
} },
]); ]);
} }
return self::$map['__type']; return self::$map['__type'];
} }
public static function typeNameMetaFieldDef() public static function typeNameMetaFieldDef()
{ {
if (!isset(self::$map['__typename'])) { if (! isset(self::$map['__typename'])) {
self::$map['__typename'] = FieldDefinition::create([ self::$map['__typename'] = FieldDefinition::create([
'name' => '__typename', 'name' => '__typename',
'type' => Type::nonNull(Type::string()), 'type' => Type::nonNull(Type::string()),
@ -716,9 +742,10 @@ EOD;
ResolveInfo $info ResolveInfo $info
) { ) {
return $info->parentType->name; return $info->parentType->name;
} },
]); ]);
} }
return self::$map['__typename']; return self::$map['__typename'];
} }
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type; namespace GraphQL\Type;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
@ -6,29 +9,22 @@ use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function call_user_func;
use function sprintf;
/** /**
* EXPERIMENTAL! * EXPERIMENTAL!
* This class can be removed or changed in future versions without a prior notice. * This class can be removed or changed in future versions without a prior notice.
*
* Class LazyResolution
* @package GraphQL\Type
*/ */
class LazyResolution implements Resolution class LazyResolution implements Resolution
{ {
/** /** @var int[] */
* @var array
*/
private $typeMap; private $typeMap;
/** /** @var int[][] */
* @var array
*/
private $possibleTypeMap; private $possibleTypeMap;
/** /** @var callable */
* @var callable
*/
private $typeLoader; private $typeLoader;
/** /**
@ -41,14 +37,13 @@ class LazyResolution implements Resolution
/** /**
* Map of $interfaceTypeName => $objectType[] * Map of $interfaceTypeName => $objectType[]
* *
* @var array * @var Type[][]
*/ */
private $loadedPossibleTypes; private $loadedPossibleTypes;
/** /**
* LazyResolution constructor. *
* @param array $descriptor * @param mixed[] $descriptor
* @param callable $typeLoader
*/ */
public function __construct(array $descriptor, callable $typeLoader) public function __construct(array $descriptor, callable $typeLoader)
{ {
@ -66,50 +61,56 @@ class LazyResolution implements Resolution
$this->loadedPossibleTypes = []; $this->loadedPossibleTypes = [];
} }
/**
* @inheritdoc
*/
public function resolveType($name)
{
if (!isset($this->typeMap[$name])) {
return null;
}
if (!isset($this->loadedTypes[$name])) {
$type = call_user_func($this->typeLoader, $name);
if (!$type instanceof Type && null !== $type) {
throw new InvariantViolation(
"Lazy Type Resolution Error: Expecting GraphQL Type instance, but got " .
Utils::getVariableType($type)
);
}
$this->loadedTypes[$name] = $type;
}
return $this->loadedTypes[$name];
}
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function resolvePossibleTypes(AbstractType $type) public function resolvePossibleTypes(AbstractType $type)
{ {
if (!isset($this->possibleTypeMap[$type->name])) { if (! isset($this->possibleTypeMap[$type->name])) {
return []; return [];
} }
if (!isset($this->loadedPossibleTypes[$type->name])) { if (! isset($this->loadedPossibleTypes[$type->name])) {
$tmp = []; $tmp = [];
foreach ($this->possibleTypeMap[$type->name] as $typeName => $true) { foreach ($this->possibleTypeMap[$type->name] as $typeName => $true) {
$obj = $this->resolveType($typeName); $obj = $this->resolveType($typeName);
if (!$obj instanceof ObjectType) { if (! $obj instanceof ObjectType) {
throw new InvariantViolation( throw new InvariantViolation(
"Lazy Type Resolution Error: Implementation {$typeName} of interface {$type->name} " . sprintf(
"is expected to be instance of ObjectType, but got " . Utils::getVariableType($obj) 'Lazy Type Resolution Error: Implementation %s of interface %s is expected to be instance of ObjectType, but got %s',
$typeName,
$type->name,
Utils::getVariableType($obj)
)
); );
} }
$tmp[] = $obj; $tmp[] = $obj;
} }
$this->loadedPossibleTypes[$type->name] = $tmp; $this->loadedPossibleTypes[$type->name] = $tmp;
} }
return $this->loadedPossibleTypes[$type->name]; return $this->loadedPossibleTypes[$type->name];
} }
/**
* @inheritdoc
*/
public function resolveType($name)
{
if (! isset($this->typeMap[$name])) {
return null;
}
if (! isset($this->loadedTypes[$name])) {
$type = call_user_func($this->typeLoader, $name);
if (! $type instanceof Type && $type !== null) {
throw new InvariantViolation(
'Lazy Type Resolution Error: Expecting GraphQL Type instance, but got ' .
Utils::getVariableType($type)
);
}
$this->loadedTypes[$name] = $type;
}
return $this->loadedTypes[$name];
}
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type; namespace GraphQL\Type;
use GraphQL\Type\Definition\AbstractType; use GraphQL\Type\Definition\AbstractType;
@ -10,7 +13,6 @@ use GraphQL\Type\Definition\Type;
* This interface can be removed or changed in future versions without a prior notice. * This interface can be removed or changed in future versions without a prior notice.
* *
* Interface Resolution * Interface Resolution
* @package GraphQL\Type
*/ */
interface Resolution interface Resolution
{ {
@ -25,7 +27,6 @@ interface Resolution
/** /**
* Returns instances of possible ObjectTypes for given InterfaceType or UnionType * Returns instances of possible ObjectTypes for given InterfaceType or UnionType
* *
* @param AbstractType $type
* @return ObjectType[] * @return ObjectType[]
*/ */
public function resolvePossibleTypes(AbstractType $type); public function resolvePossibleTypes(AbstractType $type);

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type; namespace GraphQL\Type;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -13,6 +16,11 @@ use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Utils\TypeInfo; use GraphQL\Utils\TypeInfo;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function array_values;
use function implode;
use function is_array;
use function is_callable;
use function sprintf;
/** /**
* Schema Definition (see [related docs](type-system/schema.md)) * Schema Definition (see [related docs](type-system/schema.md))
@ -33,14 +41,10 @@ use GraphQL\Utils\Utils;
* ->setMutation($MyAppMutationRootType); * ->setMutation($MyAppMutationRootType);
* *
* $schema = new GraphQL\Type\Schema($config); * $schema = new GraphQL\Type\Schema($config);
*
* @package GraphQL
*/ */
class Schema class Schema
{ {
/** /** @var SchemaConfig */
* @var SchemaConfig
*/
private $config; private $config;
/** /**
@ -50,9 +54,7 @@ class Schema
*/ */
private $resolvedTypes = []; private $resolvedTypes = [];
/** /** @var Type[][]|null */
* @var array
*/
private $possibleTypeMap; private $possibleTypeMap;
/** /**
@ -62,16 +64,12 @@ class Schema
*/ */
private $fullyLoaded = false; private $fullyLoaded = false;
/** /** @var InvariantViolation[]|null */
* @var InvariantViolation[]|null
*/
private $validationErrors; private $validationErrors;
/** /**
* Schema constructor.
*
* @api * @api
* @param array|SchemaConfig $config * @param mixed[]|SchemaConfig $config
*/ */
public function __construct($config) public function __construct($config)
{ {
@ -89,23 +87,26 @@ class Schema
Utils::invariant( Utils::invariant(
$config instanceof SchemaConfig, $config instanceof SchemaConfig,
'Schema constructor expects instance of GraphQL\Type\SchemaConfig or an array with keys: %s; but got: %s', 'Schema constructor expects instance of GraphQL\Type\SchemaConfig or an array with keys: %s; but got: %s',
implode(', ', [ implode(
', ',
[
'query', 'query',
'mutation', 'mutation',
'subscription', 'subscription',
'types', 'types',
'directives', 'directives',
'typeLoader' 'typeLoader',
]), ]
),
Utils::getVariableType($config) Utils::getVariableType($config)
); );
Utils::invariant( Utils::invariant(
!$config->types || is_array($config->types) || is_callable($config->types), ! $config->types || is_array($config->types) || is_callable($config->types),
"\"types\" must be array or callable if provided but got: " . Utils::getVariableType($config->types) '"types" must be array or callable if provided but got: ' . Utils::getVariableType($config->types)
); );
Utils::invariant( Utils::invariant(
!$config->directives || is_array($config->directives), ! $config->directives || is_array($config->directives),
"\"directives\" must be Array if provided but got: " . Utils::getVariableType($config->directives) '"directives" must be Array if provided but got: ' . Utils::getVariableType($config->directives)
); );
} }
@ -124,8 +125,10 @@ class Schema
if (isset($this->resolvedTypes[$type->name])) { if (isset($this->resolvedTypes[$type->name])) {
Utils::invariant( Utils::invariant(
$type === $this->resolvedTypes[$type->name], $type === $this->resolvedTypes[$type->name],
"Schema must contain unique named types but contains multiple types named \"$type\" ". sprintf(
"(see http://webonyx.github.io/graphql-php/type-system/#type-registry)." 'Schema must contain unique named types but contains multiple types named "%s" (see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$type
)
); );
} }
$this->resolvedTypes[$type->name] = $type; $this->resolvedTypes[$type->name] = $type;
@ -133,10 +136,98 @@ class Schema
} }
$this->resolvedTypes += Type::getInternalTypes() + Introspection::getTypes(); $this->resolvedTypes += Type::getInternalTypes() + Introspection::getTypes();
if (!$this->config->typeLoader) { if ($this->config->typeLoader) {
return;
}
// Perform full scan of the schema // Perform full scan of the schema
$this->getTypeMap(); $this->getTypeMap();
} }
/**
* @return \Generator
*/
private function resolveAdditionalTypes()
{
$types = $this->config->types ?: [];
if (is_callable($types)) {
$types = $types();
}
if (! is_array($types) && ! $types instanceof \Traversable) {
throw new InvariantViolation(sprintf(
'Schema types callable must return array or instance of Traversable but got: %s',
Utils::getVariableType($types)
));
}
foreach ($types as $index => $type) {
if (! $type instanceof Type) {
throw new InvariantViolation(sprintf(
'Each entry of schema types must be instance of GraphQL\Type\Definition\Type but entry at %s is %s',
$index,
Utils::printSafe($type)
));
}
yield $type;
}
}
/**
* Returns array of all types in this schema. Keys of this array represent type names, values are instances
* of corresponding type definitions
*
* This operation requires full schema scan. Do not use in production environment.
*
* @api
* @return Type[]
*/
public function getTypeMap()
{
if (! $this->fullyLoaded) {
$this->resolvedTypes = $this->collectAllTypes();
$this->fullyLoaded = true;
}
return $this->resolvedTypes;
}
/**
* @return Type[]
*/
private function collectAllTypes()
{
$typeMap = [];
foreach ($this->resolvedTypes as $type) {
$typeMap = TypeInfo::extractTypes($type, $typeMap);
}
foreach ($this->getDirectives() as $directive) {
if (! ($directive instanceof Directive)) {
continue;
}
$typeMap = TypeInfo::extractTypesFromDirectives($directive, $typeMap);
}
// When types are set as array they are resolved in constructor
if (is_callable($this->config->types)) {
foreach ($this->resolveAdditionalTypes() as $type) {
$typeMap = TypeInfo::extractTypes($type, $typeMap);
}
}
return $typeMap;
}
/**
* Returns a list of directives supported by this schema
*
* @api
* @return Directive[]
*/
public function getDirectives()
{
return $this->config->directives ?: GraphQL::getStandardDirectives();
} }
/** /**
@ -181,24 +272,6 @@ class Schema
return $this->config; return $this->config;
} }
/**
* Returns array of all types in this schema. Keys of this array represent type names, values are instances
* of corresponding type definitions
*
* This operation requires full schema scan. Do not use in production environment.
*
* @api
* @return Type[]
*/
public function getTypeMap()
{
if (!$this->fullyLoaded) {
$this->resolvedTypes = $this->collectAllTypes();
$this->fullyLoaded = true;
}
return $this->resolvedTypes;
}
/** /**
* Returns type by it's name * Returns type by it's name
* *
@ -208,67 +281,59 @@ class Schema
*/ */
public function getType($name) public function getType($name)
{ {
if (!isset($this->resolvedTypes[$name])) { if (! isset($this->resolvedTypes[$name])) {
$type = $this->loadType($name); $type = $this->loadType($name);
if (!$type) { if (! $type) {
return null; return null;
} }
$this->resolvedTypes[$name] = $type; $this->resolvedTypes[$name] = $type;
} }
return $this->resolvedTypes[$name]; return $this->resolvedTypes[$name];
} }
/** /**
* @return array * @param string $typeName
* @return Type
*/ */
private function collectAllTypes() private function loadType($typeName)
{ {
$typeMap = []; $typeLoader = $this->config->typeLoader;
foreach ($this->resolvedTypes as $type) {
$typeMap = TypeInfo::extractTypes($type, $typeMap); if (! $typeLoader) {
return $this->defaultTypeLoader($typeName);
} }
foreach ($this->getDirectives() as $directive) {
if ($directive instanceof Directive) { $type = $typeLoader($typeName);
$typeMap = TypeInfo::extractTypesFromDirectives($directive, $typeMap);
if (! $type instanceof Type) {
throw new InvariantViolation(
sprintf(
'Type loader is expected to return valid type "%s", but it returned %s',
$typeName,
Utils::printSafe($type)
)
);
} }
if ($type->name !== $typeName) {
throw new InvariantViolation(
sprintf('Type loader is expected to return type "%s", but it returned "%s"', $typeName, $type->name)
);
} }
// When types are set as array they are resolved in constructor
if (is_callable($this->config->types)) { return $type;
foreach ($this->resolveAdditionalTypes() as $type) {
$typeMap = TypeInfo::extractTypes($type, $typeMap);
}
}
return $typeMap;
} }
/** /**
* @return \Generator * @param string $typeName
* @return Type
*/ */
private function resolveAdditionalTypes() private function defaultTypeLoader($typeName)
{ {
$types = $this->config->types ?: []; // Default type loader simply fallbacks to collecting all types
$typeMap = $this->getTypeMap();
if (is_callable($types)) { return $typeMap[$typeName] ?? null;
$types = $types();
}
if (!is_array($types) && !$types instanceof \Traversable) {
throw new InvariantViolation(sprintf(
'Schema types callable must return array or instance of Traversable but got: %s',
Utils::getVariableType($types)
));
}
foreach ($types as $index => $type) {
if (!$type instanceof Type) {
throw new InvariantViolation(sprintf(
'Each entry of schema types must be instance of GraphQL\Type\Definition\Type but entry at %s is %s',
$index,
Utils::printSafe($type)
));
}
yield $type;
}
} }
/** /**
@ -278,17 +343,17 @@ class Schema
* This operation requires full schema scan. Do not use in production environment. * This operation requires full schema scan. Do not use in production environment.
* *
* @api * @api
* @param AbstractType $abstractType
* @return ObjectType[] * @return ObjectType[]
*/ */
public function getPossibleTypes(AbstractType $abstractType) public function getPossibleTypes(AbstractType $abstractType)
{ {
$possibleTypeMap = $this->getPossibleTypeMap(); $possibleTypeMap = $this->getPossibleTypeMap();
return isset($possibleTypeMap[$abstractType->name]) ? array_values($possibleTypeMap[$abstractType->name]) : []; return isset($possibleTypeMap[$abstractType->name]) ? array_values($possibleTypeMap[$abstractType->name]) : [];
} }
/** /**
* @return array * @return Type[][]
*/ */
private function getPossibleTypeMap() private function getPossibleTypeMap()
{ {
@ -297,55 +362,28 @@ class Schema
foreach ($this->getTypeMap() as $type) { foreach ($this->getTypeMap() as $type) {
if ($type instanceof ObjectType) { if ($type instanceof ObjectType) {
foreach ($type->getInterfaces() as $interface) { foreach ($type->getInterfaces() as $interface) {
if ($interface instanceof InterfaceType) { if (! ($interface instanceof InterfaceType)) {
continue;
}
$this->possibleTypeMap[$interface->name][$type->name] = $type; $this->possibleTypeMap[$interface->name][$type->name] = $type;
} }
} } elseif ($type instanceof UnionType) {
} else if ($type instanceof UnionType) {
foreach ($type->getTypes() as $innerType) { foreach ($type->getTypes() as $innerType) {
$this->possibleTypeMap[$type->name][$innerType->name] = $innerType; $this->possibleTypeMap[$type->name][$innerType->name] = $innerType;
} }
} }
} }
} }
return $this->possibleTypeMap; return $this->possibleTypeMap;
} }
/**
* @param $typeName
* @return Type
*/
private function loadType($typeName)
{
$typeLoader = $this->config->typeLoader;
if (!$typeLoader) {
return $this->defaultTypeLoader($typeName);
}
$type = $typeLoader($typeName);
if (!$type instanceof Type) {
throw new InvariantViolation(
"Type loader is expected to return valid type \"$typeName\", but it returned " . Utils::printSafe($type)
);
}
if ($type->name !== $typeName) {
throw new InvariantViolation(
"Type loader is expected to return type \"$typeName\", but it returned \"{$type->name}\""
);
}
return $type;
}
/** /**
* Returns true if object type is concrete type of given abstract type * Returns true if object type is concrete type of given abstract type
* (implementation for interfaces and members of union type for unions) * (implementation for interfaces and members of union type for unions)
* *
* @api * @api
* @param AbstractType $abstractType
* @param ObjectType $possibleType
* @return bool * @return bool
*/ */
public function isPossibleType(AbstractType $abstractType, ObjectType $possibleType) public function isPossibleType(AbstractType $abstractType, ObjectType $possibleType)
@ -358,22 +396,11 @@ class Schema
return $abstractType->isPossibleType($possibleType); return $abstractType->isPossibleType($possibleType);
} }
/**
* Returns a list of directives supported by this schema
*
* @api
* @return Directive[]
*/
public function getDirectives()
{
return $this->config->directives ?: GraphQL::getStandardDirectives();
}
/** /**
* Returns instance of directive by name * Returns instance of directive by name
* *
* @api * @api
* @param $name * @param string $name
* @return Directive * @return Directive
*/ */
public function getDirective($name) public function getDirective($name)
@ -383,6 +410,7 @@ class Schema
return $directive; return $directive;
} }
} }
return null; return null;
} }
@ -395,14 +423,42 @@ class Schema
} }
/** /**
* @param $typeName * Validates schema.
* @return Type *
* This operation requires full schema scan. Do not use in production environment.
*
* @api
* @throws InvariantViolation
*/ */
private function defaultTypeLoader($typeName) public function assertValid()
{ {
// Default type loader simply fallbacks to collecting all types $errors = $this->validate();
$typeMap = $this->getTypeMap();
return isset($typeMap[$typeName]) ? $typeMap[$typeName] : null; if ($errors) {
throw new InvariantViolation(implode("\n\n", $this->validationErrors));
}
$internalTypes = Type::getInternalTypes() + Introspection::getTypes();
foreach ($this->getTypeMap() as $name => $type) {
if (isset($internalTypes[$name])) {
continue;
}
$type->assertValid();
// Make sure type loader returns the same instance as registered in other places of schema
if (! $this->config->typeLoader) {
continue;
}
Utils::invariant(
$this->loadType($name) === $type,
sprintf(
'Type loader returns different instance for %s than field/argument definitions. Make sure you always return the same instance for the same type name.',
$name
)
);
}
} }
/** /**
@ -431,39 +487,4 @@ class Schema
return $this->validationErrors; return $this->validationErrors;
} }
/**
* Validates schema.
*
* This operation requires full schema scan. Do not use in production environment.
*
* @api
* @throws InvariantViolation
*/
public function assertValid()
{
$errors = $this->validate();
if ($errors) {
throw new InvariantViolation(implode("\n\n", $this->validationErrors));
}
$internalTypes = Type::getInternalTypes() + Introspection::getTypes();
foreach ($this->getTypeMap() as $name => $type) {
if (isset($internalTypes[$name])) {
continue ;
}
$type->assertValid();
// Make sure type loader returns the same instance as registered in other places of schema
if ($this->config->typeLoader) {
Utils::invariant(
$this->loadType($name) === $type,
"Type loader returns different instance for {$name} than field/argument definitions. ".
'Make sure you always return the same instance for the same type name.'
);
}
}
}
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Type; namespace GraphQL\Type;
use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\SchemaDefinitionNode;
@ -6,6 +9,7 @@ use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_callable;
/** /**
* Schema configuration class. * Schema configuration class.
@ -23,44 +27,28 @@ use GraphQL\Utils\Utils;
*/ */
class SchemaConfig class SchemaConfig
{ {
/** /** @var ObjectType */
* @var ObjectType
*/
public $query; public $query;
/** /** @var ObjectType */
* @var ObjectType
*/
public $mutation; public $mutation;
/** /** @var ObjectType */
* @var ObjectType
*/
public $subscription; public $subscription;
/** /** @var Type[]|callable */
* @var Type[]|callable
*/
public $types; public $types;
/** /** @var Directive[] */
* @var Directive[]
*/
public $directives; public $directives;
/** /** @var callable */
* @var callable
*/
public $typeLoader; public $typeLoader;
/** /** @var SchemaDefinitionNode */
* @var SchemaDefinitionNode
*/
public $astNode; public $astNode;
/** /** @var bool */
* @var bool
*/
public $assumeValid; public $assumeValid;
/** /**
@ -68,14 +56,14 @@ class SchemaConfig
* (or just returns empty config when array is not passed). * (or just returns empty config when array is not passed).
* *
* @api * @api
* @param array $options * @param mixed[] $options
* @return SchemaConfig * @return SchemaConfig
*/ */
public static function create(array $options = []) public static function create(array $options = [])
{ {
$config = new static(); $config = new static();
if (!empty($options)) { if (! empty($options)) {
if (isset($options['query'])) { if (isset($options['query'])) {
$config->setQuery($options['query']); $config->setQuery($options['query']);
} }
@ -126,88 +114,12 @@ class SchemaConfig
} }
/** /**
* @param SchemaDefinitionNode $astNode
* @return SchemaConfig * @return SchemaConfig
*/ */
public function setAstNode(SchemaDefinitionNode $astNode) public function setAstNode(SchemaDefinitionNode $astNode)
{ {
$this->astNode = $astNode; $this->astNode = $astNode;
return $this;
}
/**
* @api
* @param ObjectType $query
* @return SchemaConfig
*/
public function setQuery($query)
{
$this->query = $query;
return $this;
}
/**
* @api
* @param ObjectType $mutation
* @return SchemaConfig
*/
public function setMutation($mutation)
{
$this->mutation = $mutation;
return $this;
}
/**
* @api
* @param ObjectType $subscription
* @return SchemaConfig
*/
public function setSubscription($subscription)
{
$this->subscription = $subscription;
return $this;
}
/**
* @api
* @param Type[]|callable $types
* @return SchemaConfig
*/
public function setTypes($types)
{
$this->types = $types;
return $this;
}
/**
* @api
* @param Directive[] $directives
* @return SchemaConfig
*/
public function setDirectives(array $directives)
{
$this->directives = $directives;
return $this;
}
/**
* @api
* @param callable $typeLoader
* @return SchemaConfig
*/
public function setTypeLoader(callable $typeLoader)
{
$this->typeLoader = $typeLoader;
return $this;
}
/**
* @param bool $assumeValid
* @return SchemaConfig
*/
public function setAssumeValid($assumeValid)
{
$this->assumeValid = $assumeValid;
return $this; return $this;
} }
@ -220,6 +132,18 @@ class SchemaConfig
return $this->query; return $this->query;
} }
/**
* @api
* @param ObjectType $query
* @return SchemaConfig
*/
public function setQuery($query)
{
$this->query = $query;
return $this;
}
/** /**
* @api * @api
* @return ObjectType * @return ObjectType
@ -229,6 +153,18 @@ class SchemaConfig
return $this->mutation; return $this->mutation;
} }
/**
* @api
* @param ObjectType $mutation
* @return SchemaConfig
*/
public function setMutation($mutation)
{
$this->mutation = $mutation;
return $this;
}
/** /**
* @api * @api
* @return ObjectType * @return ObjectType
@ -238,6 +174,18 @@ class SchemaConfig
return $this->subscription; return $this->subscription;
} }
/**
* @api
* @param ObjectType $subscription
* @return SchemaConfig
*/
public function setSubscription($subscription)
{
$this->subscription = $subscription;
return $this;
}
/** /**
* @api * @api
* @return Type[] * @return Type[]
@ -247,6 +195,18 @@ class SchemaConfig
return $this->types ?: []; return $this->types ?: [];
} }
/**
* @api
* @param Type[]|callable $types
* @return SchemaConfig
*/
public function setTypes($types)
{
$this->types = $types;
return $this;
}
/** /**
* @api * @api
* @return Directive[] * @return Directive[]
@ -256,6 +216,18 @@ class SchemaConfig
return $this->directives ?: []; return $this->directives ?: [];
} }
/**
* @api
* @param Directive[] $directives
* @return SchemaConfig
*/
public function setDirectives(array $directives)
{
$this->directives = $directives;
return $this;
}
/** /**
* @api * @api
* @return callable * @return callable
@ -265,6 +237,17 @@ class SchemaConfig
return $this->typeLoader; return $this->typeLoader;
} }
/**
* @api
* @return SchemaConfig
*/
public function setTypeLoader(callable $typeLoader)
{
$this->typeLoader = $typeLoader;
return $this;
}
/** /**
* @return bool * @return bool
*/ */
@ -272,4 +255,15 @@ class SchemaConfig
{ {
return $this->assumeValid; return $this->assumeValid;
} }
/**
* @param bool $assumeValid
* @return SchemaConfig
*/
public function setAssumeValid($assumeValid)
{
$this->assumeValid = $assumeValid;
return $this;
}
} }

File diff suppressed because it is too large Load Diff

17
src/Type/TypeKind.php Normal file
View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Type;
class TypeKind
{
const SCALAR = 0;
const OBJECT = 1;
const INTERFACE_KIND = 2;
const UNION = 3;
const ENUM = 4;
const INPUT_OBJECT = 5;
const LIST_KIND = 6;
const NON_NULL = 7;
}