Fix CS in Utils

This commit is contained in:
Simon Podlipsky 2018-08-22 01:20:49 +02:00
parent a3ef1be1ab
commit 49f34d3243
No known key found for this signature in database
GPG Key ID: 725C2BD962B42663
15 changed files with 2216 additions and 1803 deletions

View File

@ -3,7 +3,7 @@ build:
analysis: analysis:
environment: environment:
php: php:
version: 5.6 version: 7.1
cache: cache:
disabled: false disabled: false
directories: directories:

View File

@ -3,6 +3,7 @@
- Spec compliance: error extensions are displayed under `extensions` key - Spec compliance: error extensions are displayed under `extensions` key
- `AbstractValidationRule` renamed to `ValidationRule` (NS `GraphQL\Validator\Rules`) - `AbstractValidationRule` renamed to `ValidationRule` (NS `GraphQL\Validator\Rules`)
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`) - `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
- `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`)
#### v0.12.5 #### v0.12.5
- Execution performance optimization for lists - Execution performance optimization for lists

View File

@ -81,9 +81,8 @@ class AST
* *
* @api * @api
* @param mixed[] $node * @param mixed[] $node
* @return Node
*/ */
public static function fromArray(array $node) public static function fromArray(array $node) : Node
{ {
if (! isset($node['kind']) || ! isset(NodeKind::$classMap[$node['kind']])) { if (! isset($node['kind']) || ! isset(NodeKind::$classMap[$node['kind']])) {
throw new InvariantViolation('Unexpected node structure: ' . Utils::printSafeJson($node)); throw new InvariantViolation('Unexpected node structure: ' . Utils::printSafeJson($node));
@ -457,6 +456,19 @@ class AST
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.'); throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
} }
/**
* Returns true if the provided valueNode is a variable which is not defined
* in the set of variables.
* @param ValueNode $valueNode
* @param mixed[] $variables
* @return bool
*/
private static function isMissingVariable($valueNode, $variables)
{
return $valueNode instanceof VariableNode &&
(count($variables) === 0 || ! array_key_exists($valueNode->name->value, $variables));
}
/** /**
* Produces a PHP value given a GraphQL Value AST. * Produces a PHP value given a GraphQL Value AST.
* *
@ -552,19 +564,6 @@ class AST
throw new Error('Unexpected type kind: ' . $inputTypeNode->kind . '.'); throw new Error('Unexpected type kind: ' . $inputTypeNode->kind . '.');
} }
/**
* Returns true if the provided valueNode is a variable which is not defined
* in the set of variables.
* @param ValueNode $valueNode
* @param mixed[] $variables
* @return bool
*/
private static function isMissingVariable($valueNode, $variables)
{
return $valueNode instanceof VariableNode &&
(count($variables) === 0 || ! array_key_exists($valueNode->name->value, $variables));
}
/** /**
* Returns operation type ("query", "mutation" or "subscription") given a document and operation name * Returns operation type ("query", "mutation" or "subscription") given a document and operation name
* *

View File

@ -75,36 +75,112 @@ class ASTDefinitionBuilder
$this->cache = Type::getAllBuiltInTypes(); $this->cache = Type::getAllBuiltInTypes();
} }
public function buildDirective(DirectiveDefinitionNode $directiveNode)
{
return new Directive([
'name' => $directiveNode->name->value,
'description' => $this->getDescription($directiveNode),
'locations' => Utils::map(
$directiveNode->locations,
function ($node) {
return $node->value;
}
),
'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
'astNode' => $directiveNode,
]);
}
/** /**
* @param TypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode * Given an ast node, returns its string description.
*/
private function getDescription($node)
{
if ($node->description) {
return $node->description->value;
}
if (isset($this->options['commentDescriptions'])) {
$rawValue = $this->getLeadingCommentBlock($node);
if ($rawValue !== null) {
return BlockString::value("\n" . $rawValue);
}
}
return null;
}
private function getLeadingCommentBlock($node)
{
$loc = $node->loc;
if (! $loc || ! $loc->startToken) {
return null;
}
$comments = [];
$token = $loc->startToken->prev;
while ($token &&
$token->kind === Token::COMMENT &&
$token->next && $token->prev &&
$token->line + 1 === $token->next->line &&
$token->line !== $token->prev->line
) {
$value = $token->value;
$comments[] = $value;
$token = $token->prev;
}
return implode("\n", array_reverse($comments));
}
private function makeInputValues($values)
{
return Utils::keyValMap(
$values,
function ($value) {
return $value->name->value;
},
function ($value) {
// Note: While this could make assertions to get the correctly typed
// value, that would throw immediately while type system validation
// with validateSchema() will produce more actionable results.
$type = $this->internalBuildWrappedType($value->type);
$config = [
'name' => $value->name->value,
'type' => $type,
'description' => $this->getDescription($value),
'astNode' => $value,
];
if (isset($value->defaultValue)) {
$config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type);
}
return $config;
}
);
}
/**
* @return Type|InputType
* @throws Error
*/
private function internalBuildWrappedType(TypeNode $typeNode)
{
$typeDef = $this->buildType($this->getNamedTypeNode($typeNode));
return $this->buildWrappedType($typeDef, $typeNode);
}
/**
* @param string|NamedTypeNode $ref
* @return Type * @return Type
* @throws Error
*/ */
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode) public function buildType($ref)
{ {
if ($inputTypeNode->kind === NodeKind::LIST_TYPE) { if (is_string($ref)) {
return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type)); return $this->internalBuildType($ref);
}
if ($inputTypeNode->kind === NodeKind::NON_NULL_TYPE) {
$wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
return Type::nonNull(NonNull::assertNullableType($wrappedType));
} }
return $innerType; return $this->internalBuildType($ref->name->value, $ref);
}
/**
* @param TypeNode|ListTypeNode|NonNullTypeNode $typeNode
* @return TypeNode
*/
private function getNamedTypeNode(TypeNode $typeNode)
{
$namedType = $typeNode;
while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) {
$namedType = $namedType->type;
}
return $namedType;
} }
/** /**
@ -154,61 +230,6 @@ class ASTDefinitionBuilder
return $this->cache[$typeName]; return $this->cache[$typeName];
} }
/**
* @param string|NamedTypeNode $ref
* @return Type
* @throws Error
*/
public function buildType($ref)
{
if (is_string($ref)) {
return $this->internalBuildType($ref);
}
return $this->internalBuildType($ref->name->value, $ref);
}
/**
* @return Type|InputType
* @throws Error
*/
private function internalBuildWrappedType(TypeNode $typeNode)
{
$typeDef = $this->buildType($this->getNamedTypeNode($typeNode));
return $this->buildWrappedType($typeDef, $typeNode);
}
public function buildDirective(DirectiveDefinitionNode $directiveNode)
{
return new Directive([
'name' => $directiveNode->name->value,
'description' => $this->getDescription($directiveNode),
'locations' => Utils::map(
$directiveNode->locations,
function ($node) {
return $node->value;
}
),
'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
'astNode' => $directiveNode,
]);
}
public function buildField(FieldDefinitionNode $field)
{
return [
// Note: While this could make assertions to get the correctly typed
// value, that would throw immediately while type system validation
// with validateSchema() will produce more actionable results.
'type' => $this->internalBuildWrappedType($field->type),
'description' => $this->getDescription($field),
'args' => $field->arguments ? $this->makeInputValues($field->arguments) : null,
'deprecationReason' => $this->getDeprecationReason($field),
'astNode' => $field,
];
}
/** /**
* @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeDefinitionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode|UnionTypeDefinitionNode $def * @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeDefinitionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode|UnionTypeDefinitionNode $def
* @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType * @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType
@ -237,35 +258,6 @@ class ASTDefinitionBuilder
} }
} }
/**
* @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeExtensionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode $def
* @param mixed[] $config
* @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType
* @throws Error
*/
private function makeSchemaDefFromConfig($def, array $config)
{
if (! $def) {
throw new Error('def must be defined.');
}
switch ($def->kind) {
case NodeKind::OBJECT_TYPE_DEFINITION:
return new ObjectType($config);
case NodeKind::INTERFACE_TYPE_DEFINITION:
return new InterfaceType($config);
case NodeKind::ENUM_TYPE_DEFINITION:
return new EnumType($config);
case NodeKind::UNION_TYPE_DEFINITION:
return new UnionType($config);
case NodeKind::SCALAR_TYPE_DEFINITION:
return new CustomScalarType($config);
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
return new InputObjectType($config);
default:
throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
}
}
private function makeTypeDef(ObjectTypeDefinitionNode $def) private function makeTypeDef(ObjectTypeDefinitionNode $def)
{ {
$typeName = $def->name->value; $typeName = $def->name->value;
@ -298,6 +290,34 @@ class ASTDefinitionBuilder
: []; : [];
} }
public function buildField(FieldDefinitionNode $field)
{
return [
// Note: While this could make assertions to get the correctly typed
// value, that would throw immediately while type system validation
// with validateSchema() will produce more actionable results.
'type' => $this->internalBuildWrappedType($field->type),
'description' => $this->getDescription($field),
'args' => $field->arguments ? $this->makeInputValues($field->arguments) : null,
'deprecationReason' => $this->getDeprecationReason($field),
'astNode' => $field,
];
}
/**
* Given a collection of directives, returns the string value for the
* deprecation reason.
*
* @param EnumValueDefinitionNode | FieldDefinitionNode $node
* @return string
*/
private function getDeprecationReason($node)
{
$deprecated = Values::getDirectiveValues(Directive::deprecatedDirective(), $node);
return $deprecated['reason'] ?? null;
}
private function makeImplementedInterfaces(ObjectTypeDefinitionNode $def) private function makeImplementedInterfaces(ObjectTypeDefinitionNode $def)
{ {
if ($def->interfaces) { if ($def->interfaces) {
@ -315,33 +335,6 @@ class ASTDefinitionBuilder
return null; return null;
} }
private function makeInputValues($values)
{
return Utils::keyValMap(
$values,
function ($value) {
return $value->name->value;
},
function ($value) {
// Note: While this could make assertions to get the correctly typed
// value, that would throw immediately while type system validation
// with validateSchema() will produce more actionable results.
$type = $this->internalBuildWrappedType($value->type);
$config = [
'name' => $value->name->value,
'type' => $type,
'description' => $this->getDescription($value),
'astNode' => $value,
];
if (isset($value->defaultValue)) {
$config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type);
}
return $config;
}
);
}
private function makeInterfaceDef(InterfaceTypeDefinitionNode $def) private function makeInterfaceDef(InterfaceTypeDefinitionNode $def)
{ {
$typeName = $def->name->value; $typeName = $def->name->value;
@ -427,56 +420,63 @@ class ASTDefinitionBuilder
} }
/** /**
* Given a collection of directives, returns the string value for the * @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeExtensionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode $def
* deprecation reason. * @param mixed[] $config
* * @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType
* @param EnumValueDefinitionNode | FieldDefinitionNode $node * @throws Error
* @return string
*/ */
private function getDeprecationReason($node) private function makeSchemaDefFromConfig($def, array $config)
{ {
$deprecated = Values::getDirectiveValues(Directive::deprecatedDirective(), $node); if (! $def) {
throw new Error('def must be defined.');
return $deprecated['reason'] ?? null; }
switch ($def->kind) {
case NodeKind::OBJECT_TYPE_DEFINITION:
return new ObjectType($config);
case NodeKind::INTERFACE_TYPE_DEFINITION:
return new InterfaceType($config);
case NodeKind::ENUM_TYPE_DEFINITION:
return new EnumType($config);
case NodeKind::UNION_TYPE_DEFINITION:
return new UnionType($config);
case NodeKind::SCALAR_TYPE_DEFINITION:
return new CustomScalarType($config);
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
return new InputObjectType($config);
default:
throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
}
} }
/** /**
* Given an ast node, returns its string description. * @param TypeNode|ListTypeNode|NonNullTypeNode $typeNode
* @return TypeNode
*/ */
private function getDescription($node) private function getNamedTypeNode(TypeNode $typeNode)
{ {
if ($node->description) { $namedType = $typeNode;
return $node->description->value; while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) {
} $namedType = $namedType->type;
if (isset($this->options['commentDescriptions'])) {
$rawValue = $this->getLeadingCommentBlock($node);
if ($rawValue !== null) {
return BlockString::value("\n" . $rawValue);
}
} }
return null; return $namedType;
} }
private function getLeadingCommentBlock($node) /**
* @param TypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
* @return Type
*/
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode)
{ {
$loc = $node->loc; if ($inputTypeNode->kind === NodeKind::LIST_TYPE) {
if (! $loc || ! $loc->startToken) { return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type));
return null;
} }
$comments = []; if ($inputTypeNode->kind === NodeKind::NON_NULL_TYPE) {
$token = $loc->startToken->prev; $wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
while ($token &&
$token->kind === Token::COMMENT && return Type::nonNull(NonNull::assertNullableType($wrappedType));
$token->next && $token->prev &&
$token->line + 1 === $token->next->line &&
$token->line !== $token->prev->line
) {
$value = $token->value;
$comments[] = $value;
$token = $token->prev;
} }
return implode("\n", array_reverse($comments)); return $innerType;
} }
} }

View File

@ -1,14 +1,28 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Utils; namespace GraphQL\Utils;
class BlockString { use function array_pop;
use function array_shift;
use function count;
use function implode;
use function mb_strlen;
use function mb_substr;
use function preg_split;
use function trim;
class BlockString
{
/** /**
* Produces the value of a block string from its parsed raw value, similar to * Produces the value of a block string from its parsed raw value, similar to
* Coffeescript's block string, Python's docstring trim or Ruby's strip_heredoc. * Coffeescript's block string, Python's docstring trim or Ruby's strip_heredoc.
* *
* This implements the GraphQL spec's BlockStringValue() static algorithm. * This implements the GraphQL spec's BlockStringValue() static algorithm.
*/ */
public static function value($rawString) { public static function value($rawString)
{
// Expand a block string's raw value into independent lines. // Expand a block string's raw value into independent lines.
$lines = preg_split("/\\r\\n|[\\n\\r]/", $rawString); $lines = preg_split("/\\r\\n|[\\n\\r]/", $rawString);
@ -20,16 +34,17 @@ class BlockString {
$line = $lines[$i]; $line = $lines[$i];
$indent = self::leadingWhitespace($line); $indent = self::leadingWhitespace($line);
if ( if ($indent >= mb_strlen($line) ||
$indent < mb_strlen($line) && ($commonIndent !== null && $indent >= $commonIndent)
($commonIndent === null || $indent < $commonIndent)
) { ) {
continue;
}
$commonIndent = $indent; $commonIndent = $indent;
if ($commonIndent === 0) { if ($commonIndent === 0) {
break; break;
} }
} }
}
if ($commonIndent) { if ($commonIndent) {
for ($i = 1; $i < $linesLength; $i++) { for ($i = 1; $i < $linesLength; $i++) {
@ -50,7 +65,8 @@ class BlockString {
return implode("\n", $lines); return implode("\n", $lines);
} }
private static function leadingWhitespace($str) { private static function leadingWhitespace($str)
{
$i = 0; $i = 0;
while ($i < mb_strlen($str) && ($str[$i] === ' ' || $str[$i] === '\t')) { while ($i < mb_strlen($str) && ($str[$i] === ' ' || $str[$i] === '\t')) {
$i++; $i++;

View File

@ -1,14 +1,21 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Utils; namespace GraphQL\Utils;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\Source; use GraphQL\Language\Source;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Schema;
use function array_map;
use function array_reduce;
use function sprintf;
/** /**
* Build instance of `GraphQL\Type\Schema` out of type language definition (string or parsed AST) * Build instance of `GraphQL\Type\Schema` out of type language definition (string or parsed AST)
@ -16,6 +23,44 @@ use GraphQL\Type\Definition\Directive;
*/ */
class BuildSchema class BuildSchema
{ {
/** @var DocumentNode */
private $ast;
/** @var Node[] */
private $nodeMap;
/** @var callable|null */
private $typeConfigDecorator;
/** @var bool[] */
private $options;
/**
* @param bool[] $options
*/
public function __construct(DocumentNode $ast, ?callable $typeConfigDecorator = null, array $options = [])
{
$this->ast = $ast;
$this->typeConfigDecorator = $typeConfigDecorator;
$this->options = $options;
}
/**
* A helper function to build a GraphQLSchema directly from a source
* document.
*
* @api
* @param DocumentNode|Source|string $source
* @param bool[] $options
* @return Schema
*/
public static function build($source, ?callable $typeConfigDecorator = null, array $options = [])
{
$doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
return self::buildAST($doc, $typeConfigDecorator, $options);
}
/** /**
* This takes the ast of a schema document produced by the parse function in * This takes the ast of a schema document produced by the parse function in
* GraphQL\Language\Parser. * GraphQL\Language\Parser.
@ -33,30 +78,17 @@ class BuildSchema
* *
* *
* @api * @api
* @param DocumentNode $ast * @param bool[] $options
* @param callable $typeConfigDecorator
* @param array $options
* @return Schema * @return Schema
* @throws Error * @throws Error
*/ */
public static function buildAST(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = []) public static function buildAST(DocumentNode $ast, ?callable $typeConfigDecorator = null, array $options = [])
{ {
$builder = new self($ast, $typeConfigDecorator, $options); $builder = new self($ast, $typeConfigDecorator, $options);
return $builder->buildSchema(); return $builder->buildSchema();
} }
private $ast;
private $nodeMap;
private $typeConfigDecorator;
private $options;
public function __construct(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [])
{
$this->ast = $ast;
$this->typeConfigDecorator = $typeConfigDecorator;
$this->options = $options;
}
public function buildSchema() public function buildSchema()
{ {
/** @var SchemaDefinitionNode $schemaDef */ /** @var SchemaDefinitionNode $schemaDef */
@ -80,7 +112,7 @@ class BuildSchema
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
$typeName = $d->name->value; $typeName = $d->name->value;
if (! empty($this->nodeMap[$typeName])) { if (! empty($this->nodeMap[$typeName])) {
throw new Error("Type \"$typeName\" was defined more than once."); throw new Error(sprintf('Type "%s" was defined more than once.', $typeName));
} }
$typeDefs[] = $d; $typeDefs[] = $d;
$this->nodeMap[$typeName] = $d; $this->nodeMap[$typeName] = $d;
@ -102,32 +134,46 @@ class BuildSchema
$defintionBuilder = new ASTDefinitionBuilder( $defintionBuilder = new ASTDefinitionBuilder(
$this->nodeMap, $this->nodeMap,
$this->options, $this->options,
function($typeName) { throw new Error('Type "'. $typeName . '" not found in document.'); }, function ($typeName) {
throw new Error('Type "' . $typeName . '" not found in document.');
},
$this->typeConfigDecorator $this->typeConfigDecorator
); );
$directives = array_map(function($def) use ($defintionBuilder) { $directives = array_map(
function ($def) use ($defintionBuilder) {
return $defintionBuilder->buildDirective($def); return $defintionBuilder->buildDirective($def);
}, $directiveDefs); },
$directiveDefs
);
// If specified directives were not explicitly declared, add them. // If specified directives were not explicitly declared, add them.
$skip = array_reduce($directives, function ($hasSkip, $directive) { $skip = array_reduce(
return $hasSkip || $directive->name == 'skip'; $directives,
}); function ($hasSkip, $directive) {
return $hasSkip || $directive->name === 'skip';
}
);
if (! $skip) { if (! $skip) {
$directives[] = Directive::skipDirective(); $directives[] = Directive::skipDirective();
} }
$include = array_reduce($directives, function ($hasInclude, $directive) { $include = array_reduce(
return $hasInclude || $directive->name == 'include'; $directives,
}); function ($hasInclude, $directive) {
return $hasInclude || $directive->name === 'include';
}
);
if (! $include) { if (! $include) {
$directives[] = Directive::includeDirective(); $directives[] = Directive::includeDirective();
} }
$deprecated = array_reduce($directives, function ($hasDeprecated, $directive) { $deprecated = array_reduce(
return $hasDeprecated || $directive->name == 'deprecated'; $directives,
}); function ($hasDeprecated, $directive) {
return $hasDeprecated || $directive->name === 'deprecated';
}
);
if (! $deprecated) { if (! $deprecated) {
$directives[] = Directive::deprecatedDirective(); $directives[] = Directive::deprecatedDirective();
} }
@ -156,8 +202,9 @@ class BuildSchema
foreach ($this->nodeMap as $name => $def) { foreach ($this->nodeMap as $name => $def) {
$types[] = $defintionBuilder->buildType($def->name->value); $types[] = $defintionBuilder->buildType($def->name->value);
} }
return $types; return $types;
} },
]); ]);
return $schema; return $schema;
@ -165,7 +212,7 @@ class BuildSchema
/** /**
* @param SchemaDefinitionNode $schemaDef * @param SchemaDefinitionNode $schemaDef
* @return array * @return string[]
* @throws Error * @throws Error
*/ */
private function getOperationTypes($schemaDef) private function getOperationTypes($schemaDef)
@ -177,11 +224,11 @@ class BuildSchema
$operation = $operationType->operation; $operation = $operationType->operation;
if (isset($opTypes[$operation])) { if (isset($opTypes[$operation])) {
throw new Error("Must provide only one $operation type in schema."); throw new Error(sprintf('Must provide only one %s type in schema.', $operation));
} }
if (! isset($this->nodeMap[$typeName])) { if (! isset($this->nodeMap[$typeName])) {
throw new Error("Specified $operation type \"$typeName\" not found in document."); throw new Error(sprintf('Specified %s type "%s" not found in document.', $operation, $typeName));
} }
$opTypes[$operation] = $typeName; $opTypes[$operation] = $typeName;
@ -189,20 +236,4 @@ class BuildSchema
return $opTypes; return $opTypes;
} }
/**
* A helper function to build a GraphQLSchema directly from a source
* document.
*
* @api
* @param DocumentNode|Source|string $source
* @param callable $typeConfigDecorator
* @param array $options
* @return Schema
*/
public static function build($source, callable $typeConfigDecorator = null, array $options = [])
{
$doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
return self::buildAST($doc, $typeConfigDecorator, $options);
}
} }

View File

@ -1,6 +1,19 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Utils; namespace GraphQL\Utils;
use GraphQL\Type\Definition\EnumValueDefinition;
use function array_key_exists;
use function array_search;
use function array_splice;
use function is_array;
use function is_float;
use function is_int;
use function is_object;
use function is_string;
/** /**
* Similar to PHP array, but allows any type of data to act as key (including arrays, objects, scalars) * Similar to PHP array, but allows any type of data to act as key (including arrays, objects, scalars)
* *
@ -8,78 +21,48 @@ namespace GraphQL\Utils;
* (yet this should be really rare case and should be avoided when possible) * (yet this should be really rare case and should be avoided when possible)
* *
* Class MixedStore * Class MixedStore
* @package GraphQL\Utils
*/ */
class MixedStore implements \ArrayAccess class MixedStore implements \ArrayAccess
{ {
/** /** @var EnumValueDefinition[] */
* @var array
*/
private $standardStore; private $standardStore;
/** /** @var mixed[] */
* @var array
*/
private $floatStore; private $floatStore;
/** /** @var \SplObjectStorage */
* @var \SplObjectStorage
*/
private $objectStore; private $objectStore;
/** /** @var callable[] */
* @var array
*/
private $arrayKeys; private $arrayKeys;
/** /** @var EnumValueDefinition[] */
* @var array
*/
private $arrayValues; private $arrayValues;
/** /** @var callable[] */
* @var array
*/
private $lastArrayKey; private $lastArrayKey;
/** /** @var mixed */
* @var mixed
*/
private $lastArrayValue; private $lastArrayValue;
/** /** @var mixed */
* @var mixed
*/
private $nullValue; private $nullValue;
/** /** @var bool */
* @var bool
*/
private $nullValueIsSet; private $nullValueIsSet;
/** /** @var mixed */
* @var mixed
*/
private $trueValue; private $trueValue;
/** /** @var bool */
* @var bool
*/
private $trueValueIsSet; private $trueValueIsSet;
/** /** @var mixed */
* @var mixed
*/
private $falseValue; private $falseValue;
/** /** @var bool */
* @var bool
*/
private $falseValueIsSet; private $falseValueIsSet;
/**
* MixedStore constructor.
*/
public function __construct() public function __construct()
{ {
$this->standardStore = []; $this->standardStore = [];
@ -98,18 +81,17 @@ class MixedStore implements \ArrayAccess
* @param mixed $offset <p> * @param mixed $offset <p>
* An offset to check for. * An offset to check for.
* </p> * </p>
* @return boolean true on success or false on failure. * @return bool true on success or false on failure.
* </p> * </p>
* <p> * <p>
* The return value will be casted to boolean if non-boolean was returned. * The return value will be casted to boolean if non-boolean was returned.
* @since 5.0.0
*/ */
public function offsetExists($offset) public function offsetExists($offset)
{ {
if (false === $offset) { if ($offset === false) {
return $this->falseValueIsSet; return $this->falseValueIsSet;
} }
if (true === $offset) { if ($offset === true) {
return $this->trueValueIsSet; return $this->trueValueIsSet;
} }
if (is_int($offset) || is_string($offset)) { if (is_int($offset) || is_string($offset)) {
@ -126,13 +108,15 @@ class MixedStore implements \ArrayAccess
if ($entry === $offset) { if ($entry === $offset) {
$this->lastArrayKey = $offset; $this->lastArrayKey = $offset;
$this->lastArrayValue = $this->arrayValues[$index]; $this->lastArrayValue = $this->arrayValues[$index];
return true; return true;
} }
} }
} }
if (null === $offset) { if ($offset === null) {
return $this->nullValueIsSet; return $this->nullValueIsSet;
} }
return false; return false;
} }
@ -143,14 +127,13 @@ class MixedStore implements \ArrayAccess
* The offset to retrieve. * The offset to retrieve.
* </p> * </p>
* @return mixed Can return all value types. * @return mixed Can return all value types.
* @since 5.0.0
*/ */
public function offsetGet($offset) public function offsetGet($offset)
{ {
if (true === $offset) { if ($offset === true) {
return $this->trueValue; return $this->trueValue;
} }
if (false === $offset) { if ($offset === false) {
return $this->falseValue; return $this->falseValue;
} }
if (is_int($offset) || is_string($offset)) { if (is_int($offset) || is_string($offset)) {
@ -173,9 +156,10 @@ class MixedStore implements \ArrayAccess
} }
} }
} }
if (null === $offset) { if ($offset === null) {
return $this->nullValue; return $this->nullValue;
} }
return null; return null;
} }
@ -189,14 +173,13 @@ class MixedStore implements \ArrayAccess
* The value to set. * The value to set.
* </p> * </p>
* @return void * @return void
* @since 5.0.0
*/ */
public function offsetSet($offset, $value) public function offsetSet($offset, $value)
{ {
if (false === $offset) { if ($offset === false) {
$this->falseValue = $value; $this->falseValue = $value;
$this->falseValueIsSet = true; $this->falseValueIsSet = true;
} else if (true === $offset) { } elseif ($offset === true) {
$this->trueValue = $value; $this->trueValue = $value;
$this->trueValueIsSet = true; $this->trueValueIsSet = true;
} elseif (is_int($offset) || is_string($offset)) { } elseif (is_int($offset) || is_string($offset)) {
@ -208,11 +191,11 @@ class MixedStore implements \ArrayAccess
} elseif (is_array($offset)) { } elseif (is_array($offset)) {
$this->arrayKeys[] = $offset; $this->arrayKeys[] = $offset;
$this->arrayValues[] = $value; $this->arrayValues[] = $value;
} else if (null === $offset) { } elseif ($offset === null) {
$this->nullValue = $value; $this->nullValue = $value;
$this->nullValueIsSet = true; $this->nullValueIsSet = true;
} else { } else {
throw new \InvalidArgumentException("Unexpected offset type: " . Utils::printSafe($offset)); throw new \InvalidArgumentException('Unexpected offset type: ' . Utils::printSafe($offset));
} }
} }
@ -223,14 +206,13 @@ class MixedStore implements \ArrayAccess
* The offset to unset. * The offset to unset.
* </p> * </p>
* @return void * @return void
* @since 5.0.0
*/ */
public function offsetUnset($offset) public function offsetUnset($offset)
{ {
if (true === $offset) { if ($offset === true) {
$this->trueValue = null; $this->trueValue = null;
$this->trueValueIsSet = false; $this->trueValueIsSet = false;
} else if (false === $offset) { } elseif ($offset === false) {
$this->falseValue = null; $this->falseValue = null;
$this->falseValueIsSet = false; $this->falseValueIsSet = false;
} elseif (is_int($offset) || is_string($offset)) { } elseif (is_int($offset) || is_string($offset)) {
@ -242,11 +224,11 @@ class MixedStore implements \ArrayAccess
} elseif (is_array($offset)) { } elseif (is_array($offset)) {
$index = array_search($offset, $this->arrayKeys, true); $index = array_search($offset, $this->arrayKeys, true);
if (false !== $index) { if ($index !== false) {
array_splice($this->arrayKeys, $index, 1); array_splice($this->arrayKeys, $index, 1);
array_splice($this->arrayValues, $index, 1); array_splice($this->arrayValues, $index, 1);
} }
} else if (null === $offset) { } elseif ($offset === null) {
$this->nullValue = null; $this->nullValue = null;
$this->nullValueIsSet = false; $this->nullValueIsSet = false;
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Utils; namespace GraphQL\Utils;
/** /**
@ -7,14 +10,9 @@ namespace GraphQL\Utils;
*/ */
class PairSet class PairSet
{ {
/** /** @var bool[][] */
* @var array
*/
private $data; private $data;
/**
* PairSet constructor.
*/
public function __construct() public function __construct()
{ {
$this->data = []; $this->data = [];
@ -28,7 +26,7 @@ class PairSet
*/ */
public function has($a, $b, $areMutuallyExclusive) public function has($a, $b, $areMutuallyExclusive)
{ {
$first = isset($this->data[$a]) ? $this->data[$a] : null; $first = $this->data[$a] ?? null;
$result = ($first && isset($first[$b])) ? $first[$b] : null; $result = ($first && isset($first[$b])) ? $first[$b] : null;
if ($result === null) { if ($result === null) {
return false; return false;
@ -39,6 +37,7 @@ class PairSet
if ($areMutuallyExclusive === false) { if ($areMutuallyExclusive === false) {
return $result === false; return $result === false;
} }
return true; return true;
} }
@ -60,7 +59,7 @@ class PairSet
*/ */
private function pairSetAdd($a, $b, $areMutuallyExclusive) private function pairSetAdd($a, $b, $areMutuallyExclusive)
{ {
$this->data[$a] = isset($this->data[$a]) ? $this->data[$a] : []; $this->data[$a] = $this->data[$a] ?? [];
$this->data[$a][$b] = $areMutuallyExclusive; $this->data[$a][$b] = $areMutuallyExclusive;
} }
} }

View File

@ -1,10 +1,12 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Utils; namespace GraphQL\Utils;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\Printer; use GraphQL\Language\Printer;
use GraphQL\Type\Introspection; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
@ -12,7 +14,23 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ScalarType; 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\Directive; use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use function array_filter;
use function array_keys;
use function array_map;
use function array_merge;
use function array_values;
use function count;
use function explode;
use function implode;
use function ksort;
use function mb_strlen;
use function preg_match_all;
use function sprintf;
use function str_replace;
use function strlen;
use function substr;
/** /**
* Given an instance of Schema, prints it in GraphQL type language. * Given an instance of Schema, prints it in GraphQL type language.
@ -25,7 +43,7 @@ class SchemaPrinter
* - commentDescriptions: * - commentDescriptions:
* Provide true to use preceding comments as the description. * Provide true to use preceding comments as the description.
* @api * @api
* @param Schema $schema * @param bool[] $options
* @return string * @return string
*/ */
public static function doPrint(Schema $schema, array $options = []) public static function doPrint(Schema $schema, array $options = [])
@ -43,34 +61,44 @@ class SchemaPrinter
} }
/** /**
* @api * @param bool[] $options
* @param Schema $schema
* @return string
*/ */
public static function printIntrosepctionSchema(Schema $schema, array $options = [])
{
return self::printFilteredSchema(
$schema,
[Directive::class, 'isSpecifiedDirective'],
[Introspection::class, 'isIntrospectionType'],
$options
);
}
private static function printFilteredSchema(Schema $schema, $directiveFilter, $typeFilter, $options) private static function printFilteredSchema(Schema $schema, $directiveFilter, $typeFilter, $options)
{ {
$directives = array_filter($schema->getDirectives(), function($directive) use ($directiveFilter) { $directives = array_filter(
$schema->getDirectives(),
function ($directive) use ($directiveFilter) {
return $directiveFilter($directive); return $directiveFilter($directive);
}); }
);
$types = $schema->getTypeMap(); $types = $schema->getTypeMap();
ksort($types); ksort($types);
$types = array_filter($types, $typeFilter); $types = array_filter($types, $typeFilter);
return implode("\n\n", array_filter(array_merge( return sprintf(
"%s\n",
implode(
"\n\n",
array_filter(
array_merge(
[self::printSchemaDefinition($schema)], [self::printSchemaDefinition($schema)],
array_map(function($directive) use ($options) { return self::printDirective($directive, $options); }, $directives), array_map(
array_map(function($type) use ($options) { return self::printType($type, $options); }, $types) function ($directive) use ($options) {
))) . "\n"; return self::printDirective($directive, $options);
},
$directives
),
array_map(
function ($type) use ($options) {
return self::printType($type, $options);
},
$types
)
)
)
)
);
} }
private static function printSchemaDefinition(Schema $schema) private static function printSchemaDefinition(Schema $schema)
@ -83,20 +111,20 @@ class SchemaPrinter
$queryType = $schema->getQueryType(); $queryType = $schema->getQueryType();
if ($queryType) { if ($queryType) {
$operationTypes[] = " query: {$queryType->name}"; $operationTypes[] = sprintf(' query: %s', $queryType->name);
} }
$mutationType = $schema->getMutationType(); $mutationType = $schema->getMutationType();
if ($mutationType) { if ($mutationType) {
$operationTypes[] = " mutation: {$mutationType->name}"; $operationTypes[] = sprintf(' mutation: %s', $mutationType->name);
} }
$subscriptionType = $schema->getSubscriptionType(); $subscriptionType = $schema->getSubscriptionType();
if ($subscriptionType) { if ($subscriptionType) {
$operationTypes[] = " subscription: {$subscriptionType->name}"; $operationTypes[] = sprintf(' subscription: %s', $subscriptionType->name);
} }
return "schema {\n" . implode("\n", $operationTypes) . "\n}"; return sprintf("schema {\n%s\n}", implode("\n", $operationTypes));
} }
/** /**
@ -131,120 +159,6 @@ class SchemaPrinter
return true; return true;
} }
public static function printType(Type $type, array $options = [])
{
if ($type instanceof ScalarType) {
return self::printScalar($type, $options);
} else if ($type instanceof ObjectType) {
return self::printObject($type, $options);
} else if ($type instanceof InterfaceType) {
return self::printInterface($type, $options);
} else if ($type instanceof UnionType) {
return self::printUnion($type, $options);
} else if ($type instanceof EnumType) {
return self::printEnum($type, $options);
} else if ($type instanceof InputObjectType) {
return self::printInputObject($type, $options);
}
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
}
private static function printScalar(ScalarType $type, array $options)
{
return self::printDescription($options, $type) . "scalar {$type->name}";
}
private static function printObject(ObjectType $type, array $options)
{
$interfaces = $type->getInterfaces();
$implementedInterfaces = !empty($interfaces) ?
' implements ' . implode(', ', array_map(function($i) {
return $i->name;
}, $interfaces)) : '';
return self::printDescription($options, $type) .
"type {$type->name}$implementedInterfaces {\n" .
self::printFields($options, $type) . "\n" .
"}";
}
private static function printInterface(InterfaceType $type, array $options)
{
return self::printDescription($options, $type) .
"interface {$type->name} {\n" .
self::printFields($options, $type) . "\n" .
"}";
}
private static function printUnion(UnionType $type, array $options)
{
return self::printDescription($options, $type) .
"union {$type->name} = " . implode(" | ", $type->getTypes());
}
private static function printEnum(EnumType $type, array $options)
{
return self::printDescription($options, $type) .
"enum {$type->name} {\n" .
self::printEnumValues($type->getValues(), $options) . "\n" .
"}";
}
private static function printEnumValues($values, $options)
{
return implode("\n", array_map(function($value, $i) use ($options) {
return self::printDescription($options, $value, ' ', !$i) . ' ' .
$value->name . self::printDeprecated($value);
}, $values, array_keys($values)));
}
private static function printInputObject(InputObjectType $type, array $options)
{
$fields = array_values($type->getFields());
return self::printDescription($options, $type) .
"input {$type->name} {\n" .
implode("\n", array_map(function($f, $i) use ($options) {
return self::printDescription($options, $f, ' ', !$i) . ' ' . self::printInputValue($f);
}, $fields, array_keys($fields))) . "\n" .
"}";
}
private static function printFields($options, $type)
{
$fields = array_values($type->getFields());
return implode("\n", array_map(function($f, $i) use ($options) {
return self::printDescription($options, $f, ' ', !$i) . ' ' .
$f->name . self::printArgs($options, $f->args, ' ') . ': ' .
(string) $f->getType() . self::printDeprecated($f);
}, $fields, array_keys($fields)));
}
private static function printArgs($options, $args, $indentation = '')
{
if (!$args) {
return '';
}
// If every arg does not have a description, print them on one line.
if (Utils::every($args, function($arg) { return empty($arg->description); })) {
return '(' . implode(', ', array_map('self::printInputValue', $args)) . ')';
}
return "(\n" . implode("\n", array_map(function($arg, $i) use ($indentation, $options) {
return self::printDescription($options, $arg, ' ' . $indentation, !$i) . ' ' . $indentation .
self::printInputValue($arg);
}, $args, array_keys($args))) . "\n" . $indentation . ')';
}
private static function printInputValue($arg)
{
$argDecl = $arg->name . ': ' . (string) $arg->getType();
if ($arg->defaultValueExists()) {
$argDecl .= ' = ' . Printer::doPrint(AST::astFromValue($arg->defaultValue, $arg->getType()));
}
return $argDecl;
}
private static function printDirective($directive, $options) private static function printDirective($directive, $options)
{ {
return self::printDescription($options, $directive) . return self::printDescription($options, $directive) .
@ -252,19 +166,6 @@ class SchemaPrinter
' on ' . implode(' | ', $directive->locations); ' on ' . implode(' | ', $directive->locations);
} }
private static function printDeprecated($fieldOrEnumVal)
{
$reason = $fieldOrEnumVal->deprecationReason;
if (empty($reason)) {
return '';
}
if ($reason === '' || $reason === Directive::DEFAULT_DEPRECATION_REASON) {
return ' @deprecated';
}
return ' @deprecated(reason: ' .
Printer::doPrint(AST::astFromValue($reason, Type::string())) . ')';
}
private static function printDescription($options, $def, $indentation = '', $firstInBlock = true) private static function printDescription($options, $def, $indentation = '', $firstInBlock = true)
{ {
if (! $def->description) { if (! $def->description) {
@ -280,8 +181,7 @@ class SchemaPrinter
: $indentation . '"""'; : $indentation . '"""';
// In some circumstances, a single line can be used for the description. // In some circumstances, a single line can be used for the description.
if ( if (count($lines) === 1 &&
count($lines) === 1 &&
mb_strlen($lines[0]) < 70 && mb_strlen($lines[0]) < 70 &&
substr($lines[0], -1) !== '"' substr($lines[0], -1) !== '"'
) { ) {
@ -310,9 +210,35 @@ class SchemaPrinter
return $description; return $description;
} }
private static function escapeQuote($line) private static function descriptionLines($description, $maxLen)
{ {
return str_replace('"""', '\\"""', $line); $lines = [];
$rawLines = explode("\n", $description);
foreach ($rawLines as $line) {
if ($line === '') {
$lines[] = $line;
} else {
// For > 120 character long lines, cut at space boundaries into sublines
// of ~80 chars.
$sublines = self::breakLine($line, $maxLen);
foreach ($sublines as $subline) {
$lines[] = $subline;
}
}
}
return $lines;
}
private static function breakLine($line, $maxLen)
{
if (strlen($line) < $maxLen + 5) {
return [$line];
}
preg_match_all('/((?: |^).{15,' . ($maxLen - 40) . '}(?= |$))/', $line, $parts);
$parts = $parts[0];
return array_map('trim', $parts);
} }
private static function printDescriptionWithComments($lines, $indentation, $firstInBlock) private static function printDescriptionWithComments($lines, $indentation, $firstInBlock)
@ -329,33 +255,231 @@ class SchemaPrinter
return $description; return $description;
} }
private static function descriptionLines($description, $maxLen) { private static function escapeQuote($line)
$lines = []; {
$rawLines = explode("\n", $description); return str_replace('"""', '\\"""', $line);
foreach($rawLines as $line) {
if ($line === '') {
$lines[] = $line;
} else {
// For > 120 character long lines, cut at space boundaries into sublines
// of ~80 chars.
$sublines = self::breakLine($line, $maxLen);
foreach ($sublines as $subline) {
$lines[] = $subline;
}
}
}
return $lines;
} }
private static function breakLine($line, $maxLen) private static function printArgs($options, $args, $indentation = '')
{ {
if (strlen($line) < $maxLen + 5) { if (! $args) {
return [$line]; return '';
} }
preg_match_all("/((?: |^).{15," . ($maxLen - 40) . "}(?= |$))/", $line, $parts);
$parts = $parts[0]; // If every arg does not have a description, print them on one line.
return array_map(function($part) { if (Utils::every(
return trim($part); $args,
}, $parts); function ($arg) {
return empty($arg->description);
}
)) {
return '(' . implode(', ', array_map('self::printInputValue', $args)) . ')';
}
return sprintf(
"(\n%s\n%s)",
implode(
"\n",
array_map(
function ($arg, $i) use ($indentation, $options) {
return self::printDescription($options, $arg, ' ' . $indentation, ! $i) . ' ' . $indentation .
self::printInputValue($arg);
},
$args,
array_keys($args)
)
),
$indentation
);
}
private static function printInputValue($arg)
{
$argDecl = $arg->name . ': ' . (string) $arg->getType();
if ($arg->defaultValueExists()) {
$argDecl .= ' = ' . Printer::doPrint(AST::astFromValue($arg->defaultValue, $arg->getType()));
}
return $argDecl;
}
/**
* @param bool[] $options
*/
public static function printType(Type $type, array $options = [])
{
if ($type instanceof ScalarType) {
return self::printScalar($type, $options);
}
if ($type instanceof ObjectType) {
return self::printObject($type, $options);
}
if ($type instanceof InterfaceType) {
return self::printInterface($type, $options);
}
if ($type instanceof UnionType) {
return self::printUnion($type, $options);
}
if ($type instanceof EnumType) {
return self::printEnum($type, $options);
}
if ($type instanceof InputObjectType) {
return self::printInputObject($type, $options);
}
throw new Error(sprintf('Unknown type: %s.', Utils::printSafe($type)));
}
/**
* @param bool[] $options
*/
private static function printScalar(ScalarType $type, array $options)
{
return sprintf('%sscalar %s', self::printDescription($options, $type), $type->name);
}
/**
* @param bool[] $options
*/
private static function printObject(ObjectType $type, array $options)
{
$interfaces = $type->getInterfaces();
$implementedInterfaces = ! empty($interfaces) ?
' implements ' . implode(
', ',
array_map(
function ($i) {
return $i->name;
},
$interfaces
)
) : '';
return self::printDescription($options, $type) .
sprintf("type %s%s {\n%s\n}", $type->name, $implementedInterfaces, self::printFields($options, $type));
}
/**
* @param bool[] $options
*/
private static function printFields($options, $type)
{
$fields = array_values($type->getFields());
return implode(
"\n",
array_map(
function ($f, $i) use ($options) {
return self::printDescription($options, $f, ' ', ! $i) . ' ' .
$f->name . self::printArgs($options, $f->args, ' ') . ': ' .
(string) $f->getType() . self::printDeprecated($f);
},
$fields,
array_keys($fields)
)
);
}
private static function printDeprecated($fieldOrEnumVal)
{
$reason = $fieldOrEnumVal->deprecationReason;
if (empty($reason)) {
return '';
}
if ($reason === '' || $reason === Directive::DEFAULT_DEPRECATION_REASON) {
return ' @deprecated';
}
return ' @deprecated(reason: ' .
Printer::doPrint(AST::astFromValue($reason, Type::string())) . ')';
}
/**
* @param bool[] $options
*/
private static function printInterface(InterfaceType $type, array $options)
{
return self::printDescription($options, $type) .
sprintf("interface %s {\n%s\n}", $type->name, self::printFields($options, $type));
}
/**
* @param bool[] $options
*/
private static function printUnion(UnionType $type, array $options)
{
return self::printDescription($options, $type) .
sprintf('union %s = %s', $type->name, implode(' | ', $type->getTypes()));
}
/**
* @param bool[] $options
*/
private static function printEnum(EnumType $type, array $options)
{
return self::printDescription($options, $type) .
sprintf("enum %s {\n%s\n}", $type->name, self::printEnumValues($type->getValues(), $options));
}
/**
* @param bool[] $options
*/
private static function printEnumValues($values, $options)
{
return implode(
"\n",
array_map(
function ($value, $i) use ($options) {
return self::printDescription($options, $value, ' ', ! $i) . ' ' .
$value->name . self::printDeprecated($value);
},
$values,
array_keys($values)
)
);
}
/**
* @param bool[] $options
*/
private static function printInputObject(InputObjectType $type, array $options)
{
$fields = array_values($type->getFields());
return self::printDescription($options, $type) .
sprintf(
"input %s {\n%s\n}",
$type->name,
implode(
"\n",
array_map(
function ($f, $i) use ($options) {
return self::printDescription($options, $f, ' ', ! $i) . ' ' . self::printInputValue($f);
},
$fields,
array_keys($fields)
)
)
);
}
/**
* @api
* @param bool[] $options
* @return string
*/
public static function printIntrosepctionSchema(Schema $schema, array $options = [])
{
return self::printFilteredSchema(
$schema,
[Directive::class, 'isSpecifiedDirective'],
[Introspection::class, 'isIntrospectionType'],
$options
);
} }
} }

View File

@ -1,21 +1,22 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Utils; namespace GraphQL\Utils;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\AbstractType; use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\CompositeType; use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
class TypeComparators class TypeComparators
{ {
/** /**
* Provided two types, return true if the types are equal (invariant). * Provided two types, return true if the types are equal (invariant).
* *
* @param Type $typeA
* @param Type $typeB
* @return bool * @return bool
*/ */
public static function isEqualType(Type $typeA, Type $typeB) public static function isEqualType(Type $typeA, Type $typeB)
@ -43,12 +44,11 @@ class TypeComparators
* Provided a type and a super type, return true if the first type is either * Provided a type and a super type, return true if the first type is either
* equal or a subset of the second super type (covariant). * equal or a subset of the second super type (covariant).
* *
* @param Schema $schema * @param AbstractType $maybeSubType
* @param Type $maybeSubType * @param AbstractType $superType
* @param Type $superType
* @return bool * @return bool
*/ */
static function isTypeSubTypeOf(Schema $schema, $maybeSubType, $superType) public static function isTypeSubTypeOf(Schema $schema, $maybeSubType, $superType)
{ {
// Equivalent type is a valid subtype // Equivalent type is a valid subtype
if ($maybeSubType === $superType) { if ($maybeSubType === $superType) {
@ -60,8 +60,11 @@ class TypeComparators
if ($maybeSubType instanceof NonNull) { if ($maybeSubType instanceof NonNull) {
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType()); return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType());
} }
return false; return false;
} else if ($maybeSubType instanceof NonNull) { }
if ($maybeSubType instanceof NonNull) {
// If superType is nullable, maybeSubType may be non-null. // If superType is nullable, maybeSubType may be non-null.
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType); return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType);
} }
@ -71,15 +74,24 @@ class TypeComparators
if ($maybeSubType instanceof ListOfType) { if ($maybeSubType instanceof ListOfType) {
return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType()); return self::isTypeSubTypeOf($schema, $maybeSubType->getWrappedType(), $superType->getWrappedType());
} }
return false; return false;
} else if ($maybeSubType instanceof ListOfType) { }
if ($maybeSubType instanceof ListOfType) {
// If superType is not a list, maybeSubType must also be not a list. // If superType is not a list, maybeSubType must also be not a list.
return false; return false;
} }
// If superType type is an abstract type, maybeSubType type may be a currently // If superType type is an abstract type, maybeSubType type may be a currently
// possible object type. // possible object type.
if (Type::isAbstractType($superType) && $maybeSubType instanceof ObjectType && $schema->isPossibleType($superType, $maybeSubType)) { if (Type::isAbstractType($superType) &&
$maybeSubType instanceof ObjectType &&
$schema->isPossibleType(
$superType,
$maybeSubType
)
) {
return true; return true;
} }
@ -96,12 +108,9 @@ class TypeComparators
* *
* This function is commutative. * This function is commutative.
* *
* @param Schema $schema
* @param CompositeType $typeA
* @param CompositeType $typeB
* @return bool * @return bool
*/ */
static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB) public static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB)
{ {
// Equivalent types overlap // Equivalent types overlap
if ($typeA === $typeB) { if ($typeA === $typeB) {
@ -117,6 +126,7 @@ class TypeComparators
return true; return true;
} }
} }
return false; return false;
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Utils; namespace GraphQL\Utils;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
@ -9,7 +12,6 @@ use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NonNullTypeNode; use GraphQL\Language\AST\NonNullTypeNode;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\CompositeType; use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
@ -24,13 +26,70 @@ use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Definition\WrappingType; use GraphQL\Type\Definition\WrappingType;
use GraphQL\Type\Introspection; use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use function array_map;
use function array_merge;
use function array_pop;
use function count;
use function sprintf;
/** /**
* Class TypeInfo * Class TypeInfo
* @package GraphQL\Utils
*/ */
class TypeInfo class TypeInfo
{ {
/** @var Schema */
private $schema;
/** @var \SplStack<OutputType> */
private $typeStack;
/** @var \SplStack<CompositeType> */
private $parentTypeStack;
/** @var \SplStack<InputType> */
private $inputTypeStack;
/** @var \SplStack<FieldDefinition> */
private $fieldDefStack;
/** @var Directive */
private $directive;
/** @var FieldArgument */
private $argument;
/** @var mixed */
private $enumValue;
/**
*
* @param Type|null $initialType
*/
public function __construct(Schema $schema, $initialType = null)
{
$this->schema = $schema;
$this->typeStack = [];
$this->parentTypeStack = [];
$this->inputTypeStack = [];
$this->fieldDefStack = [];
if (! $initialType) {
return;
}
if (Type::isInputType($initialType)) {
$this->inputTypeStack[] = $initialType;
}
if (Type::isCompositeType($initialType)) {
$this->parentTypeStack[] = $initialType;
}
if (! Type::isOutputType($initialType)) {
return;
}
$this->typeStack[] = $initialType;
}
/** /**
* @deprecated moved to GraphQL\Utils\TypeComparators * @deprecated moved to GraphQL\Utils\TypeComparators
*/ */
@ -42,7 +101,7 @@ class TypeInfo
/** /**
* @deprecated moved to GraphQL\Utils\TypeComparators * @deprecated moved to GraphQL\Utils\TypeComparators
*/ */
static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type $superType) public static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type $superType)
{ {
return TypeComparators::isTypeSubTypeOf($schema, $maybeSubType, $superType); return TypeComparators::isTypeSubTypeOf($schema, $maybeSubType, $superType);
} }
@ -50,22 +109,11 @@ class TypeInfo
/** /**
* @deprecated moved to GraphQL\Utils\TypeComparators * @deprecated moved to GraphQL\Utils\TypeComparators
*/ */
static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB) public static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB)
{ {
return TypeComparators::doTypesOverlap($schema, $typeA, $typeB); return TypeComparators::doTypesOverlap($schema, $typeA, $typeB);
} }
/**
* @param Schema $schema
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
* @return Type|null
* @throws InvariantViolation
*/
public static function typeFromAST(Schema $schema, $inputTypeNode)
{
return AST::typeFromAST($schema, $inputTypeNode);
}
/** /**
* Given root type scans through all fields to find nested types. Returns array where keys are for type name * Given root type scans through all fields to find nested types. Returns array where keys are for type name
* and value contains corresponding type instance. * and value contains corresponding type instance.
@ -77,11 +125,11 @@ class TypeInfo
* ... * ...
* ] * ]
* *
* @param Type $type * @param Type|null $type
* @param array|null $typeMap * @param Type[]|null $typeMap
* @return array * @return Type[]|null
*/ */
public static function extractTypes($type, array $typeMap = null) public static function extractTypes($type, ?array $typeMap = null)
{ {
if (! $typeMap) { if (! $typeMap) {
$typeMap = []; $typeMap = [];
@ -99,15 +147,17 @@ class TypeInfo
'Try running $schema->assertValid() to find out the cause of this warning.', 'Try running $schema->assertValid() to find out the cause of this warning.',
Warning::WARNING_NOT_A_TYPE Warning::WARNING_NOT_A_TYPE
); );
return $typeMap; return $typeMap;
} }
if (! empty($typeMap[$type->name])) { if (! empty($typeMap[$type->name])) {
Utils::invariant( Utils::invariant(
$typeMap[$type->name] === $type, $typeMap[$type->name] === $type,
"Schema must contain unique named types but contains multiple types named \"$type\" ". sprintf('Schema must contain unique named types but contains multiple types named "%s" ', $type) .
"(see http://webonyx.github.io/graphql-php/type-system/#type-registry)." '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).'
); );
return $typeMap; return $typeMap;
} }
$typeMap[$type->name] = $type; $typeMap[$type->name] = $type;
@ -123,7 +173,13 @@ class TypeInfo
if ($type instanceof ObjectType || $type instanceof InterfaceType) { if ($type instanceof ObjectType || $type instanceof InterfaceType) {
foreach ((array) $type->getFields() as $fieldName => $field) { foreach ((array) $type->getFields() as $fieldName => $field) {
if (! empty($field->args)) { if (! empty($field->args)) {
$fieldArgTypes = array_map(function(FieldArgument $arg) { return $arg->getType(); }, $field->args); $fieldArgTypes = array_map(
function (FieldArgument $arg) {
return $arg->getType();
},
$field->args
);
$nestedTypes = array_merge($nestedTypes, $fieldArgTypes); $nestedTypes = array_merge($nestedTypes, $fieldArgTypes);
} }
$nestedTypes[] = $field->getType(); $nestedTypes[] = $field->getType();
@ -137,139 +193,10 @@ class TypeInfo
foreach ($nestedTypes as $type) { foreach ($nestedTypes as $type) {
$typeMap = self::extractTypes($type, $typeMap); $typeMap = self::extractTypes($type, $typeMap);
} }
return $typeMap; return $typeMap;
} }
/**
* Not exactly the same as the executor's definition of getFieldDef, in this
* statically evaluated environment we do not always have an Object type,
* and need to handle Interface and Union types.
*
* @return FieldDefinition
*/
static private function getFieldDefinition(Schema $schema, Type $parentType, FieldNode $fieldNode)
{
$name = $fieldNode->name->value;
$schemaMeta = Introspection::schemaMetaFieldDef();
if ($name === $schemaMeta->name && $schema->getQueryType() === $parentType) {
return $schemaMeta;
}
$typeMeta = Introspection::typeMetaFieldDef();
if ($name === $typeMeta->name && $schema->getQueryType() === $parentType) {
return $typeMeta;
}
$typeNameMeta = Introspection::typeNameMetaFieldDef();
if ($name === $typeNameMeta->name && $parentType instanceof CompositeType) {
return $typeNameMeta;
}
if ($parentType instanceof ObjectType ||
$parentType instanceof InterfaceType) {
$fields = $parentType->getFields();
return isset($fields[$name]) ? $fields[$name] : null;
}
return null;
}
/**
* @var Schema
*/
private $schema;
/**
* @var \SplStack<OutputType>
*/
private $typeStack;
/**
* @var \SplStack<CompositeType>
*/
private $parentTypeStack;
/**
* @var \SplStack<InputType>
*/
private $inputTypeStack;
/**
* @var \SplStack<FieldDefinition>
*/
private $fieldDefStack;
/**
* @var Directive
*/
private $directive;
/**
* @var FieldArgument
*/
private $argument;
/**
* @var mixed
*/
private $enumValue;
/**
* TypeInfo constructor.
* @param Schema $schema
* @param Type|null $initialType
*/
public function __construct(Schema $schema, $initialType = null)
{
$this->schema = $schema;
$this->typeStack = [];
$this->parentTypeStack = [];
$this->inputTypeStack = [];
$this->fieldDefStack = [];
if ($initialType) {
if (Type::isInputType($initialType)) {
$this->inputTypeStack[] = $initialType;
}
if (Type::isCompositeType($initialType)) {
$this->parentTypeStack[] = $initialType;
}
if (Type::isOutputType($initialType)) {
$this->typeStack[] = $initialType;
}
}
}
/**
* @return Type
*/
function getType()
{
if (!empty($this->typeStack)) {
return $this->typeStack[count($this->typeStack) - 1];
}
return null;
}
/**
* @return Type
*/
function getParentType()
{
if (!empty($this->parentTypeStack)) {
return $this->parentTypeStack[count($this->parentTypeStack) - 1];
}
return null;
}
/**
* @return InputType
*/
function getInputType()
{
if (!empty($this->inputTypeStack)) {
return $this->inputTypeStack[count($this->inputTypeStack) - 1];
}
return null;
}
/** /**
* @return InputType|null * @return InputType|null
*/ */
@ -281,29 +208,10 @@ class TypeInfo
} }
} }
/**
* @return FieldDefinition
*/
function getFieldDef()
{
if (!empty($this->fieldDefStack)) {
return $this->fieldDefStack[count($this->fieldDefStack) - 1];
}
return null;
}
/**
* @return Directive|null
*/
function getDirective()
{
return $this->directive;
}
/** /**
* @return FieldArgument|null * @return FieldArgument|null
*/ */
function getArgument() public function getArgument()
{ {
return $this->argument; return $this->argument;
} }
@ -311,15 +219,12 @@ class TypeInfo
/** /**
* @return mixed * @return mixed
*/ */
function getEnumValue() public function getEnumValue()
{ {
return $this->enumValue; return $this->enumValue;
} }
/** public function enter(Node $node)
* @param Node $node
*/
function enter(Node $node)
{ {
$schema = $this->schema; $schema = $this->schema;
@ -366,7 +271,10 @@ class TypeInfo
case NodeKind::INLINE_FRAGMENT: case NodeKind::INLINE_FRAGMENT:
case NodeKind::FRAGMENT_DEFINITION: case NodeKind::FRAGMENT_DEFINITION:
$typeConditionNode = $node->typeCondition; $typeConditionNode = $node->typeCondition;
$outputType = $typeConditionNode ? self::typeFromAST($schema, $typeConditionNode) : Type::getNamedType($this->getType()); $outputType = $typeConditionNode ? self::typeFromAST(
$schema,
$typeConditionNode
) : Type::getNamedType($this->getType());
$this->typeStack[] = Type::isOutputType($outputType) ? $outputType : null; $this->typeStack[] = Type::isOutputType($outputType) ? $outputType : null;
break; break;
@ -379,7 +287,12 @@ class TypeInfo
$fieldOrDirective = $this->getDirective() ?: $this->getFieldDef(); $fieldOrDirective = $this->getDirective() ?: $this->getFieldDef();
$argDef = $argType = null; $argDef = $argType = null;
if ($fieldOrDirective) { if ($fieldOrDirective) {
$argDef = Utils::find($fieldOrDirective->args, function($arg) use ($node) {return $arg->name === $node->name->value;}); $argDef = Utils::find(
$fieldOrDirective->args,
function ($arg) use ($node) {
return $arg->name === $node->name->value;
}
);
if ($argDef) { if ($argDef) {
$argType = $argDef->getType(); $argType = $argDef->getType();
} }
@ -402,7 +315,7 @@ class TypeInfo
$inputFieldType = null; $inputFieldType = null;
if ($objectType instanceof InputObjectType) { if ($objectType instanceof InputObjectType) {
$tmp = $objectType->getFields(); $tmp = $objectType->getFields();
$inputField = isset($tmp[$node->name->value]) ? $tmp[$node->name->value] : null; $inputField = $tmp[$node->name->value] ?? null;
$inputFieldType = $inputField ? $inputField->getType() : null; $inputFieldType = $inputField ? $inputField->getType() : null;
} }
$this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null; $this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null;
@ -420,9 +333,105 @@ class TypeInfo
} }
/** /**
* @param Node $node * @return Type
*/ */
function leave(Node $node) public function getType()
{
if (! empty($this->typeStack)) {
return $this->typeStack[count($this->typeStack) - 1];
}
return null;
}
/**
* @return Type
*/
public function getParentType()
{
if (! empty($this->parentTypeStack)) {
return $this->parentTypeStack[count($this->parentTypeStack) - 1];
}
return null;
}
/**
* Not exactly the same as the executor's definition of getFieldDef, in this
* statically evaluated environment we do not always have an Object type,
* and need to handle Interface and Union types.
*
* @return FieldDefinition
*/
private static function getFieldDefinition(Schema $schema, Type $parentType, FieldNode $fieldNode)
{
$name = $fieldNode->name->value;
$schemaMeta = Introspection::schemaMetaFieldDef();
if ($name === $schemaMeta->name && $schema->getQueryType() === $parentType) {
return $schemaMeta;
}
$typeMeta = Introspection::typeMetaFieldDef();
if ($name === $typeMeta->name && $schema->getQueryType() === $parentType) {
return $typeMeta;
}
$typeNameMeta = Introspection::typeNameMetaFieldDef();
if ($name === $typeNameMeta->name && $parentType instanceof CompositeType) {
return $typeNameMeta;
}
if ($parentType instanceof ObjectType ||
$parentType instanceof InterfaceType) {
$fields = $parentType->getFields();
return $fields[$name] ?? null;
}
return null;
}
/**
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
* @return Type|null
* @throws InvariantViolation
*/
public static function typeFromAST(Schema $schema, $inputTypeNode)
{
return AST::typeFromAST($schema, $inputTypeNode);
}
/**
* @return Directive|null
*/
public function getDirective()
{
return $this->directive;
}
/**
* @return FieldDefinition
*/
public function getFieldDef()
{
if (! empty($this->fieldDefStack)) {
return $this->fieldDefStack[count($this->fieldDefStack) - 1];
}
return null;
}
/**
* @return InputType
*/
public function getInputType()
{
if (! empty($this->inputTypeStack)) {
return $this->inputTypeStack[count($this->inputTypeStack) - 1];
}
return null;
}
public function leave(Node $node)
{ {
switch ($node->kind) { switch ($node->kind) {
case NodeKind::SELECTION_SET: case NodeKind::SELECTION_SET:

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Utils; namespace GraphQL\Utils;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -7,13 +10,51 @@ use GraphQL\Error\Warning;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\WrappingType; use GraphQL\Type\Definition\WrappingType;
use \Traversable, \InvalidArgumentException; use InvalidArgumentException;
use Traversable;
use function array_keys;
use function array_map;
use function array_reduce;
use function array_shift;
use function array_slice;
use function array_values;
use function asort;
use function chr;
use function count;
use function dechex;
use function func_get_args;
use function func_num_args;
use function get_class;
use function gettype;
use function is_array;
use function is_int;
use function is_object;
use function is_scalar;
use function is_string;
use function json_encode;
use function levenshtein;
use function max;
use function mb_convert_encoding;
use function mb_strlen;
use function mb_substr;
use function method_exists;
use function ord;
use function pack;
use function preg_match;
use function property_exists;
use function range;
use function restore_error_handler;
use function set_error_handler;
use function sprintf;
use function strtolower;
use function unpack;
class Utils class Utils
{ {
public static function undefined() public static function undefined()
{ {
static $undefined; static $undefined;
return $undefined ?: $undefined = new \stdClass(); return $undefined ?: $undefined = new \stdClass();
} }
@ -30,8 +71,8 @@ class Utils
/** /**
* @param object $obj * @param object $obj
* @param array $vars * @param mixed[] $vars
* @param array $requiredKeys * @param string[] $requiredKeys
* *
* @return object * @return object
*/ */
@ -39,7 +80,7 @@ class Utils
{ {
foreach ($requiredKeys as $key) { foreach ($requiredKeys as $key) {
if (! isset($vars[$key])) { if (! isset($vars[$key])) {
throw new InvalidArgumentException("Key {$key} is expected to be set and not to be null"); throw new InvalidArgumentException(sprintf('Key %s is expected to be set and not to be null', $key));
} }
} }
@ -47,41 +88,47 @@ class Utils
if (! property_exists($obj, $key)) { if (! property_exists($obj, $key)) {
$cls = get_class($obj); $cls = get_class($obj);
Warning::warn( Warning::warn(
"Trying to set non-existing property '$key' on class '$cls'", sprintf("Trying to set non-existing property '%s' on class '%s'", $key, $cls),
Warning::WARNING_ASSIGN Warning::WARNING_ASSIGN
); );
} }
$obj->{$key} = $value; $obj->{$key} = $value;
} }
return $obj; return $obj;
} }
/** /**
* @param array|Traversable $traversable * @param mixed|Traversable $traversable
* @param callable $predicate
* @return mixed|null * @return mixed|null
*/ */
public static function find($traversable, callable $predicate) public static function find($traversable, callable $predicate)
{ {
self::invariant(is_array($traversable) || $traversable instanceof \Traversable, __METHOD__ . ' expects array or Traversable'); self::invariant(
is_array($traversable) || $traversable instanceof \Traversable,
__METHOD__ . ' expects array or Traversable'
);
foreach ($traversable as $key => $value) { foreach ($traversable as $key => $value) {
if ($predicate($value, $key)) { if ($predicate($value, $key)) {
return $value; return $value;
} }
} }
return null; return null;
} }
/** /**
* @param $traversable * @param mixed|Traversable $traversable
* @param callable $predicate * @return mixed[]
* @return array
* @throws \Exception * @throws \Exception
*/ */
public static function filter($traversable, callable $predicate) public static function filter($traversable, callable $predicate)
{ {
self::invariant(is_array($traversable) || $traversable instanceof \Traversable, __METHOD__ . ' expects array or Traversable'); self::invariant(
is_array($traversable) || $traversable instanceof \Traversable,
__METHOD__ . ' expects array or Traversable'
);
$result = []; $result = [];
$assoc = false; $assoc = false;
@ -89,76 +136,88 @@ class Utils
if (! $assoc && ! is_int($key)) { if (! $assoc && ! is_int($key)) {
$assoc = true; $assoc = true;
} }
if ($predicate($value, $key)) { if (! $predicate($value, $key)) {
$result[$key] = $value; continue;
} }
$result[$key] = $value;
} }
return $assoc ? $result : array_values($result); return $assoc ? $result : array_values($result);
} }
/** /**
* @param array|\Traversable $traversable * @param mixed|\Traversable $traversable
* @param callable $fn function($value, $key) => $newValue * @return int[][]
* @return array
* @throws \Exception * @throws \Exception
*/ */
public static function map($traversable, callable $fn) public static function map($traversable, callable $fn)
{ {
self::invariant(is_array($traversable) || $traversable instanceof \Traversable, __METHOD__ . ' expects array or Traversable'); self::invariant(
is_array($traversable) || $traversable instanceof \Traversable,
__METHOD__ . ' expects array or Traversable'
);
$map = []; $map = [];
foreach ($traversable as $key => $value) { foreach ($traversable as $key => $value) {
$map[$key] = $fn($value, $key); $map[$key] = $fn($value, $key);
} }
return $map; return $map;
} }
/** /**
* @param $traversable * @param mixed|Traversable $traversable
* @param callable $fn * @return mixed[]
* @return array
* @throws \Exception * @throws \Exception
*/ */
public static function mapKeyValue($traversable, callable $fn) public static function mapKeyValue($traversable, callable $fn)
{ {
self::invariant(is_array($traversable) || $traversable instanceof \Traversable, __METHOD__ . ' expects array or Traversable'); self::invariant(
is_array($traversable) || $traversable instanceof \Traversable,
__METHOD__ . ' expects array or Traversable'
);
$map = []; $map = [];
foreach ($traversable as $key => $value) { foreach ($traversable as $key => $value) {
list($newKey, $newValue) = $fn($value, $key); list($newKey, $newValue) = $fn($value, $key);
$map[$newKey] = $newValue; $map[$newKey] = $newValue;
} }
return $map; return $map;
} }
/** /**
* @param $traversable * @param mixed|Traversable $traversable
* @param callable $keyFn function($value, $key) => $newKey * @return mixed[]
* @return array
* @throws \Exception * @throws \Exception
*/ */
public static function keyMap($traversable, callable $keyFn) public static function keyMap($traversable, callable $keyFn)
{ {
self::invariant(is_array($traversable) || $traversable instanceof \Traversable, __METHOD__ . ' expects array or Traversable'); self::invariant(
is_array($traversable) || $traversable instanceof \Traversable,
__METHOD__ . ' expects array or Traversable'
);
$map = []; $map = [];
foreach ($traversable as $key => $value) { foreach ($traversable as $key => $value) {
$newKey = $keyFn($value, $key); $newKey = $keyFn($value, $key);
if (is_scalar($newKey)) { if (! is_scalar($newKey)) {
continue;
}
$map[$newKey] = $value; $map[$newKey] = $value;
} }
}
return $map; return $map;
} }
/**
* @param $traversable
* @param callable $fn
*/
public static function each($traversable, callable $fn) public static function each($traversable, callable $fn)
{ {
self::invariant(is_array($traversable) || $traversable instanceof \Traversable, __METHOD__ . ' expects array or Traversable'); self::invariant(
is_array($traversable) || $traversable instanceof \Traversable,
__METHOD__ . ' expects array or Traversable'
);
foreach ($traversable as $key => $item) { foreach ($traversable as $key => $item) {
$fn($item, $key); $fn($item, $key);
@ -177,13 +236,15 @@ class Utils
* *
* $keyFn is also allowed to return array of keys. Then value will be added to all arrays with given keys * $keyFn is also allowed to return array of keys. Then value will be added to all arrays with given keys
* *
* @param $traversable * @param mixed[]|Traversable $traversable
* @param callable $keyFn function($value, $key) => $newKey(s) * @return mixed[]
* @return array
*/ */
public static function groupBy($traversable, callable $keyFn) public static function groupBy($traversable, callable $keyFn)
{ {
self::invariant(is_array($traversable) || $traversable instanceof \Traversable, __METHOD__ . ' expects array or Traversable'); self::invariant(
is_array($traversable) || $traversable instanceof \Traversable,
__METHOD__ . ' expects array or Traversable'
);
$grouped = []; $grouped = [];
foreach ($traversable as $key => $value) { foreach ($traversable as $key => $value) {
@ -197,10 +258,8 @@ class Utils
} }
/** /**
* @param array|Traversable $traversable * @param mixed[]|Traversable $traversable
* @param callable $keyFn * @return mixed[][]
* @param callable $valFn
* @return array
*/ */
public static function keyValMap($traversable, callable $keyFn, callable $valFn) public static function keyValMap($traversable, callable $keyFn, callable $valFn)
{ {
@ -208,12 +267,12 @@ class Utils
foreach ($traversable as $item) { foreach ($traversable as $item) {
$map[$keyFn($item)] = $valFn($item); $map[$keyFn($item)] = $valFn($item);
} }
return $map; return $map;
} }
/** /**
* @param $traversable * @param mixed[] $traversable
* @param callable $predicate
* @return bool * @return bool
*/ */
public static function every($traversable, callable $predicate) public static function every($traversable, callable $predicate)
@ -223,15 +282,13 @@ class Utils
return false; return false;
} }
} }
return true; return true;
} }
/** /**
* @param $test * @param bool $test
* @param string $message * @param string $message
* @param mixed $sprintfParam1
* @param mixed $sprintfParam2 ...
* @throws Error
*/ */
public static function invariant($test, $message = '') public static function invariant($test, $message = '')
{ {
@ -239,7 +296,7 @@ class Utils
if (func_num_args() > 2) { if (func_num_args() > 2) {
$args = func_get_args(); $args = func_get_args();
array_shift($args); array_shift($args);
$message = call_user_func_array('sprintf', $args); $message = sprintf(...$args);
} }
// TODO switch to Error here // TODO switch to Error here
throw new InvariantViolation($message); throw new InvariantViolation($message);
@ -247,7 +304,7 @@ class Utils
} }
/** /**
* @param $var * @param Type|mixed $var
* @return string * @return string
*/ */
public static function getVariableType($var) public static function getVariableType($var)
@ -257,8 +314,10 @@ class Utils
if ($var instanceof WrappingType) { if ($var instanceof WrappingType) {
$var = $var->getWrappedType(true); $var = $var->getWrappedType(true);
} }
return $var->name; return $var->name;
} }
return is_object($var) ? get_class($var) : gettype($var); return is_object($var) ? get_class($var) : gettype($var);
} }
@ -274,29 +333,30 @@ class Utils
if (is_array($var)) { if (is_array($var)) {
return json_encode($var); return json_encode($var);
} }
if ('' === $var) { if ($var === '') {
return '(empty string)'; return '(empty string)';
} }
if (null === $var) { if ($var === null) {
return 'null'; return 'null';
} }
if (false === $var) { if ($var === false) {
return 'false'; return 'false';
} }
if (true === $var) { if ($var === true) {
return 'true'; return 'true';
} }
if (is_string($var)) { if (is_string($var)) {
return "\"$var\""; return sprintf('"%s"', $var);
} }
if (is_scalar($var)) { if (is_scalar($var)) {
return (string) $var; return (string) $var;
} }
return gettype($var); return gettype($var);
} }
/** /**
* @param $var * @param Type|mixed $var
* @return string * @return string
*/ */
public static function printSafe($var) public static function printSafe($var)
@ -307,23 +367,23 @@ class Utils
if (is_object($var)) { if (is_object($var)) {
if (method_exists($var, '__toString')) { if (method_exists($var, '__toString')) {
return (string) $var; return (string) $var;
} else {
return 'instance of ' . get_class($var);
} }
return 'instance of ' . get_class($var);
} }
if (is_array($var)) { if (is_array($var)) {
return json_encode($var); return json_encode($var);
} }
if ('' === $var) { if ($var === '') {
return '(empty string)'; return '(empty string)';
} }
if (null === $var) { if ($var === null) {
return 'null'; return 'null';
} }
if (false === $var) { if ($var === false) {
return 'false'; return 'false';
} }
if (true === $var) { if ($var === true) {
return 'true'; return 'true';
} }
if (is_string($var)) { if (is_string($var)) {
@ -332,6 +392,7 @@ class Utils
if (is_scalar($var)) { if (is_scalar($var)) {
return (string) $var; return (string) $var;
} }
return gettype($var); return gettype($var);
} }
@ -348,10 +409,10 @@ class Utils
return chr($ord); return chr($ord);
} }
if ($encoding === 'UCS-4BE') { if ($encoding === 'UCS-4BE') {
return pack("N", $ord); return pack('N', $ord);
} else {
return mb_convert_encoding(self::chr($ord, 'UCS-4BE'), $encoding, 'UCS-4BE');
} }
return mb_convert_encoding(self::chr($ord, 'UCS-4BE'), $encoding, 'UCS-4BE');
} }
/** /**
@ -363,7 +424,7 @@ class Utils
*/ */
public static function ord($char, $encoding = 'UTF-8') public static function ord($char, $encoding = 'UTF-8')
{ {
if (!$char && '0' !== $char) { if (! $char && $char !== '0') {
return 0; return 0;
} }
if (! isset($char[1])) { if (! isset($char[1])) {
@ -372,35 +433,37 @@ class Utils
if ($encoding !== 'UCS-4BE') { if ($encoding !== 'UCS-4BE') {
$char = mb_convert_encoding($char, 'UCS-4BE', $encoding); $char = mb_convert_encoding($char, 'UCS-4BE', $encoding);
} }
list(, $ord) = unpack('N', $char);
return $ord; return unpack('N', $char)[1];
} }
/** /**
* Returns UTF-8 char code at given $positing of the $string * Returns UTF-8 char code at given $positing of the $string
* *
* @param $string * @param string $string
* @param $position * @param int $position
* @return mixed * @return mixed
*/ */
public static function charCodeAt($string, $position) public static function charCodeAt($string, $position)
{ {
$char = mb_substr($string, $position, 1, 'UTF-8'); $char = mb_substr($string, $position, 1, 'UTF-8');
return self::ord($char); return self::ord($char);
} }
/** /**
* @param $code * @param int|null $code
* @return string * @return string
*/ */
public static function printCharCode($code) public static function printCharCode($code)
{ {
if (null === $code) { if ($code === null) {
return '<EOF>'; return '<EOF>';
} }
return $code < 0x007F return $code < 0x007F
// Trust JSON for ASCII. // Trust JSON for ASCII.
? json_encode(Utils::chr($code)) ? json_encode(self::chr($code))
// Otherwise print the escaped form. // Otherwise print the escaped form.
: '"\\u' . dechex($code) . '"'; : '"\\u' . dechex($code) . '"';
} }
@ -408,7 +471,7 @@ class Utils
/** /**
* Upholds the spec rules about naming. * Upholds the spec rules about naming.
* *
* @param $name * @param string $name
* @throws Error * @throws Error
*/ */
public static function assertValidName($name) public static function assertValidName($name)
@ -428,19 +491,19 @@ class Utils
*/ */
public static function isValidNameError($name, $node = null) public static function isValidNameError($name, $node = null)
{ {
Utils::invariant(is_string($name), 'Expected string'); self::invariant(is_string($name), 'Expected string');
if (isset($name[1]) && $name[0] === '_' && $name[1] === '_') { if (isset($name[1]) && $name[0] === '_' && $name[1] === '_') {
return new Error( return new Error(
"Name \"{$name}\" must not begin with \"__\", which is reserved by " . sprintf('Name "%s" must not begin with "__", which is reserved by ', $name) .
"GraphQL introspection.", 'GraphQL introspection.',
$node $node
); );
} }
if (! preg_match('/^[_a-zA-Z][_a-zA-Z0-9]*$/', $name)) { if (! preg_match('/^[_a-zA-Z][_a-zA-Z0-9]*$/', $name)) {
return new Error( return new Error(
"Names must match /^[_a-zA-Z][_a-zA-Z0-9]*\$/ but \"{$name}\" does not.", sprintf('Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "%s" does not.', $name),
$node $node
); );
} }
@ -452,7 +515,6 @@ class Utils
* Wraps original closure with PHP error handling (using set_error_handler). * Wraps original closure with PHP error handling (using set_error_handler).
* Resulting closure will collect all PHP errors that occur during the call in $errors array. * Resulting closure will collect all PHP errors that occur during the call in $errors array.
* *
* @param callable $fn
* @param \ErrorException[] $errors * @param \ErrorException[] $errors
* @return \Closure * @return \Closure
*/ */
@ -472,20 +534,29 @@ class Utils
}; };
} }
/** /**
* @param string[] $items * @param string[] $items
* @return string * @return string
*/ */
public static function quotedOrList(array $items) public static function quotedOrList(array $items)
{ {
$items = array_map(function($item) { return "\"$item\""; }, $items); $items = array_map(
function ($item) {
return sprintf('"%s"', $item);
},
$items
);
return self::orList($items); return self::orList($items);
} }
/**
* @param string[] $items
* @return string
*/
public static function orList(array $items) public static function orList(array $items)
{ {
if (!$items) { if (count($items) === 0) {
throw new \LogicException('items must not need to be empty.'); throw new \LogicException('items must not need to be empty.');
} }
$selected = array_slice($items, 0, 5); $selected = array_slice($items, 0, 5);
@ -516,7 +587,7 @@ class Utils
* as a single edit which helps identify mis-cased values with an edit distance * as a single edit which helps identify mis-cased values with an edit distance
* of 1 * of 1
* @param string $input * @param string $input
* @param array $options * @param string[] $options
* @return string[] * @return string[]
*/ */
public static function suggestionList($input, array $options) public static function suggestionList($input, array $options)
@ -524,15 +595,19 @@ class Utils
$optionsByDistance = []; $optionsByDistance = [];
$inputThreshold = mb_strlen($input) / 2; $inputThreshold = mb_strlen($input) / 2;
foreach ($options as $option) { foreach ($options as $option) {
$distance = $input === $option if ($input === $option) {
? 0 $distance = 0;
: (strtolower($input) === strtolower($option) } else {
$distance = (strtolower($input) === strtolower($option)
? 1 ? 1
: levenshtein($input, $option)); : levenshtein($input, $option));
$threshold = max($inputThreshold, mb_strlen($option) / 2, 1);
if ($distance <= $threshold) {
$optionsByDistance[$option] = $distance;
} }
$threshold = max($inputThreshold, mb_strlen($option) / 2, 1);
if ($distance > $threshold) {
continue;
}
$optionsByDistance[$option] = $distance;
} }
asort($optionsByDistance); asort($optionsByDistance);

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Utils; namespace GraphQL\Utils;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -9,6 +12,14 @@ use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\ScalarType;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_merge;
use function is_array;
use function is_object;
use function is_string;
use function sprintf;
/** /**
* Coerces a PHP value given a GraphQL Type. * Coerces a PHP value given a GraphQL Type.
@ -20,23 +31,26 @@ class Value
{ {
/** /**
* Given a type and any value, return a runtime value coerced to match the type. * Given a type and any value, return a runtime value coerced to match the type.
*
* @param mixed[] $path
*/ */
public static function coerceValue($value, InputType $type, $blameNode = null, array $path = null) public static function coerceValue($value, InputType $type, $blameNode = null, ?array $path = null)
{ {
if ($type instanceof NonNull) { if ($type instanceof NonNull) {
if ($value === null) { if ($value === null) {
return self::ofErrors([ return self::ofErrors([
self::coercionError( self::coercionError(
"Expected non-nullable type $type not to be null", sprintf('Expected non-nullable type %s not to be null', $type),
$blameNode, $blameNode,
$path $path
), ),
]); ]);
} }
return self::coerceValue($value, $type->getWrappedType(), $blameNode, $path); return self::coerceValue($value, $type->getWrappedType(), $blameNode, $path);
} }
if (null === $value) { if ($value === null) {
// Explicitly return the value null. // Explicitly return the value null.
return self::ofValue(null); return self::ofValue(null);
} }
@ -50,7 +64,7 @@ class Value
} catch (\Exception $error) { } catch (\Exception $error) {
return self::ofErrors([ return self::ofErrors([
self::coercionError( self::coercionError(
"Expected type {$type->name}", sprintf('Expected type %s', $type->name),
$blameNode, $blameNode,
$path, $path,
$error->getMessage(), $error->getMessage(),
@ -60,7 +74,7 @@ class Value
} catch (\Throwable $error) { } catch (\Throwable $error) {
return self::ofErrors([ return self::ofErrors([
self::coercionError( self::coercionError(
"Expected type {$type->name}", sprintf('Expected type %s', $type->name),
$blameNode, $blameNode,
$path, $path,
$error->getMessage(), $error->getMessage(),
@ -80,15 +94,21 @@ class Value
$suggestions = Utils::suggestionList( $suggestions = Utils::suggestionList(
Utils::printSafe($value), Utils::printSafe($value),
array_map(function($enumValue) { return $enumValue->name; }, $type->getValues()) array_map(
function ($enumValue) {
return $enumValue->name;
},
$type->getValues()
)
); );
$didYouMean = $suggestions $didYouMean = $suggestions
? "did you mean " . Utils::orList($suggestions) . "?" ? 'did you mean ' . Utils::orList($suggestions) . '?'
: null; : null;
return self::ofErrors([ return self::ofErrors([
self::coercionError( self::coercionError(
"Expected type {$type->name}", sprintf('Expected type %s', $type->name),
$blameNode, $blameNode,
$path, $path,
$didYouMean $didYouMean
@ -114,10 +134,12 @@ class Value
$coercedValue[] = $coercedItem['value']; $coercedValue[] = $coercedItem['value'];
} }
} }
return $errors ? self::ofErrors($errors) : self::ofValue($coercedValue); return $errors ? self::ofErrors($errors) : self::ofValue($coercedValue);
} }
// Lists accept a non-list value as a list of one. // Lists accept a non-list value as a list of one.
$coercedItem = self::coerceValue($value, $itemType, $blameNode); $coercedItem = self::coerceValue($value, $itemType, $blameNode);
return $coercedItem['errors'] ? $coercedItem : self::ofValue([$coercedItem['value']]); return $coercedItem['errors'] ? $coercedItem : self::ofValue([$coercedItem['value']]);
} }
@ -125,7 +147,7 @@ class Value
if (! is_object($value) && ! is_array($value) && ! $value instanceof \Traversable) { if (! is_object($value) && ! is_array($value) && ! $value instanceof \Traversable) {
return self::ofErrors([ return self::ofErrors([
self::coercionError( self::coercionError(
"Expected type {$type->name} to be an object", sprintf('Expected type %s to be an object', $type->name),
$blameNode, $blameNode,
$path $path
), ),
@ -136,21 +158,7 @@ class Value
$coercedValue = []; $coercedValue = [];
$fields = $type->getFields(); $fields = $type->getFields();
foreach ($fields as $fieldName => $field) { foreach ($fields as $fieldName => $field) {
if (!array_key_exists($fieldName, $value)) { if (array_key_exists($fieldName, $value)) {
if ($field->defaultValueExists()) {
$coercedValue[$fieldName] = $field->defaultValue;
} else if ($field->getType() instanceof NonNull) {
$fieldPath = self::printPath(self::atPath($path, $fieldName));
$errors = self::add(
$errors,
self::coercionError(
"Field {$fieldPath} of required " .
"type {$field->type} was not provided",
$blameNode
)
);
}
} else {
$fieldValue = $value[$fieldName]; $fieldValue = $value[$fieldName];
$coercedField = self::coerceValue( $coercedField = self::coerceValue(
$fieldValue, $fieldValue,
@ -163,63 +171,76 @@ class Value
} else { } else {
$coercedValue[$fieldName] = $coercedField['value']; $coercedValue[$fieldName] = $coercedField['value'];
} }
} elseif ($field->defaultValueExists()) {
$coercedValue[$fieldName] = $field->defaultValue;
} elseif ($field->getType() instanceof NonNull) {
$fieldPath = self::printPath(self::atPath($path, $fieldName));
$errors = self::add(
$errors,
self::coercionError(
sprintf(
'Field %s of required type %s was not provided',
$fieldPath,
$field->type->toString()
),
$blameNode
)
);
} }
} }
// Ensure every provided field is defined. // Ensure every provided field is defined.
foreach ($value as $fieldName => $field) { foreach ($value as $fieldName => $field) {
if (!array_key_exists($fieldName, $fields)) { if (array_key_exists($fieldName, $fields)) {
continue;
}
$suggestions = Utils::suggestionList( $suggestions = Utils::suggestionList(
$fieldName, $fieldName,
array_keys($fields) array_keys($fields)
); );
$didYouMean = $suggestions $didYouMean = $suggestions
? "did you mean " . Utils::orList($suggestions) . "?" ? 'did you mean ' . Utils::orList($suggestions) . '?'
: null; : null;
$errors = self::add( $errors = self::add(
$errors, $errors,
self::coercionError( self::coercionError(
"Field \"{$fieldName}\" is not defined by type {$type->name}", sprintf('Field "%s" is not defined by type %s', $fieldName, $type->name),
$blameNode, $blameNode,
$path, $path,
$didYouMean $didYouMean
) )
); );
} }
}
return $errors ? self::ofErrors($errors) : self::ofValue($coercedValue); return $errors ? self::ofErrors($errors) : self::ofValue($coercedValue);
} }
throw new Error("Unexpected type {$type}"); throw new Error(sprintf('Unexpected type %s', $type->name));
} }
private static function ofValue($value) { private static function ofErrors($errors)
return ['errors' => null, 'value' => $value]; {
}
private static function ofErrors($errors) {
return ['errors' => $errors, 'value' => Utils::undefined()]; return ['errors' => $errors, 'value' => Utils::undefined()];
} }
private static function add($errors, $moreErrors) {
return array_merge($errors, is_array($moreErrors) ? $moreErrors : [$moreErrors]);
}
private static function atPath($prev, $key) {
return ['prev' => $prev, 'key' => $key];
}
/** /**
* @param string $message * @param string $message
* @param Node $blameNode * @param Node $blameNode
* @param array|null $path * @param mixed[]|null $path
* @param string $subMessage * @param string $subMessage
* @param \Exception|\Throwable|null $originalError * @param \Exception|\Throwable|null $originalError
* @return Error * @return Error
*/ */
private static function coercionError($message, $blameNode, array $path = null, $subMessage = null, $originalError = null) { private static function coercionError(
$message,
$blameNode,
?array $path = null,
$subMessage = null,
$originalError = null
) {
$pathStr = self::printPath($path); $pathStr = self::printPath($path);
// Return a GraphQLError instance // Return a GraphQLError instance
return new Error( return new Error(
$message . $message .
@ -236,10 +257,11 @@ class Value
/** /**
* Build a string describing the path into the value where the error was found * Build a string describing the path into the value where the error was found
* *
* @param $path * @param mixed[]|null $path
* @return string * @return string
*/ */
private static function printPath(array $path = null) { private static function printPath(?array $path = null)
{
$pathStr = ''; $pathStr = '';
$currentPath = $path; $currentPath = $path;
while ($currentPath) { while ($currentPath) {
@ -249,6 +271,36 @@ class Value
: '[' . $currentPath['key'] . ']') . $pathStr; : '[' . $currentPath['key'] . ']') . $pathStr;
$currentPath = $currentPath['prev']; $currentPath = $currentPath['prev'];
} }
return $pathStr ? 'value' . $pathStr : ''; return $pathStr ? 'value' . $pathStr : '';
} }
/**
* @param mixed $value
* @return (mixed|null)[]
*/
private static function ofValue($value)
{
return ['errors' => null, 'value' => $value];
}
/**
* @param mixed|null $prev
* @param mixed|null $key
* @return (mixed|null)[]
*/
private static function atPath($prev, $key)
{
return ['prev' => $prev, 'key' => $key];
}
/**
* @param Error[] $errors
* @param Error|Error[] $moreErrors
* @return Error[]
*/
private static function add($errors, $moreErrors)
{
return array_merge($errors, is_array($moreErrors) ? $moreErrors : [$moreErrors]);
}
} }

View File

@ -1,4 +1,5 @@
<?php <?php
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Language\DirectiveLocation; use GraphQL\Language\DirectiveLocation;
@ -11,10 +12,10 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use GraphQL\Utils\FindBreakingChanges; use GraphQL\Utils\BreakingChangesFinder;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class FindBreakingChangesTest extends TestCase class BreakingChangesFinderTest extends TestCase
{ {
private $queryType; private $queryType;
@ -60,17 +61,17 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED,
'description' => 'Type1 was removed.' 'description' => 'Type1 was removed.'
] ]
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findRemovedTypes($oldSchema, $newSchema) BreakingChangesFinder::findRemovedTypes($oldSchema, $newSchema)
); );
$this->assertEquals([], FindBreakingChanges::findRemovedTypes($oldSchema, $oldSchema)); $this->assertEquals([], BreakingChangesFinder::findRemovedTypes($oldSchema, $oldSchema));
} }
/** /**
@ -109,14 +110,14 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_TYPE_CHANGED_KIND,
'description' => 'Type1 changed from an Interface type to a Union type.' 'description' => 'Type1 changed from an Interface type to a Union type.'
] ]
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findTypesThatChangedKind($oldSchema, $newSchema) BreakingChangesFinder::findTypesThatChangedKind($oldSchema, $newSchema)
); );
} }
@ -208,60 +209,60 @@ class FindBreakingChangesTest extends TestCase
$expectedFieldChanges = [ $expectedFieldChanges = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_REMOVED,
'description' => 'Type1.field2 was removed.', 'description' => 'Type1.field2 was removed.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field3 changed type from String to Boolean.', 'description' => 'Type1.field3 changed type from String to Boolean.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field4 changed type from TypeA to TypeB.', 'description' => 'Type1.field4 changed type from TypeA to TypeB.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field6 changed type from String to [String].', 'description' => 'Type1.field6 changed type from String to [String].',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field7 changed type from [String] to String.', 'description' => 'Type1.field7 changed type from [String] to String.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field9 changed type from Int! to Int.', 'description' => 'Type1.field9 changed type from Int! to Int.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field10 changed type from [Int]! to [Int].', 'description' => 'Type1.field10 changed type from [Int]! to [Int].',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field11 changed type from Int to [Int]!.', 'description' => 'Type1.field11 changed type from Int to [Int]!.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field13 changed type from [Int!] to [Int].', 'description' => 'Type1.field13 changed type from [Int!] to [Int].',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field14 changed type from [Int] to [[Int]].', 'description' => 'Type1.field14 changed type from [Int] to [[Int]].',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field15 changed type from [[Int]] to [Int].', 'description' => 'Type1.field15 changed type from [[Int]] to [Int].',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field16 changed type from Int! to [Int]!.', 'description' => 'Type1.field16 changed type from Int! to [Int]!.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'Type1.field18 changed type from [[Int!]!] to [[Int!]].', 'description' => 'Type1.field18 changed type from [[Int!]!] to [[Int!]].',
], ],
]; ];
$this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedTypeOnObjectOrInterfaceTypes($oldSchema, $newSchema)); $this->assertEquals($expectedFieldChanges, BreakingChangesFinder::findFieldsThatChangedTypeOnObjectOrInterfaceTypes($oldSchema, $newSchema));
} }
/** /**
@ -380,52 +381,52 @@ class FindBreakingChangesTest extends TestCase
$expectedFieldChanges = [ $expectedFieldChanges = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'InputType1.field1 changed type from String to Int.', 'description' => 'InputType1.field1 changed type from String to Int.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_REMOVED,
'description' => 'InputType1.field2 was removed.', 'description' => 'InputType1.field2 was removed.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'InputType1.field3 changed type from [String] to String.', 'description' => 'InputType1.field3 changed type from [String] to String.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'InputType1.field5 changed type from String to String!.', 'description' => 'InputType1.field5 changed type from String to String!.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'InputType1.field6 changed type from [Int] to [Int]!.', 'description' => 'InputType1.field6 changed type from [Int] to [Int]!.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'InputType1.field8 changed type from Int to [Int]!.', 'description' => 'InputType1.field8 changed type from Int to [Int]!.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'InputType1.field9 changed type from [Int] to [Int!].', 'description' => 'InputType1.field9 changed type from [Int] to [Int!].',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'InputType1.field11 changed type from [Int] to [[Int]].', 'description' => 'InputType1.field11 changed type from [Int] to [[Int]].',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'InputType1.field12 changed type from [[Int]] to [Int].', 'description' => 'InputType1.field12 changed type from [[Int]] to [Int].',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'InputType1.field13 changed type from Int! to [Int]!.', 'description' => 'InputType1.field13 changed type from Int! to [Int]!.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'InputType1.field15 changed type from [[Int]!] to [[Int!]!].', 'description' => 'InputType1.field15 changed type from [[Int]!] to [[Int!]!].',
], ],
]; ];
$this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['breakingChanges']); $this->assertEquals($expectedFieldChanges, BreakingChangesFinder::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['breakingChanges']);
} }
/** /**
@ -461,14 +462,14 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED,
'description' => 'A non-null field requiredField on input type InputType1 was added.' 'description' => 'A non-null field requiredField on input type InputType1 was added.'
], ],
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['breakingChanges'] BreakingChangesFinder::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['breakingChanges']
); );
} }
@ -524,14 +525,14 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, 'type' => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION,
'description' => 'Type2 was removed from union type UnionType1.' 'description' => 'Type2 was removed from union type UnionType1.'
] ]
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findTypesRemovedFromUnions($oldSchema, $newSchema) BreakingChangesFinder::findTypesRemovedFromUnions($oldSchema, $newSchema)
); );
} }
@ -569,14 +570,14 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, 'type' => BreakingChangesFinder::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM,
'description' => 'VALUE1 was removed from enum type EnumType1.' 'description' => 'VALUE1 was removed from enum type EnumType1.'
] ]
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findValuesRemovedFromEnums($oldSchema, $newSchema) BreakingChangesFinder::findValuesRemovedFromEnums($oldSchema, $newSchema)
); );
} }
@ -646,20 +647,20 @@ class FindBreakingChangesTest extends TestCase
$expectedChanges = [ $expectedChanges = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_REMOVED,
'description' => 'Type1.field1 arg name was removed', 'description' => 'Type1.field1 arg name was removed',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_REMOVED,
'description' => 'Interface1.field1 arg arg1 was removed', 'description' => 'Interface1.field1 arg arg1 was removed',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_REMOVED,
'description' => 'Interface1.field1 arg objectArg was removed', 'description' => 'Interface1.field1 arg objectArg was removed',
] ]
]; ];
$this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); $this->assertEquals($expectedChanges, BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['breakingChanges']);
} }
/** /**
@ -731,56 +732,56 @@ class FindBreakingChangesTest extends TestCase
$expectedChanges = [ $expectedChanges = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg1 has changed type from String to Int', 'description' => 'Type1.field1 arg arg1 has changed type from String to Int',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg2 has changed type from String to [String]' 'description' => 'Type1.field1 arg arg2 has changed type from String to [String]'
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg3 has changed type from [String] to String', 'description' => 'Type1.field1 arg arg3 has changed type from [String] to String',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg4 has changed type from String to String!', 'description' => 'Type1.field1 arg arg4 has changed type from String to String!',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg5 has changed type from String! to Int', 'description' => 'Type1.field1 arg arg5 has changed type from String! to Int',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg6 has changed type from String! to Int!', 'description' => 'Type1.field1 arg arg6 has changed type from String! to Int!',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg8 has changed type from Int to [Int]!', 'description' => 'Type1.field1 arg arg8 has changed type from Int to [Int]!',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg9 has changed type from [Int] to [Int!]', 'description' => 'Type1.field1 arg arg9 has changed type from [Int] to [Int!]',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg11 has changed type from [Int] to [[Int]]', 'description' => 'Type1.field1 arg arg11 has changed type from [Int] to [[Int]]',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg12 has changed type from [[Int]] to [Int]', 'description' => 'Type1.field1 arg arg12 has changed type from [[Int]] to [Int]',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg13 has changed type from Int! to [Int]!', 'description' => 'Type1.field1 arg arg13 has changed type from Int! to [Int]!',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'Type1.field1 arg arg15 has changed type from [[Int]!] to [[Int!]!]', 'description' => 'Type1.field1 arg arg15 has changed type from [[Int]!] to [[Int!]!]',
], ],
]; ];
$this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); $this->assertEquals($expectedChanges, BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['breakingChanges']);
} }
/** /**
@ -821,14 +822,15 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_NON_NULL_ARG_ADDED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_ARG_ADDED,
'description' => 'A non-null arg newRequiredArg on Type1.field1 was added' 'description' => 'A non-null arg newRequiredArg on Type1.field1 was added'
] ]
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['breakingChanges']
);
} }
/** /**
@ -885,7 +887,7 @@ class FindBreakingChangesTest extends TestCase
'types' => [$newType], 'types' => [$newType],
]); ]);
$this->assertEquals([], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); $this->assertEquals([], BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['breakingChanges']);
} }
/** /**
@ -925,7 +927,7 @@ class FindBreakingChangesTest extends TestCase
'types' => [$newType], 'types' => [$newType],
]); ]);
$this->assertEquals([], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); $this->assertEquals([], BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['breakingChanges']);
} }
/** /**
@ -964,14 +966,15 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT, 'type' => BreakingChangesFinder::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT,
'description' => 'Type1 no longer implements interface Interface1.' 'description' => 'Type1 no longer implements interface Interface1.'
], ],
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findInterfacesRemovedFromObjectTypes($oldSchema, $newSchema)); BreakingChangesFinder::findInterfacesRemovedFromObjectTypes($oldSchema, $newSchema)
);
} }
/** /**
@ -1178,11 +1181,11 @@ class FindBreakingChangesTest extends TestCase
$expectedBreakingChanges = [ $expectedBreakingChanges = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED,
'description' => 'TypeThatGetsRemoved was removed.', 'description' => 'TypeThatGetsRemoved was removed.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED,
'description' => 'TypeInUnion2 was removed.', 'description' => 'TypeInUnion2 was removed.',
], ],
/* This is reported in the js version because builtin sclar types are added on demand /* This is reported in the js version because builtin sclar types are added on demand
@ -1192,52 +1195,52 @@ class FindBreakingChangesTest extends TestCase
'description' => 'Int was removed.' 'description' => 'Int was removed.'
],*/ ],*/
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_TYPE_CHANGED_KIND,
'description' => 'TypeThatChangesType changed from an Object type to an Interface type.', 'description' => 'TypeThatChangesType changed from an Object type to an Interface type.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_REMOVED,
'description' => 'TypeThatHasBreakingFieldChanges.field1 was removed.', 'description' => 'TypeThatHasBreakingFieldChanges.field1 was removed.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
'description' => 'TypeThatHasBreakingFieldChanges.field2 changed type from String to Boolean.', 'description' => 'TypeThatHasBreakingFieldChanges.field2 changed type from String to Boolean.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, 'type' => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION,
'description' => 'TypeInUnion2 was removed from union type UnionTypeThatLosesAType.', 'description' => 'TypeInUnion2 was removed from union type UnionTypeThatLosesAType.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, 'type' => BreakingChangesFinder::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM,
'description' => 'VALUE0 was removed from enum type EnumTypeThatLosesAValue.', 'description' => 'VALUE0 was removed from enum type EnumTypeThatLosesAValue.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, 'type' => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
'description' => 'ArgThatChanges.field1 arg id has changed type from Int to String', 'description' => 'ArgThatChanges.field1 arg id has changed type from Int to String',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT, 'type' => BreakingChangesFinder::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT,
'description' => 'TypeThatLosesInterface1 no longer implements interface Interface1.', 'description' => 'TypeThatLosesInterface1 no longer implements interface Interface1.',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_DIRECTIVE_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_REMOVED,
'description' => 'skip was removed', 'description' => 'skip was removed',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_DIRECTIVE_ARG_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_ARG_REMOVED,
'description' => 'arg1 was removed from DirectiveThatRemovesArg', 'description' => 'arg1 was removed from DirectiveThatRemovesArg',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED,
'description' => 'A non-null arg arg1 on directive NonNullDirectiveAdded was added', 'description' => 'A non-null arg arg1 on directive NonNullDirectiveAdded was added',
], ],
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_DIRECTIVE_LOCATION_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_LOCATION_REMOVED,
'description' => 'QUERY was removed from Directive Name', 'description' => 'QUERY was removed from Directive Name',
] ]
]; ];
$this->assertEquals($expectedBreakingChanges, FindBreakingChanges::findBreakingChanges($oldSchema, $newSchema)); $this->assertEquals($expectedBreakingChanges, BreakingChangesFinder::findBreakingChanges($oldSchema, $newSchema));
} }
/** /**
@ -1257,12 +1260,12 @@ class FindBreakingChangesTest extends TestCase
$expectedBreakingChanges = [ $expectedBreakingChanges = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_DIRECTIVE_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_REMOVED,
'description' => "{$includeDirective->name} was removed", 'description' => "{$includeDirective->name} was removed",
] ]
]; ];
$this->assertEquals($expectedBreakingChanges, FindBreakingChanges::findRemovedDirectives($oldSchema, $newSchema)); $this->assertEquals($expectedBreakingChanges, BreakingChangesFinder::findRemovedDirectives($oldSchema, $newSchema));
} }
/** /**
@ -1280,12 +1283,12 @@ class FindBreakingChangesTest extends TestCase
$expectedBreakingChanges = [ $expectedBreakingChanges = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_DIRECTIVE_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_REMOVED,
'description' => "{$deprecatedDirective->name} was removed", 'description' => "{$deprecatedDirective->name} was removed",
] ]
]; ];
$this->assertEquals($expectedBreakingChanges, FindBreakingChanges::findRemovedDirectives($oldSchema, $newSchema)); $this->assertEquals($expectedBreakingChanges, BreakingChangesFinder::findRemovedDirectives($oldSchema, $newSchema));
} }
/** /**
@ -1318,12 +1321,12 @@ class FindBreakingChangesTest extends TestCase
$expectedBreakingChanges = [ $expectedBreakingChanges = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_DIRECTIVE_ARG_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_ARG_REMOVED,
'description' => "arg1 was removed from DirectiveWithArg", 'description' => "arg1 was removed from DirectiveWithArg",
] ]
]; ];
$this->assertEquals($expectedBreakingChanges, FindBreakingChanges::findRemovedDirectiveArgs($oldSchema, $newSchema)); $this->assertEquals($expectedBreakingChanges, BreakingChangesFinder::findRemovedDirectiveArgs($oldSchema, $newSchema));
} }
/** /**
@ -1357,12 +1360,12 @@ class FindBreakingChangesTest extends TestCase
$expectedBreakingChanges = [ $expectedBreakingChanges = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED,
'description' => "A non-null arg arg1 on directive DirectiveName was added", 'description' => "A non-null arg arg1 on directive DirectiveName was added",
] ]
]; ];
$this->assertEquals($expectedBreakingChanges, FindBreakingChanges::findAddedNonNullDirectiveArgs($oldSchema, $newSchema)); $this->assertEquals($expectedBreakingChanges, BreakingChangesFinder::findAddedNonNullDirectiveArgs($oldSchema, $newSchema));
} }
/** /**
@ -1380,7 +1383,7 @@ class FindBreakingChangesTest extends TestCase
'locations' => [DirectiveLocation::FIELD_DEFINITION], 'locations' => [DirectiveLocation::FIELD_DEFINITION],
]); ]);
$this->assertEquals([DirectiveLocation::QUERY], FindBreakingChanges::findRemovedLocationsForDirective($d1, $d2)); $this->assertEquals([DirectiveLocation::QUERY], BreakingChangesFinder::findRemovedLocationsForDirective($d1, $d2));
} }
/** /**
@ -1411,12 +1414,12 @@ class FindBreakingChangesTest extends TestCase
$expectedBreakingChanges = [ $expectedBreakingChanges = [
[ [
'type' => FindBreakingChanges::BREAKING_CHANGE_DIRECTIVE_LOCATION_REMOVED, 'type' => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_LOCATION_REMOVED,
'description' => "QUERY was removed from Directive Name", 'description' => "QUERY was removed from Directive Name",
] ]
]; ];
$this->assertEquals($expectedBreakingChanges, FindBreakingChanges::findRemovedDirectiveLocations($oldSchema, $newSchema)); $this->assertEquals($expectedBreakingChanges, BreakingChangesFinder::findRemovedDirectiveLocations($oldSchema, $newSchema));
} }
// DESCRIBE: findDangerousChanges // DESCRIBE: findDangerousChanges
@ -1469,14 +1472,14 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED, 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED,
'description' => 'Type1.field1 arg name has changed defaultValue' 'description' => 'Type1.field1 arg name has changed defaultValue'
] ]
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['dangerousChanges'] BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['dangerousChanges']
); );
} }
@ -1513,14 +1516,14 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM, 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM,
'description' => 'VALUE2 was added to enum type EnumType1.' 'description' => 'VALUE2 was added to enum type EnumType1.'
] ]
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findValuesAddedToEnums($oldSchema, $newSchema) BreakingChangesFinder::findValuesAddedToEnums($oldSchema, $newSchema)
); );
} }
@ -1562,14 +1565,14 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT, 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT,
'description' => 'Interface1 added to interfaces implemented by Type1.' 'description' => 'Interface1 added to interfaces implemented by Type1.'
] ]
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findInterfacesAddedToObjectTypes($oldSchema, $newSchema) BreakingChangesFinder::findInterfacesAddedToObjectTypes($oldSchema, $newSchema)
); );
} }
@ -1620,14 +1623,14 @@ class FindBreakingChangesTest extends TestCase
$expected = [ $expected = [
[ [
'type' => FindBreakingChanges::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION,
'description' => 'Type2 was added to union type UnionType1.' 'description' => 'Type2 was added to union type UnionType1.'
] ]
]; ];
$this->assertEquals( $this->assertEquals(
$expected, $expected,
FindBreakingChanges::findTypesAddedToUnions($oldSchema, $newSchema) BreakingChangesFinder::findTypesAddedToUnions($oldSchema, $newSchema)
); );
} }
@ -1673,11 +1676,11 @@ class FindBreakingChangesTest extends TestCase
$expectedFieldChanges = [ $expectedFieldChanges = [
[ [
'description' => 'A nullable field field2 on input type InputType1 was added.', 'description' => 'A nullable field field2 on input type InputType1 was added.',
'type' => FindBreakingChanges::DANGEROUS_CHANGE_NULLABLE_INPUT_FIELD_ADDED 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_NULLABLE_INPUT_FIELD_ADDED,
], ],
]; ];
$this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['dangerousChanges']); $this->assertEquals($expectedFieldChanges, BreakingChangesFinder::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['dangerousChanges']);
} }
/** /**
@ -1797,23 +1800,23 @@ class FindBreakingChangesTest extends TestCase
$expectedDangerousChanges = [ $expectedDangerousChanges = [
[ [
'description' => 'Type1.field1 arg name has changed defaultValue', 'description' => 'Type1.field1 arg name has changed defaultValue',
'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED,
], ],
[ [
'description' => 'VALUE2 was added to enum type EnumType1.', 'description' => 'VALUE2 was added to enum type EnumType1.',
'type' => FindBreakingChanges::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM,
], ],
[ [
'type' => FindBreakingChanges::DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT, 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT,
'description' => 'Interface1 added to interfaces implemented by TypeThatGainsInterface1.', 'description' => 'Interface1 added to interfaces implemented by TypeThatGainsInterface1.',
], ],
[ [
'type' => FindBreakingChanges::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION,
'description' => 'TypeInUnion2 was added to union type UnionTypeThatGainsAType.', 'description' => 'TypeInUnion2 was added to union type UnionTypeThatGainsAType.',
] ]
]; ];
$this->assertEquals($expectedDangerousChanges, FindBreakingChanges::findDangerousChanges($oldSchema, $newSchema)); $this->assertEquals($expectedDangerousChanges, BreakingChangesFinder::findDangerousChanges($oldSchema, $newSchema));
} }
/** /**
@ -1865,10 +1868,10 @@ class FindBreakingChangesTest extends TestCase
$expectedFieldChanges = [ $expectedFieldChanges = [
[ [
'description' => 'A nullable arg arg2 on Type1.field1 was added', 'description' => 'A nullable arg arg2 on Type1.field1 was added',
'type' => FindBreakingChanges::DANGEROUS_CHANGE_NULLABLE_ARG_ADDED 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_NULLABLE_ARG_ADDED,
], ],
]; ];
$this->assertEquals($expectedFieldChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['dangerousChanges']); $this->assertEquals($expectedFieldChanges, BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['dangerousChanges']);
} }
} }