diff --git a/CHANGELOG.md b/CHANGELOG.md
index 100a1db..3a6599e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
# Changelog
## dev-master
- Spec compliance: error extensions are displayed under `extensions` key
+- `AbstractValidationRule` renamed to `ValidationRule` (NS `GraphQL\Validator\Rules`)
+- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
#### v0.12.5
- Execution performance optimization for lists
diff --git a/benchmarks/HugeSchemaBench.php b/benchmarks/HugeSchemaBench.php
index 7146e7f..31073e2 100644
--- a/benchmarks/HugeSchemaBench.php
+++ b/benchmarks/HugeSchemaBench.php
@@ -2,10 +2,9 @@
namespace GraphQL\Benchmarks;
use GraphQL\GraphQL;
-use GraphQL\Schema;
use GraphQL\Benchmarks\Utils\QueryGenerator;
use GraphQL\Benchmarks\Utils\SchemaGenerator;
-use GraphQL\Type\LazyResolution;
+use GraphQL\Type\Schema;
/**
* @BeforeMethods({"setUp"})
@@ -23,8 +22,6 @@ class HugeSchemaBench
private $schema;
- private $lazySchema;
-
/**
* @var string
*/
diff --git a/benchmarks/Utils/QueryGenerator.php b/benchmarks/Utils/QueryGenerator.php
index e99f43a..7312e30 100644
--- a/benchmarks/Utils/QueryGenerator.php
+++ b/benchmarks/Utils/QueryGenerator.php
@@ -7,11 +7,11 @@ use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\Printer;
-use GraphQL\Schema;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\WrappingType;
+use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
class QueryGenerator
diff --git a/benchmarks/Utils/SchemaGenerator.php b/benchmarks/Utils/SchemaGenerator.php
index 6cc9145..8ee39e7 100644
--- a/benchmarks/Utils/SchemaGenerator.php
+++ b/benchmarks/Utils/SchemaGenerator.php
@@ -1,11 +1,11 @@
'Email',
'serialize' => function($value) {/* See function body above */},
'parseValue' => function($value) {/* See function body above */},
- 'parseLiteral' => function($valueNode) {/* See function body above */},
+ 'parseLiteral' => function($valueNode, array $variables = null) {/* See function body above */},
]);
```
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 4be54dd..aba1cb2 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -90,4 +90,11 @@
/>
+
+
+
+
+
+
+
diff --git a/src/Error/Error.php b/src/Error/Error.php
index 02c26ec..35019b2 100644
--- a/src/Error/Error.php
+++ b/src/Error/Error.php
@@ -8,9 +8,9 @@ use GraphQL\Language\AST\Node;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
use GraphQL\Utils\Utils;
+use Traversable;
use function array_filter;
use function array_map;
-use function array_merge;
use function is_array;
use function iterator_to_array;
@@ -81,12 +81,12 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
protected $extensions;
/**
- * @param string $message
- * @param Node[]|null $nodes
- * @param mixed[]|null $positions
- * @param mixed[]|null $path
- * @param \Throwable $previous
- * @param mixed[] $extensions
+ * @param string $message
+ * @param Node|Node[]|Traversable|null $nodes
+ * @param mixed[]|null $positions
+ * @param mixed[]|null $path
+ * @param \Throwable $previous
+ * @param mixed[] $extensions
*/
public function __construct(
$message,
@@ -271,7 +271,7 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
$this->locations = array_filter(
array_map(
function ($node) {
- if ($node->loc) {
+ if ($node->loc && $node->loc->source) {
return $node->loc->source->getLocation($node->loc->start);
}
},
diff --git a/src/GraphQL.php b/src/GraphQL.php
index ce863d6..d5c6237 100644
--- a/src/GraphQL.php
+++ b/src/GraphQL.php
@@ -13,7 +13,7 @@ use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\Type;
use GraphQL\Validator\DocumentValidator;
-use GraphQL\Validator\Rules\AbstractValidationRule;
+use GraphQL\Validator\Rules\ValidationRule;
use GraphQL\Validator\Rules\QueryComplexity;
/**
@@ -272,7 +272,7 @@ class GraphQL
* Returns standard validation rules implementing GraphQL spec
*
* @api
- * @return AbstractValidationRule[]
+ * @return ValidationRule[]
*/
public static function getStandardValidationRules()
{
diff --git a/src/Language/AST/ArgumentNode.php b/src/Language/AST/ArgumentNode.php
index 3c942e7..1519429 100644
--- a/src/Language/AST/ArgumentNode.php
+++ b/src/Language/AST/ArgumentNode.php
@@ -1,17 +1,19 @@
start = $start;
- $tmp->end = $end;
+ $tmp->end = $end;
return $tmp;
}
- public function __construct(Token $startToken = null, Token $endToken = null, Source $source = null)
+ public function __construct(?Token $startToken = null, ?Token $endToken = null, ?Source $source = null)
{
$this->startToken = $startToken;
- $this->endToken = $endToken;
- $this->source = $source;
+ $this->endToken = $endToken;
+ $this->source = $source;
- if ($startToken && $endToken) {
- $this->start = $startToken->start;
- $this->end = $endToken->end;
+ if (! $startToken || ! $endToken) {
+ return;
}
+
+ $this->start = $startToken->start;
+ $this->end = $endToken->end;
}
}
diff --git a/src/Language/AST/NameNode.php b/src/Language/AST/NameNode.php
index 7860775..91f6d05 100644
--- a/src/Language/AST/NameNode.php
+++ b/src/Language/AST/NameNode.php
@@ -1,12 +1,15 @@
$arrValue) {
$cloned[$key] = $this->cloneValue($arrValue);
}
- } else if ($value instanceof Node) {
+ } elseif ($value instanceof self) {
$cloned = clone $value;
foreach (get_object_vars($cloned) as $prop => $propValue) {
$cloned->{$prop} = $this->cloneValue($propValue);
@@ -84,34 +88,34 @@ abstract class Node
public function __toString()
{
$tmp = $this->toArray(true);
+
return (string) json_encode($tmp);
}
/**
* @param bool $recursive
- * @return array
+ * @return mixed[]
*/
public function toArray($recursive = false)
{
if ($recursive) {
return $this->recursiveToArray($this);
- } else {
- $tmp = (array) $this;
-
- if ($this->loc) {
- $tmp['loc'] = [
- 'start' => $this->loc->start,
- 'end' => $this->loc->end
- ];
- }
-
- return $tmp;
}
+
+ $tmp = (array) $this;
+
+ if ($this->loc) {
+ $tmp['loc'] = [
+ 'start' => $this->loc->start,
+ 'end' => $this->loc->end,
+ ];
+ }
+
+ return $tmp;
}
/**
- * @param Node $node
- * @return array
+ * @return mixed[]
*/
private function recursiveToArray(Node $node)
{
@@ -122,25 +126,27 @@ abstract class Node
if ($node->loc) {
$result['loc'] = [
'start' => $node->loc->start,
- 'end' => $node->loc->end
+ 'end' => $node->loc->end,
];
}
foreach (get_object_vars($node) as $prop => $propValue) {
- if (isset($result[$prop]))
+ if (isset($result[$prop])) {
continue;
+ }
- if ($propValue === null)
+ if ($propValue === null) {
continue;
+ }
if (is_array($propValue) || $propValue instanceof NodeList) {
$tmp = [];
foreach ($propValue as $tmp1) {
$tmp[] = $tmp1 instanceof Node ? $this->recursiveToArray($tmp1) : (array) $tmp1;
}
- } else if ($propValue instanceof Node) {
+ } elseif ($propValue instanceof Node) {
$tmp = $this->recursiveToArray($propValue);
- } else if (is_scalar($propValue) || null === $propValue) {
+ } elseif (is_scalar($propValue) || $propValue === null) {
$tmp = $propValue;
} else {
$tmp = null;
@@ -148,6 +154,7 @@ abstract class Node
$result[$prop] = $tmp;
}
+
return $result;
}
}
diff --git a/src/Language/AST/NodeKind.php b/src/Language/AST/NodeKind.php
index 1091c6e..d321cdc 100644
--- a/src/Language/AST/NodeKind.php
+++ b/src/Language/AST/NodeKind.php
@@ -1,5 +1,7 @@
NameNode::class,
+ self::NAME => NameNode::class,
// Document
- NodeKind::DOCUMENT => DocumentNode::class,
- NodeKind::OPERATION_DEFINITION => OperationDefinitionNode::class,
- NodeKind::VARIABLE_DEFINITION => VariableDefinitionNode::class,
- NodeKind::VARIABLE => VariableNode::class,
- NodeKind::SELECTION_SET => SelectionSetNode::class,
- NodeKind::FIELD => FieldNode::class,
- NodeKind::ARGUMENT => ArgumentNode::class,
+ self::DOCUMENT => DocumentNode::class,
+ self::OPERATION_DEFINITION => OperationDefinitionNode::class,
+ self::VARIABLE_DEFINITION => VariableDefinitionNode::class,
+ self::VARIABLE => VariableNode::class,
+ self::SELECTION_SET => SelectionSetNode::class,
+ self::FIELD => FieldNode::class,
+ self::ARGUMENT => ArgumentNode::class,
// Fragments
- NodeKind::FRAGMENT_SPREAD => FragmentSpreadNode::class,
- NodeKind::INLINE_FRAGMENT => InlineFragmentNode::class,
- NodeKind::FRAGMENT_DEFINITION => FragmentDefinitionNode::class,
+ self::FRAGMENT_SPREAD => FragmentSpreadNode::class,
+ self::INLINE_FRAGMENT => InlineFragmentNode::class,
+ self::FRAGMENT_DEFINITION => FragmentDefinitionNode::class,
// Values
- NodeKind::INT => IntValueNode::class,
- NodeKind::FLOAT => FloatValueNode::class,
- NodeKind::STRING => StringValueNode::class,
- NodeKind::BOOLEAN => BooleanValueNode::class,
- NodeKind::ENUM => EnumValueNode::class,
- NodeKind::NULL => NullValueNode::class,
- NodeKind::LST => ListValueNode::class,
- NodeKind::OBJECT => ObjectValueNode::class,
- NodeKind::OBJECT_FIELD => ObjectFieldNode::class,
+ self::INT => IntValueNode::class,
+ self::FLOAT => FloatValueNode::class,
+ self::STRING => StringValueNode::class,
+ self::BOOLEAN => BooleanValueNode::class,
+ self::ENUM => EnumValueNode::class,
+ self::NULL => NullValueNode::class,
+ self::LST => ListValueNode::class,
+ self::OBJECT => ObjectValueNode::class,
+ self::OBJECT_FIELD => ObjectFieldNode::class,
// Directives
- NodeKind::DIRECTIVE => DirectiveNode::class,
+ self::DIRECTIVE => DirectiveNode::class,
// Types
- NodeKind::NAMED_TYPE => NamedTypeNode::class,
- NodeKind::LIST_TYPE => ListTypeNode::class,
- NodeKind::NON_NULL_TYPE => NonNullTypeNode::class,
+ self::NAMED_TYPE => NamedTypeNode::class,
+ self::LIST_TYPE => ListTypeNode::class,
+ self::NON_NULL_TYPE => NonNullTypeNode::class,
// Type System Definitions
- NodeKind::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
- NodeKind::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class,
+ self::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
+ self::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class,
// Type Definitions
- NodeKind::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class,
- NodeKind::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class,
- NodeKind::FIELD_DEFINITION => FieldDefinitionNode::class,
- NodeKind::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class,
- NodeKind::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class,
- NodeKind::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class,
- NodeKind::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class,
- NodeKind::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class,
- NodeKind::INPUT_OBJECT_TYPE_DEFINITION =>InputObjectTypeDefinitionNode::class,
+ self::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class,
+ self::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class,
+ self::FIELD_DEFINITION => FieldDefinitionNode::class,
+ self::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class,
+ self::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class,
+ self::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class,
+ self::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class,
+ self::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class,
+ self::INPUT_OBJECT_TYPE_DEFINITION => InputObjectTypeDefinitionNode::class,
// Type Extensions
- NodeKind::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class,
- NodeKind::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class,
- NodeKind::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class,
- NodeKind::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class,
- NodeKind::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class,
- NodeKind::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class,
+ self::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class,
+ self::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class,
+ self::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class,
+ self::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class,
+ self::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class,
+ self::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class,
// Directive Definitions
- NodeKind::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class
+ self::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class,
];
}
diff --git a/src/Language/AST/NodeList.php b/src/Language/AST/NodeList.php
index b0adb81..5028022 100644
--- a/src/Language/AST/NodeList.php
+++ b/src/Language/AST/NodeList.php
@@ -1,22 +1,22 @@
nodes;
}
return new NodeList(array_merge($this->nodes, $list));
diff --git a/src/Language/AST/NonNullTypeNode.php b/src/Language/AST/NonNullTypeNode.php
index e450404..da0ce3d 100644
--- a/src/Language/AST/NonNullTypeNode.php
+++ b/src/Language/AST/NonNullTypeNode.php
@@ -1,12 +1,15 @@
self::QUERY,
self::MUTATION => self::MUTATION,
diff --git a/src/Language/Lexer.php b/src/Language/Lexer.php
index 835b947..223b6f8 100644
--- a/src/Language/Lexer.php
+++ b/src/Language/Lexer.php
@@ -1,9 +1,16 @@
source = $source;
- $this->options = $options;
+ $this->source = $source;
+ $this->options = $options;
$this->lastToken = $startOfFileToken;
- $this->token = $startOfFileToken;
- $this->line = 1;
+ $this->token = $startOfFileToken;
+ $this->line = 1;
$this->lineStart = 0;
- $this->position = $this->byteStreamPosition = 0;
+ $this->position = $this->byteStreamPosition = 0;
}
/**
@@ -93,7 +93,8 @@ class Lexer
public function advance()
{
$this->lastToken = $this->token;
- $token = $this->token = $this->lookahead();
+ $token = $this->token = $this->lookahead();
+
return $token;
}
@@ -105,11 +106,11 @@ class Lexer
$token = $token->next ?: ($token->next = $this->readToken($token));
} while ($token->kind === Token::COMMENT);
}
+
return $token;
}
/**
- * @param Token $prev
* @return Token
* @throws SyntaxError
*/
@@ -121,7 +122,7 @@ class Lexer
$position = $this->position;
$line = $this->line;
- $col = 1 + $position - $this->lineStart;
+ $col = 1 + $position - $this->lineStart;
if ($position >= $bodyLength) {
return new Token(Token::EOF, $bodyLength, $bodyLength, $line, $col, $prev);
@@ -144,6 +145,7 @@ class Lexer
return new Token(Token::BANG, $position, $position + 1, $line, $col, $prev);
case 35: // #
$this->moveStringCursor(-1, -1 * $bytes);
+
return $this->readComment($line, $col, $prev);
case 36: // $
return new Token(Token::DOLLAR, $position, $position + 1, $line, $col, $prev);
@@ -178,30 +180,82 @@ class Lexer
case 125: // }
return new Token(Token::BRACE_R, $position, $position + 1, $line, $col, $prev);
// A-Z
- case 65: case 66: case 67: case 68: case 69: case 70: case 71: case 72:
- case 73: case 74: case 75: case 76: case 77: case 78: case 79: case 80:
- case 81: case 82: case 83: case 84: case 85: case 86: case 87: case 88:
- case 89: case 90:
- // _
+ case 65:
+ case 66:
+ case 67:
+ case 68:
+ case 69:
+ case 70:
+ case 71:
+ case 72:
+ case 73:
+ case 74:
+ case 75:
+ case 76:
+ case 77:
+ case 78:
+ case 79:
+ case 80:
+ case 81:
+ case 82:
+ case 83:
+ case 84:
+ case 85:
+ case 86:
+ case 87:
+ case 88:
+ case 89:
+ case 90:
+ // _
case 95:
- // a-z
- case 97: case 98: case 99: case 100: case 101: case 102: case 103: case 104:
- case 105: case 106: case 107: case 108: case 109: case 110: case 111:
- case 112: case 113: case 114: case 115: case 116: case 117: case 118:
- case 119: case 120: case 121: case 122:
+ // a-z
+ case 97:
+ case 98:
+ case 99:
+ case 100:
+ case 101:
+ case 102:
+ case 103:
+ case 104:
+ case 105:
+ case 106:
+ case 107:
+ case 108:
+ case 109:
+ case 110:
+ case 111:
+ case 112:
+ case 113:
+ case 114:
+ case 115:
+ case 116:
+ case 117:
+ case 118:
+ case 119:
+ case 120:
+ case 121:
+ case 122:
return $this->moveStringCursor(-1, -1 * $bytes)
->readName($line, $col, $prev);
// -
case 45:
- // 0-9
- case 48: case 49: case 50: case 51: case 52:
- case 53: case 54: case 55: case 56: case 57:
+ // 0-9
+ case 48:
+ case 49:
+ case 50:
+ case 51:
+ case 52:
+ case 53:
+ case 54:
+ case 55:
+ case 56:
+ case 57:
return $this->moveStringCursor(-1, -1 * $bytes)
->readNumber($line, $col, $prev);
// "
case 34:
- list(,$nextCode) = $this->readChar();
- list(,$nextNextCode) = $this->moveStringCursor(1, 1)->readChar();
+ list(, $nextCode) = $this->readChar();
+ list(, $nextNextCode) = $this->moveStringCursor(1, 1)->readChar();
if ($nextCode === 34 && $nextNextCode === 34) {
return $this->moveStringCursor(-2, (-1 * $bytes) - 1)
@@ -213,7 +267,7 @@ class Lexer
}
$errMessage = $code === 39
- ? "Unexpected single quote character ('), did you mean to use ". 'a double quote (")?'
+ ? "Unexpected single quote character ('), did you mean to use " . 'a double quote (")?'
: 'Cannot parse the unexpected character ' . Utils::printCharCode($code) . '.';
throw new SyntaxError(
@@ -230,24 +284,24 @@ class Lexer
*
* @param int $line
* @param int $col
- * @param Token $prev
* @return Token
*/
private function readName($line, $col, Token $prev)
{
- $value = '';
- $start = $this->position;
+ $value = '';
+ $start = $this->position;
list ($char, $code) = $this->readChar();
while ($code && (
- $code === 95 || // _
- $code >= 48 && $code <= 57 || // 0-9
- $code >= 65 && $code <= 90 || // A-Z
- $code >= 97 && $code <= 122 // a-z
- )) {
- $value .= $char;
+ $code === 95 || // _
+ $code >= 48 && $code <= 57 || // 0-9
+ $code >= 65 && $code <= 90 || // A-Z
+ $code >= 97 && $code <= 122 // a-z
+ )) {
+ $value .= $char;
list ($char, $code) = $this->moveStringCursor(1, 1)->readChar();
}
+
return new Token(
Token::NAME,
$start,
@@ -268,33 +322,36 @@ class Lexer
*
* @param int $line
* @param int $col
- * @param Token $prev
* @return Token
* @throws SyntaxError
*/
private function readNumber($line, $col, Token $prev)
{
- $value = '';
- $start = $this->position;
+ $value = '';
+ $start = $this->position;
list ($char, $code) = $this->readChar();
$isFloat = false;
if ($code === 45) { // -
- $value .= $char;
+ $value .= $char;
list ($char, $code) = $this->moveStringCursor(1, 1)->readChar();
}
// guard against leading zero's
if ($code === 48) { // 0
- $value .= $char;
+ $value .= $char;
list ($char, $code) = $this->moveStringCursor(1, 1)->readChar();
if ($code >= 48 && $code <= 57) {
- throw new SyntaxError($this->source, $this->position, "Invalid number, unexpected digit after 0: " . Utils::printCharCode($code));
+ throw new SyntaxError(
+ $this->source,
+ $this->position,
+ 'Invalid number, unexpected digit after 0: ' . Utils::printCharCode($code)
+ );
}
} else {
- $value .= $this->readDigits();
+ $value .= $this->readDigits();
list ($char, $code) = $this->readChar();
}
@@ -302,14 +359,14 @@ class Lexer
$isFloat = true;
$this->moveStringCursor(1, 1);
- $value .= $char;
- $value .= $this->readDigits();
+ $value .= $char;
+ $value .= $this->readDigits();
list ($char, $code) = $this->readChar();
}
if ($code === 69 || $code === 101) { // E e
- $isFloat = true;
- $value .= $char;
+ $isFloat = true;
+ $value .= $char;
list ($char, $code) = $this->moveStringCursor(1, 1)->readChar();
if ($code === 43 || $code === 45) { // + -
@@ -341,7 +398,7 @@ class Lexer
$value = '';
do {
- $value .= $char;
+ $value .= $char;
list ($char, $code) = $this->moveStringCursor(1, 1)->readChar();
} while ($code >= 48 && $code <= 57); // 0 - 9
@@ -362,7 +419,6 @@ class Lexer
/**
* @param int $line
* @param int $col
- * @param Token $prev
* @return Token
* @throws SyntaxError
*/
@@ -371,13 +427,12 @@ class Lexer
$start = $this->position;
// Skip leading quote and read first string char:
- list ($char, $code, $bytes) = $this->moveStringCursor(1, 1)->readChar();
+ [$char, $code, $bytes] = $this->moveStringCursor(1, 1)->readChar();
$chunk = '';
$value = '';
- while (
- $code !== null &&
+ while ($code !== null &&
// not LineTerminator
$code !== 10 && $code !== 13
) {
@@ -403,22 +458,38 @@ class Lexer
$this->moveStringCursor(1, $bytes);
if ($code === 92) { // \
- $value .= $chunk;
+ $value .= $chunk;
list (, $code) = $this->readChar(true);
switch ($code) {
- case 34: $value .= '"'; break;
- case 47: $value .= '/'; break;
- case 92: $value .= '\\'; break;
- case 98: $value .= chr(8); break; // \b (backspace)
- case 102: $value .= "\f"; break;
- case 110: $value .= "\n"; break;
- case 114: $value .= "\r"; break;
- case 116: $value .= "\t"; break;
+ case 34:
+ $value .= '"';
+ break;
+ case 47:
+ $value .= '/';
+ break;
+ case 92:
+ $value .= '\\';
+ break;
+ case 98:
+ $value .= chr(8);
+ break; // \b (backspace)
+ case 102:
+ $value .= "\f";
+ break;
+ case 110:
+ $value .= "\n";
+ break;
+ case 114:
+ $value .= "\r";
+ break;
+ case 116:
+ $value .= "\t";
+ break;
case 117:
- $position = $this->position;
+ $position = $this->position;
list ($hex) = $this->readChars(4, true);
- if (!preg_match('/[0-9a-fA-F]{4}/', $hex)) {
+ if (! preg_match('/[0-9a-fA-F]{4}/', $hex)) {
throw new SyntaxError(
$this->source,
$position - 1,
@@ -470,8 +541,8 @@ class Lexer
// Closing Triple-Quote (""")
if ($code === 34) {
// Move 2 quotes
- list(,$nextCode) = $this->moveStringCursor(1, 1)->readChar();
- list(,$nextNextCode) = $this->moveStringCursor(1, 1)->readChar();
+ list(, $nextCode) = $this->moveStringCursor(1, 1)->readChar();
+ list(, $nextNextCode) = $this->moveStringCursor(1, 1)->readChar();
if ($nextCode === 34 && $nextNextCode === 34) {
$value .= $chunk;
@@ -496,9 +567,9 @@ class Lexer
$this->assertValidBlockStringCharacterCode($code, $this->position);
$this->moveStringCursor(1, $bytes);
- list(,$nextCode) = $this->readChar();
- list(,$nextNextCode) = $this->moveStringCursor(1, 1)->readChar();
- list(,$nextNextNextCode) = $this->moveStringCursor(1, 1)->readChar();
+ list(, $nextCode) = $this->readChar();
+ list(, $nextNextCode) = $this->moveStringCursor(1, 1)->readChar();
+ list(, $nextNextNextCode) = $this->moveStringCursor(1, 1)->readChar();
// Escape Triple-Quote (\""")
if ($code === 92 &&
@@ -508,7 +579,7 @@ class Lexer
) {
$this->moveStringCursor(1, 1);
$value .= $chunk . '"""';
- $chunk = '';
+ $chunk = '';
} else {
$this->moveStringCursor(-2, -2);
$chunk .= $char;
@@ -561,11 +632,11 @@ class Lexer
// tab | space | comma | BOM
if ($code === 9 || $code === 32 || $code === 44 || $code === 0xFEFF) {
$this->moveStringCursor(1, $bytes);
- } else if ($code === 10) { // new line
+ } elseif ($code === 10) { // new line
$this->moveStringCursor(1, $bytes);
$this->line++;
$this->lineStart = $this->position;
- } else if ($code === 13) { // carriage return
+ } elseif ($code === 13) { // carriage return
list(, $nextCode, $nextBytes) = $this->moveStringCursor(1, $bytes)->readChar();
if ($nextCode === 10) { // lf after cr
@@ -584,9 +655,8 @@ class Lexer
*
* #[\u0009\u0020-\uFFFF]*
*
- * @param $line
- * @param $col
- * @param Token $prev
+ * @param int $line
+ * @param int $col
* @return Token
*/
private function readComment($line, $col, Token $prev)
@@ -597,11 +667,10 @@ class Lexer
do {
list ($char, $code, $bytes) = $this->moveStringCursor(1, $bytes)->readChar();
- $value .= $char;
- } while (
- $code &&
- // SourceCharacter but not LineTerminator
- ($code > 0x001F || $code === 0x0009)
+ $value .= $char;
+ } while ($code &&
+ // SourceCharacter but not LineTerminator
+ ($code > 0x001F || $code === 0x0009)
);
return new Token(
@@ -619,8 +688,8 @@ class Lexer
* Reads next UTF8Character from the byte stream, starting from $byteStreamPosition.
*
* @param bool $advance
- * @param int $byteStreamPosition
- * @return array
+ * @param int $byteStreamPosition
+ * @return (string|int)[]
*/
private function readChar($advance = false, $byteStreamPosition = null)
{
@@ -628,9 +697,9 @@ class Lexer
$byteStreamPosition = $this->byteStreamPosition;
}
- $code = null;
- $utf8char = '';
- $bytes = 0;
+ $code = null;
+ $utf8char = '';
+ $bytes = 0;
$positionOffset = 0;
if (isset($this->source->body[$byteStreamPosition])) {
@@ -638,7 +707,7 @@ class Lexer
if ($ord < 128) {
$bytes = 1;
- } else if ($ord < 224) {
+ } elseif ($ord < 224) {
$bytes = 2;
} elseif ($ord < 240) {
$bytes = 3;
@@ -651,7 +720,7 @@ class Lexer
$utf8char .= $this->source->body[$pos];
}
$positionOffset = 1;
- $code = $bytes === 1 ? $ord : Utils::ord($utf8char);
+ $code = $bytes === 1 ? $ord : Utils::ord($utf8char);
}
if ($advance) {
@@ -664,40 +733,42 @@ class Lexer
/**
* Reads next $numberOfChars UTF8 characters from the byte stream, starting from $byteStreamPosition.
*
- * @param $numberOfChars
+ * @param int $charCount
* @param bool $advance
* @param null $byteStreamPosition
- * @return array
+ * @return (string|int)[]
*/
- private function readChars($numberOfChars, $advance = false, $byteStreamPosition = null)
+ private function readChars($charCount, $advance = false, $byteStreamPosition = null)
{
- $result = '';
+ $result = '';
$totalBytes = 0;
$byteOffset = $byteStreamPosition ?: $this->byteStreamPosition;
- for ($i = 0; $i < $numberOfChars; $i++) {
+ for ($i = 0; $i < $charCount; $i++) {
list ($char, $code, $bytes) = $this->readChar(false, $byteOffset);
- $totalBytes += $bytes;
- $byteOffset += $bytes;
- $result .= $char;
+ $totalBytes += $bytes;
+ $byteOffset += $bytes;
+ $result .= $char;
}
if ($advance) {
- $this->moveStringCursor($numberOfChars, $totalBytes);
+ $this->moveStringCursor($charCount, $totalBytes);
}
+
return [$result, $totalBytes];
}
/**
* Moves internal string cursor position
*
- * @param $positionOffset
- * @param $byteStreamOffset
+ * @param int $positionOffset
+ * @param int $byteStreamOffset
* @return self
*/
private function moveStringCursor($positionOffset, $byteStreamOffset)
{
- $this->position += $positionOffset;
+ $this->position += $positionOffset;
$this->byteStreamPosition += $byteStreamOffset;
+
return $this;
}
}
diff --git a/src/Language/Parser.php b/src/Language/Parser.php
index b920583..6f0ce0a 100644
--- a/src/Language/Parser.php
+++ b/src/Language/Parser.php
@@ -1,38 +1,43 @@
parseDocument();
}
@@ -121,16 +127,17 @@ class Parser
*
* @api
* @param Source|string $source
- * @param array $options
+ * @param bool[] $options
* @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode
*/
public static function parseValue($source, array $options = [])
{
$sourceObj = $source instanceof Source ? $source : new Source($source);
- $parser = new Parser($sourceObj, $options);
+ $parser = new Parser($sourceObj, $options);
$parser->expect(Token::SOF);
$value = $parser->parseValueLiteral(false);
$parser->expect(Token::EOF);
+
return $value;
}
@@ -146,30 +153,28 @@ class Parser
*
* @api
* @param Source|string $source
- * @param array $options
+ * @param bool[] $options
* @return ListTypeNode|NameNode|NonNullTypeNode
*/
public static function parseType($source, array $options = [])
{
$sourceObj = $source instanceof Source ? $source : new Source($source);
- $parser = new Parser($sourceObj, $options);
+ $parser = new Parser($sourceObj, $options);
$parser->expect(Token::SOF);
$type = $parser->parseTypeReference();
$parser->expect(Token::EOF);
+
return $type;
}
- /**
- * @var Lexer
- */
+ /** @var Lexer */
private $lexer;
/**
- * Parser constructor.
- * @param Source $source
- * @param array $options
+ *
+ * @param bool[] $options
*/
- function __construct(Source $source, array $options = [])
+ public function __construct(Source $source, array $options = [])
{
$this->lexer = new Lexer($source, $options);
}
@@ -178,24 +183,24 @@ class Parser
* Returns a location object, used to identify the place in
* the source that created a given parsed object.
*
- * @param Token $startToken
* @return Location|null
*/
- function loc(Token $startToken)
+ private function loc(Token $startToken)
{
if (empty($this->lexer->options['noLocation'])) {
return new Location($startToken, $this->lexer->lastToken, $this->lexer->source);
}
+
return null;
}
/**
* Determines if the next token is of a given kind
*
- * @param $kind
+ * @param string $kind
* @return bool
*/
- function peek($kind)
+ private function peek($kind)
{
return $this->lexer->token->kind === $kind;
}
@@ -204,16 +209,17 @@ class Parser
* If the next token is of the given kind, return true after advancing
* the parser. Otherwise, do not change the parser state and return false.
*
- * @param $kind
+ * @param string $kind
* @return bool
*/
- function skip($kind)
+ private function skip($kind)
{
$match = $this->lexer->token->kind === $kind;
if ($match) {
$this->lexer->advance();
}
+
return $match;
}
@@ -224,19 +230,20 @@ class Parser
* @return Token
* @throws SyntaxError
*/
- function expect($kind)
+ private function expect($kind)
{
$token = $this->lexer->token;
if ($token->kind === $kind) {
$this->lexer->advance();
+
return $token;
}
throw new SyntaxError(
$this->lexer->source,
$token->start,
- "Expected $kind, found " . $token->getDescription()
+ sprintf('Expected %s, found %s', $kind, $token->getDescription())
);
}
@@ -249,12 +256,13 @@ class Parser
* @return Token
* @throws SyntaxError
*/
- function expectKeyword($value)
+ private function expectKeyword($value)
{
$token = $this->lexer->token;
if ($token->kind === Token::NAME && $token->value === $value) {
$this->lexer->advance();
+
return $token;
}
throw new SyntaxError(
@@ -265,13 +273,13 @@ class Parser
}
/**
- * @param Token|null $atToken
* @return SyntaxError
*/
- function unexpected(Token $atToken = null)
+ private function unexpected(?Token $atToken = null)
{
$token = $atToken ?: $this->lexer->token;
- return new SyntaxError($this->lexer->source, $token->start, "Unexpected " . $token->getDescription());
+
+ return new SyntaxError($this->lexer->source, $token->start, 'Unexpected ' . $token->getDescription());
}
/**
@@ -280,20 +288,21 @@ class Parser
* and ends with a lex token of closeKind. Advances the parser
* to the next lex token after the closing token.
*
- * @param int $openKind
+ * @param string $openKind
* @param callable $parseFn
- * @param int $closeKind
+ * @param string $closeKind
* @return NodeList
* @throws SyntaxError
*/
- function any($openKind, $parseFn, $closeKind)
+ private function any($openKind, $parseFn, $closeKind)
{
$this->expect($openKind);
$nodes = [];
- while (!$this->skip($closeKind)) {
+ while (! $this->skip($closeKind)) {
$nodes[] = $parseFn($this);
}
+
return new NodeList($nodes);
}
@@ -303,20 +312,21 @@ class Parser
* and ends with a lex token of closeKind. Advances the parser
* to the next lex token after the closing token.
*
- * @param $openKind
- * @param $parseFn
- * @param $closeKind
+ * @param string $openKind
+ * @param callable $parseFn
+ * @param string $closeKind
* @return NodeList
* @throws SyntaxError
*/
- function many($openKind, $parseFn, $closeKind)
+ private function many($openKind, $parseFn, $closeKind)
{
$this->expect($openKind);
$nodes = [$parseFn($this)];
- while (!$this->skip($closeKind)) {
+ while (! $this->skip($closeKind)) {
$nodes[] = $parseFn($this);
}
+
return new NodeList($nodes);
}
@@ -326,13 +336,13 @@ class Parser
* @return NameNode
* @throws SyntaxError
*/
- function parseName()
+ private function parseName()
{
$token = $this->expect(Token::NAME);
return new NameNode([
'value' => $token->value,
- 'loc' => $this->loc($token)
+ 'loc' => $this->loc($token),
]);
}
@@ -342,7 +352,7 @@ class Parser
* @return DocumentNode
* @throws SyntaxError
*/
- function parseDocument()
+ private function parseDocument()
{
$start = $this->lexer->token;
$this->expect(Token::SOF);
@@ -350,11 +360,11 @@ class Parser
$definitions = [];
do {
$definitions[] = $this->parseDefinition();
- } while (!$this->skip(Token::EOF));
+ } while (! $this->skip(Token::EOF));
return new DocumentNode([
'definitions' => new NodeList($definitions),
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -362,7 +372,7 @@ class Parser
* @return ExecutableDefinitionNode|TypeSystemDefinitionNode
* @throws SyntaxError
*/
- function parseDefinition()
+ private function parseDefinition()
{
if ($this->peek(Token::NAME)) {
switch ($this->lexer->token->value) {
@@ -385,9 +395,9 @@ class Parser
// Note: The schema definition language is an experimental addition.
return $this->parseTypeSystemDefinition();
}
- } else if ($this->peek(Token::BRACE_L)) {
+ } elseif ($this->peek(Token::BRACE_L)) {
return $this->parseExecutableDefinition();
- } else if ($this->peekDescription()) {
+ } elseif ($this->peekDescription()) {
// Note: The schema definition language is an experimental addition.
return $this->parseTypeSystemDefinition();
}
@@ -399,7 +409,7 @@ class Parser
* @return ExecutableDefinitionNode
* @throws SyntaxError
*/
- function parseExecutableDefinition()
+ private function parseExecutableDefinition()
{
if ($this->peek(Token::NAME)) {
switch ($this->lexer->token->value) {
@@ -411,7 +421,7 @@ class Parser
case 'fragment':
return $this->parseFragmentDefinition();
}
- } else if ($this->peek(Token::BRACE_L)) {
+ } elseif ($this->peek(Token::BRACE_L)) {
return $this->parseOperationDefinition();
}
@@ -424,17 +434,17 @@ class Parser
* @return OperationDefinitionNode
* @throws SyntaxError
*/
- function parseOperationDefinition()
+ private function parseOperationDefinition()
{
$start = $this->lexer->token;
if ($this->peek(Token::BRACE_L)) {
return new OperationDefinitionNode([
- 'operation' => 'query',
- 'name' => null,
+ 'operation' => 'query',
+ 'name' => null,
'variableDefinitions' => new NodeList([]),
- 'directives' => new NodeList([]),
- 'selectionSet' => $this->parseSelectionSet(),
- 'loc' => $this->loc($start)
+ 'directives' => new NodeList([]),
+ 'selectionSet' => $this->parseSelectionSet(),
+ 'loc' => $this->loc($start),
]);
}
@@ -446,12 +456,12 @@ class Parser
}
return new OperationDefinitionNode([
- 'operation' => $operation,
- 'name' => $name,
+ 'operation' => $operation,
+ 'name' => $name,
'variableDefinitions' => $this->parseVariableDefinitions(),
- 'directives' => $this->parseDirectives(false),
- 'selectionSet' => $this->parseSelectionSet(),
- 'loc' => $this->loc($start)
+ 'directives' => $this->parseDirectives(false),
+ 'selectionSet' => $this->parseSelectionSet(),
+ 'loc' => $this->loc($start),
]);
}
@@ -459,13 +469,16 @@ class Parser
* @return string
* @throws SyntaxError
*/
- function parseOperationType()
+ private function parseOperationType()
{
$operationToken = $this->expect(Token::NAME);
switch ($operationToken->value) {
- case 'query': return 'query';
- case 'mutation': return 'mutation';
- case 'subscription': return 'subscription';
+ case 'query':
+ return 'query';
+ case 'mutation':
+ return 'mutation';
+ case 'subscription':
+ return 'subscription';
}
throw $this->unexpected($operationToken);
@@ -474,12 +487,14 @@ class Parser
/**
* @return VariableDefinitionNode[]|NodeList
*/
- function parseVariableDefinitions()
+ private function parseVariableDefinitions()
{
return $this->peek(Token::PAREN_L) ?
$this->many(
Token::PAREN_L,
- [$this, 'parseVariableDefinition'],
+ function () {
+ return $this->parseVariableDefinition();
+ },
Token::PAREN_R
) :
new NodeList([]);
@@ -489,20 +504,20 @@ class Parser
* @return VariableDefinitionNode
* @throws SyntaxError
*/
- function parseVariableDefinition()
+ private function parseVariableDefinition()
{
$start = $this->lexer->token;
- $var = $this->parseVariable();
+ $var = $this->parseVariable();
$this->expect(Token::COLON);
$type = $this->parseTypeReference();
return new VariableDefinitionNode([
- 'variable' => $var,
- 'type' => $type,
+ 'variable' => $var,
+ 'type' => $type,
'defaultValue' =>
($this->skip(Token::EQUALS) ? $this->parseValueLiteral(true) : null),
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -510,27 +525,36 @@ class Parser
* @return VariableNode
* @throws SyntaxError
*/
- function parseVariable()
+ private function parseVariable()
{
$start = $this->lexer->token;
$this->expect(Token::DOLLAR);
return new VariableNode([
'name' => $this->parseName(),
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
/**
* @return SelectionSetNode
*/
- function parseSelectionSet()
+ private function parseSelectionSet()
{
$start = $this->lexer->token;
- return new SelectionSetNode([
- 'selections' => $this->many(Token::BRACE_L, [$this, 'parseSelection'], Token::BRACE_R),
- 'loc' => $this->loc($start)
- ]);
+
+ return new SelectionSetNode(
+ [
+ 'selections' => $this->many(
+ Token::BRACE_L,
+ function () {
+ return $this->parseSelection();
+ },
+ Token::BRACE_R
+ ),
+ 'loc' => $this->loc($start),
+ ]
+ );
}
/**
@@ -541,7 +565,7 @@ class Parser
*
* @return mixed
*/
- function parseSelection()
+ private function parseSelection()
{
return $this->peek(Token::SPREAD) ?
$this->parseFragment() :
@@ -552,26 +576,26 @@ class Parser
* @return FieldNode
* @throws SyntaxError
*/
- function parseField()
+ private function parseField()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$nameOrAlias = $this->parseName();
if ($this->skip(Token::COLON)) {
$alias = $nameOrAlias;
- $name = $this->parseName();
+ $name = $this->parseName();
} else {
$alias = null;
- $name = $nameOrAlias;
+ $name = $nameOrAlias;
}
return new FieldNode([
- 'alias' => $alias,
- 'name' => $name,
- 'arguments' => $this->parseArguments(false),
- 'directives' => $this->parseDirectives(false),
+ 'alias' => $alias,
+ 'name' => $name,
+ 'arguments' => $this->parseArguments(false),
+ 'directives' => $this->parseDirectives(false),
'selectionSet' => $this->peek(Token::BRACE_L) ? $this->parseSelectionSet() : null,
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -580,11 +604,18 @@ class Parser
* @return ArgumentNode[]|NodeList
* @throws SyntaxError
*/
- function parseArguments($isConst)
+ private function parseArguments($isConst)
{
- $item = $isConst ? 'parseConstArgument' : 'parseArgument';
+ $parseFn = $isConst ?
+ function () {
+ return $this->parseConstArgument();
+ } :
+ function () {
+ return $this->parseArgument();
+ };
+
return $this->peek(Token::PAREN_L) ?
- $this->many(Token::PAREN_L, [$this, $item], Token::PAREN_R) :
+ $this->many(Token::PAREN_L, $parseFn, Token::PAREN_R) :
new NodeList([]);
}
@@ -592,18 +623,18 @@ class Parser
* @return ArgumentNode
* @throws SyntaxError
*/
- function parseArgument()
+ private function parseArgument()
{
$start = $this->lexer->token;
- $name = $this->parseName();
+ $name = $this->parseName();
$this->expect(Token::COLON);
$value = $this->parseValueLiteral(false);
return new ArgumentNode([
- 'name' => $name,
+ 'name' => $name,
'value' => $value,
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -611,18 +642,18 @@ class Parser
* @return ArgumentNode
* @throws SyntaxError
*/
- function parseConstArgument()
+ private function parseConstArgument()
{
$start = $this->lexer->token;
- $name = $this->parseName();
+ $name = $this->parseName();
$this->expect(Token::COLON);
$value = $this->parseConstValue();
return new ArgumentNode([
- 'name' => $name,
+ 'name' => $name,
'value' => $value,
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -632,16 +663,16 @@ class Parser
* @return FragmentSpreadNode|InlineFragmentNode
* @throws SyntaxError
*/
- function parseFragment()
+ private function parseFragment()
{
$start = $this->lexer->token;
$this->expect(Token::SPREAD);
if ($this->peek(Token::NAME) && $this->lexer->token->value !== 'on') {
return new FragmentSpreadNode([
- 'name' => $this->parseFragmentName(),
+ 'name' => $this->parseFragmentName(),
'directives' => $this->parseDirectives(false),
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -653,9 +684,9 @@ class Parser
return new InlineFragmentNode([
'typeCondition' => $typeCondition,
- 'directives' => $this->parseDirectives(false),
- 'selectionSet' => $this->parseSelectionSet(),
- 'loc' => $this->loc($start)
+ 'directives' => $this->parseDirectives(false),
+ 'selectionSet' => $this->parseSelectionSet(),
+ 'loc' => $this->loc($start),
]);
}
@@ -663,7 +694,7 @@ class Parser
* @return FragmentDefinitionNode
* @throws SyntaxError
*/
- function parseFragmentDefinition()
+ private function parseFragmentDefinition()
{
$start = $this->lexer->token;
$this->expectKeyword('fragment');
@@ -679,13 +710,14 @@ class Parser
}
$this->expectKeyword('on');
$typeCondition = $this->parseNamedType();
+
return new FragmentDefinitionNode([
- 'name' => $name,
+ 'name' => $name,
'variableDefinitions' => $variableDefinitions,
- 'typeCondition' => $typeCondition,
- 'directives' => $this->parseDirectives(false),
- 'selectionSet' => $this->parseSelectionSet(),
- 'loc' => $this->loc($start)
+ 'typeCondition' => $typeCondition,
+ 'directives' => $this->parseDirectives(false),
+ 'selectionSet' => $this->parseSelectionSet(),
+ 'loc' => $this->loc($start),
]);
}
@@ -693,11 +725,12 @@ class Parser
* @return NameNode
* @throws SyntaxError
*/
- function parseFragmentName()
+ private function parseFragmentName()
{
if ($this->lexer->token->value === 'on') {
throw $this->unexpected();
}
+
return $this->parseName();
}
@@ -721,11 +754,11 @@ class Parser
*
* EnumValue : Name but not `true`, `false` or `null`
*
- * @param $isConst
+ * @param bool $isConst
* @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode|ListValueNode|ObjectValueNode|NullValueNode
* @throws SyntaxError
*/
- function parseValueLiteral($isConst)
+ private function parseValueLiteral($isConst)
{
$token = $this->lexer->token;
switch ($token->kind) {
@@ -735,15 +768,17 @@ class Parser
return $this->parseObject($isConst);
case Token::INT:
$this->lexer->advance();
+
return new IntValueNode([
'value' => $token->value,
- 'loc' => $this->loc($token)
+ 'loc' => $this->loc($token),
]);
case Token::FLOAT:
$this->lexer->advance();
+
return new FloatValueNode([
'value' => $token->value,
- 'loc' => $this->loc($token)
+ 'loc' => $this->loc($token),
]);
case Token::STRING:
case Token::BLOCK_STRING:
@@ -751,26 +786,29 @@ class Parser
case Token::NAME:
if ($token->value === 'true' || $token->value === 'false') {
$this->lexer->advance();
+
return new BooleanValueNode([
'value' => $token->value === 'true',
- 'loc' => $this->loc($token)
+ 'loc' => $this->loc($token),
]);
- } else if ($token->value === 'null') {
+ } elseif ($token->value === 'null') {
$this->lexer->advance();
+
return new NullValueNode([
- 'loc' => $this->loc($token)
+ 'loc' => $this->loc($token),
]);
} else {
$this->lexer->advance();
+
return new EnumValueNode([
'value' => $token->value,
- 'loc' => $this->loc($token)
+ 'loc' => $this->loc($token),
]);
}
break;
case Token::DOLLAR:
- if (!$isConst) {
+ if (! $isConst) {
return $this->parseVariable();
}
break;
@@ -781,14 +819,15 @@ class Parser
/**
* @return StringValueNode
*/
- function parseStringLiteral() {
+ private function parseStringLiteral()
+ {
$token = $this->lexer->token;
$this->lexer->advance();
return new StringValueNode([
'value' => $token->value,
'block' => $token->kind === Token::BLOCK_STRING,
- 'loc' => $this->loc($token)
+ 'loc' => $this->loc($token),
]);
}
@@ -796,7 +835,7 @@ class Parser
* @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode
* @throws SyntaxError
*/
- function parseConstValue()
+ private function parseConstValue()
{
return $this->parseValueLiteral(true);
}
@@ -804,7 +843,7 @@ class Parser
/**
* @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode
*/
- function parseVariableValue()
+ private function parseVariableValue()
{
return $this->parseValueLiteral(false);
}
@@ -813,49 +852,57 @@ class Parser
* @param bool $isConst
* @return ListValueNode
*/
- function parseArray($isConst)
+ private function parseArray($isConst)
{
- $start = $this->lexer->token;
- $item = $isConst ? 'parseConstValue' : 'parseVariableValue';
- return new ListValueNode([
- 'values' => $this->any(Token::BRACKET_L, [$this, $item], Token::BRACKET_R),
- 'loc' => $this->loc($start)
- ]);
+ $start = $this->lexer->token;
+ $parseFn = $isConst ? function () {
+ return $this->parseConstValue();
+ } : function () {
+ return $this->parseVariableValue();
+ };
+
+ return new ListValueNode(
+ [
+ 'values' => $this->any(Token::BRACKET_L, $parseFn, Token::BRACKET_R),
+ 'loc' => $this->loc($start),
+ ]
+ );
}
/**
- * @param $isConst
+ * @param bool $isConst
* @return ObjectValueNode
*/
- function parseObject($isConst)
+ private function parseObject($isConst)
{
$start = $this->lexer->token;
$this->expect(Token::BRACE_L);
$fields = [];
- while (!$this->skip(Token::BRACE_R)) {
+ while (! $this->skip(Token::BRACE_R)) {
$fields[] = $this->parseObjectField($isConst);
}
+
return new ObjectValueNode([
'fields' => new NodeList($fields),
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
/**
- * @param $isConst
+ * @param bool $isConst
* @return ObjectFieldNode
*/
- function parseObjectField($isConst)
+ private function parseObjectField($isConst)
{
$start = $this->lexer->token;
- $name = $this->parseName();
+ $name = $this->parseName();
$this->expect(Token::COLON);
return new ObjectFieldNode([
- 'name' => $name,
+ 'name' => $name,
'value' => $this->parseValueLiteral($isConst),
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -866,12 +913,13 @@ class Parser
* @return DirectiveNode[]|NodeList
* @throws SyntaxError
*/
- function parseDirectives($isConst)
+ private function parseDirectives($isConst)
{
$directives = [];
while ($this->peek(Token::AT)) {
$directives[] = $this->parseDirective($isConst);
}
+
return new NodeList($directives);
}
@@ -880,14 +928,15 @@ class Parser
* @return DirectiveNode
* @throws SyntaxError
*/
- function parseDirective($isConst)
+ private function parseDirective($isConst)
{
$start = $this->lexer->token;
$this->expect(Token::AT);
+
return new DirectiveNode([
- 'name' => $this->parseName(),
+ 'name' => $this->parseName(),
'arguments' => $this->parseArguments($isConst),
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -899,7 +948,7 @@ class Parser
* @return ListTypeNode|NameNode|NonNullTypeNode
* @throws SyntaxError
*/
- function parseTypeReference()
+ private function parseTypeReference()
{
$start = $this->lexer->token;
@@ -908,7 +957,7 @@ class Parser
$this->expect(Token::BRACKET_R);
$type = new ListTypeNode([
'type' => $type,
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
} else {
$type = $this->parseNamedType();
@@ -916,20 +965,20 @@ class Parser
if ($this->skip(Token::BANG)) {
return new NonNullTypeNode([
'type' => $type,
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
-
}
+
return $type;
}
- function parseNamedType()
+ private function parseNamedType()
{
$start = $this->lexer->token;
return new NamedTypeNode([
'name' => $this->parseName(),
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -953,7 +1002,7 @@ class Parser
* @return TypeSystemDefinitionNode
* @throws SyntaxError
*/
- function parseTypeSystemDefinition()
+ private function parseTypeSystemDefinition()
{
// Many definitions begin with a description and require a lookahead.
$keywordToken = $this->peekDescription()
@@ -962,15 +1011,24 @@ class Parser
if ($keywordToken->kind === Token::NAME) {
switch ($keywordToken->value) {
- case 'schema': return $this->parseSchemaDefinition();
- case 'scalar': return $this->parseScalarTypeDefinition();
- case 'type': return $this->parseObjectTypeDefinition();
- case 'interface': return $this->parseInterfaceTypeDefinition();
- case 'union': return $this->parseUnionTypeDefinition();
- case 'enum': return $this->parseEnumTypeDefinition();
- case 'input': return $this->parseInputObjectTypeDefinition();
- case 'extend': return $this->parseTypeExtension();
- case 'directive': return $this->parseDirectiveDefinition();
+ case 'schema':
+ return $this->parseSchemaDefinition();
+ case 'scalar':
+ return $this->parseScalarTypeDefinition();
+ case 'type':
+ return $this->parseObjectTypeDefinition();
+ case 'interface':
+ return $this->parseInterfaceTypeDefinition();
+ case 'union':
+ return $this->parseUnionTypeDefinition();
+ case 'enum':
+ return $this->parseEnumTypeDefinition();
+ case 'input':
+ return $this->parseInputObjectTypeDefinition();
+ case 'extend':
+ return $this->parseTypeExtension();
+ case 'directive':
+ return $this->parseDirectiveDefinition();
}
}
@@ -980,14 +1038,16 @@ class Parser
/**
* @return bool
*/
- function peekDescription() {
+ private function peekDescription()
+ {
return $this->peek(Token::STRING) || $this->peek(Token::BLOCK_STRING);
}
/**
* @return StringValueNode|null
*/
- function parseDescription() {
+ private function parseDescription()
+ {
if ($this->peekDescription()) {
return $this->parseStringLiteral();
}
@@ -997,7 +1057,7 @@ class Parser
* @return SchemaDefinitionNode
* @throws SyntaxError
*/
- function parseSchemaDefinition()
+ private function parseSchemaDefinition()
{
$start = $this->lexer->token;
$this->expectKeyword('schema');
@@ -1005,14 +1065,16 @@ class Parser
$operationTypes = $this->many(
Token::BRACE_L,
- [$this, 'parseOperationTypeDefinition'],
+ function () {
+ return $this->parseOperationTypeDefinition();
+ },
Token::BRACE_R
);
return new SchemaDefinitionNode([
- 'directives' => $directives,
+ 'directives' => $directives,
'operationTypes' => $operationTypes,
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -1020,17 +1082,17 @@ class Parser
* @return OperationTypeDefinitionNode
* @throws SyntaxError
*/
- function parseOperationTypeDefinition()
+ private function parseOperationTypeDefinition()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$operation = $this->parseOperationType();
$this->expect(Token::COLON);
$type = $this->parseNamedType();
return new OperationTypeDefinitionNode([
'operation' => $operation,
- 'type' => $type,
- 'loc' => $this->loc($start)
+ 'type' => $type,
+ 'loc' => $this->loc($start),
]);
}
@@ -1038,19 +1100,19 @@ class Parser
* @return ScalarTypeDefinitionNode
* @throws SyntaxError
*/
- function parseScalarTypeDefinition()
+ private function parseScalarTypeDefinition()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$description = $this->parseDescription();
$this->expectKeyword('scalar');
- $name = $this->parseName();
+ $name = $this->parseName();
$directives = $this->parseDirectives(true);
return new ScalarTypeDefinitionNode([
- 'name' => $name,
- 'directives' => $directives,
- 'loc' => $this->loc($start),
- 'description' => $description
+ 'name' => $name,
+ 'directives' => $directives,
+ 'loc' => $this->loc($start),
+ 'description' => $description,
]);
}
@@ -1058,23 +1120,23 @@ class Parser
* @return ObjectTypeDefinitionNode
* @throws SyntaxError
*/
- function parseObjectTypeDefinition()
+ private function parseObjectTypeDefinition()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$description = $this->parseDescription();
$this->expectKeyword('type');
- $name = $this->parseName();
+ $name = $this->parseName();
$interfaces = $this->parseImplementsInterfaces();
$directives = $this->parseDirectives(true);
- $fields = $this->parseFieldsDefinition();
+ $fields = $this->parseFieldsDefinition();
return new ObjectTypeDefinitionNode([
- 'name' => $name,
- 'interfaces' => $interfaces,
- 'directives' => $directives,
- 'fields' => $fields,
- 'loc' => $this->loc($start),
- 'description' => $description
+ 'name' => $name,
+ 'interfaces' => $interfaces,
+ 'directives' => $directives,
+ 'fields' => $fields,
+ 'loc' => $this->loc($start),
+ 'description' => $description,
]);
}
@@ -1085,7 +1147,7 @@ class Parser
*
* @return NamedTypeNode[]
*/
- function parseImplementsInterfaces()
+ private function parseImplementsInterfaces()
{
$types = [];
if ($this->lexer->token->value === 'implements') {
@@ -1094,12 +1156,12 @@ class Parser
$this->skip(Token::AMP);
do {
$types[] = $this->parseNamedType();
- } while (
- $this->skip(Token::AMP) ||
- // Legacy support for the SDL?
- (!empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME))
+ } while ($this->skip(Token::AMP) ||
+ // Legacy support for the SDL?
+ (! empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME))
);
}
+
return $types;
}
@@ -1107,22 +1169,25 @@ class Parser
* @return FieldDefinitionNode[]|NodeList
* @throws SyntaxError
*/
- function parseFieldsDefinition()
+ private function parseFieldsDefinition()
{
// Legacy support for the SDL?
- if (
- !empty($this->lexer->options['allowLegacySDLEmptyFields']) &&
+ if (! empty($this->lexer->options['allowLegacySDLEmptyFields']) &&
$this->peek(Token::BRACE_L) &&
$this->lexer->lookahead()->kind === Token::BRACE_R
) {
$this->lexer->advance();
$this->lexer->advance();
+
return [];
}
+
return $this->peek(Token::BRACE_L)
? $this->many(
Token::BRACE_L,
- [$this, 'parseFieldDefinition'],
+ function () {
+ return $this->parseFieldDefinition();
+ },
Token::BRACE_R
)
: new NodeList([]);
@@ -1132,23 +1197,23 @@ class Parser
* @return FieldDefinitionNode
* @throws SyntaxError
*/
- function parseFieldDefinition()
+ private function parseFieldDefinition()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$description = $this->parseDescription();
- $name = $this->parseName();
- $args = $this->parseArgumentDefs();
+ $name = $this->parseName();
+ $args = $this->parseArgumentDefs();
$this->expect(Token::COLON);
- $type = $this->parseTypeReference();
+ $type = $this->parseTypeReference();
$directives = $this->parseDirectives(true);
return new FieldDefinitionNode([
- 'name' => $name,
- 'arguments' => $args,
- 'type' => $type,
- 'directives' => $directives,
- 'loc' => $this->loc($start),
- 'description' => $description
+ 'name' => $name,
+ 'arguments' => $args,
+ 'type' => $type,
+ 'directives' => $directives,
+ 'loc' => $this->loc($start),
+ 'description' => $description,
]);
}
@@ -1156,37 +1221,45 @@ class Parser
* @return InputValueDefinitionNode[]|NodeList
* @throws SyntaxError
*/
- function parseArgumentDefs()
+ private function parseArgumentDefs()
{
- if (!$this->peek(Token::PAREN_L)) {
+ if (! $this->peek(Token::PAREN_L)) {
return new NodeList([]);
}
- return $this->many(Token::PAREN_L, [$this, 'parseInputValueDef'], Token::PAREN_R);
+
+ return $this->many(
+ Token::PAREN_L,
+ function () {
+ return $this->parseInputValueDef();
+ },
+ Token::PAREN_R
+ );
}
/**
* @return InputValueDefinitionNode
* @throws SyntaxError
*/
- function parseInputValueDef()
+ private function parseInputValueDef()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$description = $this->parseDescription();
- $name = $this->parseName();
+ $name = $this->parseName();
$this->expect(Token::COLON);
- $type = $this->parseTypeReference();
+ $type = $this->parseTypeReference();
$defaultValue = null;
if ($this->skip(Token::EQUALS)) {
$defaultValue = $this->parseConstValue();
}
$directives = $this->parseDirectives(true);
+
return new InputValueDefinitionNode([
- 'name' => $name,
- 'type' => $type,
+ 'name' => $name,
+ 'type' => $type,
'defaultValue' => $defaultValue,
- 'directives' => $directives,
- 'loc' => $this->loc($start),
- 'description' => $description
+ 'directives' => $directives,
+ 'loc' => $this->loc($start),
+ 'description' => $description,
]);
}
@@ -1194,21 +1267,21 @@ class Parser
* @return InterfaceTypeDefinitionNode
* @throws SyntaxError
*/
- function parseInterfaceTypeDefinition()
+ private function parseInterfaceTypeDefinition()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$description = $this->parseDescription();
$this->expectKeyword('interface');
- $name = $this->parseName();
+ $name = $this->parseName();
$directives = $this->parseDirectives(true);
- $fields = $this->parseFieldsDefinition();
+ $fields = $this->parseFieldsDefinition();
return new InterfaceTypeDefinitionNode([
- 'name' => $name,
- 'directives' => $directives,
- 'fields' => $fields,
- 'loc' => $this->loc($start),
- 'description' => $description
+ 'name' => $name,
+ 'directives' => $directives,
+ 'fields' => $fields,
+ 'loc' => $this->loc($start),
+ 'description' => $description,
]);
}
@@ -1219,21 +1292,21 @@ class Parser
* @return UnionTypeDefinitionNode
* @throws SyntaxError
*/
- function parseUnionTypeDefinition()
+ private function parseUnionTypeDefinition()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$description = $this->parseDescription();
$this->expectKeyword('union');
- $name = $this->parseName();
+ $name = $this->parseName();
$directives = $this->parseDirectives(true);
- $types = $this->parseUnionMemberTypes();
+ $types = $this->parseUnionMemberTypes();
return new UnionTypeDefinitionNode([
- 'name' => $name,
- 'directives' => $directives,
- 'types' => $types,
- 'loc' => $this->loc($start),
- 'description' => $description
+ 'name' => $name,
+ 'directives' => $directives,
+ 'types' => $types,
+ 'loc' => $this->loc($start),
+ 'description' => $description,
]);
}
@@ -1244,7 +1317,7 @@ class Parser
*
* @return NamedTypeNode[]
*/
- function parseUnionMemberTypes()
+ private function parseUnionMemberTypes()
{
$types = [];
if ($this->skip(Token::EQUALS)) {
@@ -1254,6 +1327,7 @@ class Parser
$types[] = $this->parseNamedType();
} while ($this->skip(Token::PIPE));
}
+
return $types;
}
@@ -1261,21 +1335,21 @@ class Parser
* @return EnumTypeDefinitionNode
* @throws SyntaxError
*/
- function parseEnumTypeDefinition()
+ private function parseEnumTypeDefinition()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$description = $this->parseDescription();
$this->expectKeyword('enum');
- $name = $this->parseName();
+ $name = $this->parseName();
$directives = $this->parseDirectives(true);
- $values = $this->parseEnumValuesDefinition();
+ $values = $this->parseEnumValuesDefinition();
return new EnumTypeDefinitionNode([
- 'name' => $name,
- 'directives' => $directives,
- 'values' => $values,
- 'loc' => $this->loc($start),
- 'description' => $description
+ 'name' => $name,
+ 'directives' => $directives,
+ 'values' => $values,
+ 'loc' => $this->loc($start),
+ 'description' => $description,
]);
}
@@ -1283,14 +1357,16 @@ class Parser
* @return EnumValueDefinitionNode[]|NodeList
* @throws SyntaxError
*/
- function parseEnumValuesDefinition()
+ private function parseEnumValuesDefinition()
{
return $this->peek(Token::BRACE_L)
? $this->many(
Token::BRACE_L,
- [$this, 'parseEnumValueDefinition'],
+ function () {
+ return $this->parseEnumValueDefinition();
+ },
Token::BRACE_R
- )
+ )
: new NodeList([]);
}
@@ -1298,18 +1374,18 @@ class Parser
* @return EnumValueDefinitionNode
* @throws SyntaxError
*/
- function parseEnumValueDefinition()
+ private function parseEnumValueDefinition()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$description = $this->parseDescription();
- $name = $this->parseName();
- $directives = $this->parseDirectives(true);
+ $name = $this->parseName();
+ $directives = $this->parseDirectives(true);
return new EnumValueDefinitionNode([
- 'name' => $name,
- 'directives' => $directives,
- 'loc' => $this->loc($start),
- 'description' => $description
+ 'name' => $name,
+ 'directives' => $directives,
+ 'loc' => $this->loc($start),
+ 'description' => $description,
]);
}
@@ -1317,21 +1393,21 @@ class Parser
* @return InputObjectTypeDefinitionNode
* @throws SyntaxError
*/
- function parseInputObjectTypeDefinition()
+ private function parseInputObjectTypeDefinition()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$description = $this->parseDescription();
$this->expectKeyword('input');
- $name = $this->parseName();
+ $name = $this->parseName();
$directives = $this->parseDirectives(true);
- $fields = $this->parseInputFieldsDefinition();
+ $fields = $this->parseInputFieldsDefinition();
return new InputObjectTypeDefinitionNode([
- 'name' => $name,
- 'directives' => $directives,
- 'fields' => $fields,
- 'loc' => $this->loc($start),
- 'description' => $description
+ 'name' => $name,
+ 'directives' => $directives,
+ 'fields' => $fields,
+ 'loc' => $this->loc($start),
+ 'description' => $description,
]);
}
@@ -1339,11 +1415,14 @@ class Parser
* @return InputValueDefinitionNode[]|NodeList
* @throws SyntaxError
*/
- function parseInputFieldsDefinition() {
+ private function parseInputFieldsDefinition()
+ {
return $this->peek(Token::BRACE_L)
? $this->many(
Token::BRACE_L,
- [$this, 'parseInputValueDef'],
+ function () {
+ return $this->parseInputValueDef();
+ },
Token::BRACE_R
)
: new NodeList([]);
@@ -1361,7 +1440,7 @@ class Parser
* @return TypeExtensionNode
* @throws SyntaxError
*/
- function parseTypeExtension()
+ private function parseTypeExtension()
{
$keywordToken = $this->lexer->lookahead();
@@ -1389,20 +1468,21 @@ class Parser
* @return ScalarTypeExtensionNode
* @throws SyntaxError
*/
- function parseScalarTypeExtension() {
+ private function parseScalarTypeExtension()
+ {
$start = $this->lexer->token;
$this->expectKeyword('extend');
$this->expectKeyword('scalar');
- $name = $this->parseName();
+ $name = $this->parseName();
$directives = $this->parseDirectives(true);
if (count($directives) === 0) {
throw $this->unexpected();
}
return new ScalarTypeExtensionNode([
- 'name' => $name,
+ 'name' => $name,
'directives' => $directives,
- 'loc' => $this->loc($start)
+ 'loc' => $this->loc($start),
]);
}
@@ -1410,17 +1490,17 @@ class Parser
* @return ObjectTypeExtensionNode
* @throws SyntaxError
*/
- function parseObjectTypeExtension() {
+ private function parseObjectTypeExtension()
+ {
$start = $this->lexer->token;
$this->expectKeyword('extend');
$this->expectKeyword('type');
- $name = $this->parseName();
+ $name = $this->parseName();
$interfaces = $this->parseImplementsInterfaces();
$directives = $this->parseDirectives(true);
- $fields = $this->parseFieldsDefinition();
+ $fields = $this->parseFieldsDefinition();
- if (
- !$interfaces &&
+ if (count($interfaces) === 0 &&
count($directives) === 0 &&
count($fields) === 0
) {
@@ -1428,11 +1508,11 @@ class Parser
}
return new ObjectTypeExtensionNode([
- 'name' => $name,
+ 'name' => $name,
'interfaces' => $interfaces,
'directives' => $directives,
- 'fields' => $fields,
- 'loc' => $this->loc($start)
+ 'fields' => $fields,
+ 'loc' => $this->loc($start),
]);
}
@@ -1440,25 +1520,25 @@ class Parser
* @return InterfaceTypeExtensionNode
* @throws SyntaxError
*/
- function parseInterfaceTypeExtension() {
+ private function parseInterfaceTypeExtension()
+ {
$start = $this->lexer->token;
$this->expectKeyword('extend');
$this->expectKeyword('interface');
- $name = $this->parseName();
+ $name = $this->parseName();
$directives = $this->parseDirectives(true);
- $fields = $this->parseFieldsDefinition();
- if (
- count($directives) === 0 &&
+ $fields = $this->parseFieldsDefinition();
+ if (count($directives) === 0 &&
count($fields) === 0
) {
throw $this->unexpected();
}
return new InterfaceTypeExtensionNode([
- 'name' => $name,
+ 'name' => $name,
'directives' => $directives,
- 'fields' => $fields,
- 'loc' => $this->loc($start)
+ 'fields' => $fields,
+ 'loc' => $this->loc($start),
]);
}
@@ -1470,25 +1550,25 @@ class Parser
* @return UnionTypeExtensionNode
* @throws SyntaxError
*/
- function parseUnionTypeExtension() {
+ private function parseUnionTypeExtension()
+ {
$start = $this->lexer->token;
$this->expectKeyword('extend');
$this->expectKeyword('union');
- $name = $this->parseName();
+ $name = $this->parseName();
$directives = $this->parseDirectives(true);
- $types = $this->parseUnionMemberTypes();
- if (
- count($directives) === 0 &&
- !$types
+ $types = $this->parseUnionMemberTypes();
+ if (count($directives) === 0 &&
+ ! $types
) {
throw $this->unexpected();
}
return new UnionTypeExtensionNode([
- 'name' => $name,
+ 'name' => $name,
'directives' => $directives,
- 'types' => $types,
- 'loc' => $this->loc($start)
+ 'types' => $types,
+ 'loc' => $this->loc($start),
]);
}
@@ -1496,25 +1576,25 @@ class Parser
* @return EnumTypeExtensionNode
* @throws SyntaxError
*/
- function parseEnumTypeExtension() {
+ private function parseEnumTypeExtension()
+ {
$start = $this->lexer->token;
$this->expectKeyword('extend');
$this->expectKeyword('enum');
- $name = $this->parseName();
+ $name = $this->parseName();
$directives = $this->parseDirectives(true);
- $values = $this->parseEnumValuesDefinition();
- if (
- count($directives) === 0 &&
+ $values = $this->parseEnumValuesDefinition();
+ if (count($directives) === 0 &&
count($values) === 0
) {
throw $this->unexpected();
}
return new EnumTypeExtensionNode([
- 'name' => $name,
+ 'name' => $name,
'directives' => $directives,
- 'values' => $values,
- 'loc' => $this->loc($start)
+ 'values' => $values,
+ 'loc' => $this->loc($start),
]);
}
@@ -1522,25 +1602,25 @@ class Parser
* @return InputObjectTypeExtensionNode
* @throws SyntaxError
*/
- function parseInputObjectTypeExtension() {
+ private function parseInputObjectTypeExtension()
+ {
$start = $this->lexer->token;
$this->expectKeyword('extend');
$this->expectKeyword('input');
- $name = $this->parseName();
+ $name = $this->parseName();
$directives = $this->parseDirectives(true);
- $fields = $this->parseInputFieldsDefinition();
- if (
- count($directives) === 0 &&
+ $fields = $this->parseInputFieldsDefinition();
+ if (count($directives) === 0 &&
count($fields) === 0
) {
throw $this->unexpected();
}
return new InputObjectTypeExtensionNode([
- 'name' => $name,
+ 'name' => $name,
'directives' => $directives,
- 'fields' => $fields,
- 'loc' => $this->loc($start)
+ 'fields' => $fields,
+ 'loc' => $this->loc($start),
]);
}
@@ -1551,9 +1631,9 @@ class Parser
* @return DirectiveDefinitionNode
* @throws SyntaxError
*/
- function parseDirectiveDefinition()
+ private function parseDirectiveDefinition()
{
- $start = $this->lexer->token;
+ $start = $this->lexer->token;
$description = $this->parseDescription();
$this->expectKeyword('directive');
$this->expect(Token::AT);
@@ -1563,11 +1643,11 @@ class Parser
$locations = $this->parseDirectiveLocations();
return new DirectiveDefinitionNode([
- 'name' => $name,
- 'arguments' => $args,
- 'locations' => $locations,
- 'loc' => $this->loc($start),
- 'description' => $description
+ 'name' => $name,
+ 'arguments' => $args,
+ 'locations' => $locations,
+ 'loc' => $this->loc($start),
+ 'description' => $description,
]);
}
@@ -1575,7 +1655,7 @@ class Parser
* @return NameNode[]
* @throws SyntaxError
*/
- function parseDirectiveLocations()
+ private function parseDirectiveLocations()
{
// Optional leading pipe
$this->skip(Token::PIPE);
@@ -1583,6 +1663,7 @@ class Parser
do {
$locations[] = $this->parseDirectiveLocation();
} while ($this->skip(Token::PIPE));
+
return $locations;
}
@@ -1590,10 +1671,10 @@ class Parser
* @return NameNode
* @throws SyntaxError
*/
- function parseDirectiveLocation()
+ private function parseDirectiveLocation()
{
$start = $this->lexer->token;
- $name = $this->parseName();
+ $name = $this->parseName();
if (DirectiveLocation::has($name->value)) {
return $name;
}
diff --git a/src/Language/Printer.php b/src/Language/Printer.php
index 49ea385..3c9873f 100644
--- a/src/Language/Printer.php
+++ b/src/Language/Printer.php
@@ -1,29 +1,32 @@
printAST($ast);
}
protected function __construct()
- {}
+ {
+ }
public function printAST($ast)
{
- return Visitor::visit($ast, [
- 'leave' => [
- NodeKind::NAME => function(Node $node) {
- return '' . $node->value;
- },
- NodeKind::VARIABLE => function($node) {
- return '$' . $node->name;
- },
- NodeKind::DOCUMENT => function(DocumentNode $node) {
- return $this->join($node->definitions, "\n\n") . "\n";
- },
- NodeKind::OPERATION_DEFINITION => function(OperationDefinitionNode $node) {
- $op = $node->operation;
- $name = $node->name;
- $varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
- $directives = $this->join($node->directives, ' ');
- $selectionSet = $node->selectionSet;
- // Anonymous queries with no directives or variable definitions can use
- // the query short form.
- return !$name && !$directives && !$varDefs && $op === 'query'
- ? $selectionSet
- : $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' ');
- },
- NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $node) {
- return $node->variable . ': ' . $node->type . $this->wrap(' = ', $node->defaultValue);
- },
- NodeKind::SELECTION_SET => function(SelectionSetNode $node) {
- return $this->block($node->selections);
- },
- NodeKind::FIELD => function(FieldNode $node) {
- return $this->join([
- $this->wrap('', $node->alias, ': ') . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')'),
- $this->join($node->directives, ' '),
- $node->selectionSet
- ], ' ');
- },
- NodeKind::ARGUMENT => function(ArgumentNode $node) {
- return $node->name . ': ' . $node->value;
- },
+ return Visitor::visit(
+ $ast,
+ [
+ 'leave' => [
+ NodeKind::NAME => function (Node $node) {
+ return '' . $node->value;
+ },
- // Fragments
- NodeKind::FRAGMENT_SPREAD => function(FragmentSpreadNode $node) {
- return '...' . $node->name . $this->wrap(' ', $this->join($node->directives, ' '));
- },
- NodeKind::INLINE_FRAGMENT => function(InlineFragmentNode $node) {
- return $this->join([
- "...",
- $this->wrap('on ', $node->typeCondition),
- $this->join($node->directives, ' '),
- $node->selectionSet
- ], ' ');
- },
- NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) {
- // Note: fragment variable definitions are experimental and may be changed
- // or removed in the future.
- return "fragment {$node->name}"
- . $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')')
- . " on {$node->typeCondition} "
- . $this->wrap('', $this->join($node->directives, ' '), ' ')
- . $node->selectionSet;
- },
+ NodeKind::VARIABLE => function ($node) {
+ return '$' . $node->name;
+ },
- // Value
- NodeKind::INT => function(IntValueNode $node) {
- return $node->value;
- },
- NodeKind::FLOAT => function(FloatValueNode $node) {
- return $node->value;
- },
- NodeKind::STRING => function(StringValueNode $node, $key) {
- if ($node->block) {
- return $this->printBlockString($node->value, $key === 'description');
- }
- return json_encode($node->value);
- },
- NodeKind::BOOLEAN => function(BooleanValueNode $node) {
- return $node->value ? 'true' : 'false';
- },
- NodeKind::NULL => function(NullValueNode $node) {
- return 'null';
- },
- NodeKind::ENUM => function(EnumValueNode $node) {
- return $node->value;
- },
- NodeKind::LST => function(ListValueNode $node) {
- return '[' . $this->join($node->values, ', ') . ']';
- },
- NodeKind::OBJECT => function(ObjectValueNode $node) {
- return '{' . $this->join($node->fields, ', ') . '}';
- },
- NodeKind::OBJECT_FIELD => function(ObjectFieldNode $node) {
- return $node->name . ': ' . $node->value;
- },
+ NodeKind::DOCUMENT => function (DocumentNode $node) {
+ return $this->join($node->definitions, "\n\n") . "\n";
+ },
- // DirectiveNode
- NodeKind::DIRECTIVE => function(DirectiveNode $node) {
- return '@' . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')');
- },
+ NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) {
+ $op = $node->operation;
+ $name = $node->name;
+ $varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
+ $directives = $this->join($node->directives, ' ');
+ $selectionSet = $node->selectionSet;
+ // Anonymous queries with no directives or variable definitions can use
+ // the query short form.
+ return ! $name && ! $directives && ! $varDefs && $op === 'query'
+ ? $selectionSet
+ : $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' ');
+ },
- // Type
- NodeKind::NAMED_TYPE => function(NamedTypeNode $node) {
- return $node->name;
- },
- NodeKind::LIST_TYPE => function(ListTypeNode $node) {
- return '[' . $node->type . ']';
- },
- NodeKind::NON_NULL_TYPE => function(NonNullTypeNode $node) {
- return $node->type . '!';
- },
+ NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) {
+ return $node->variable . ': ' . $node->type . $this->wrap(' = ', $node->defaultValue);
+ },
- // Type System Definitions
- NodeKind::SCHEMA_DEFINITION => function(SchemaDefinitionNode $def) {
- return $this->join([
- 'schema',
- $this->join($def->directives, ' '),
- $this->block($def->operationTypes)
- ], ' ');
- },
- NodeKind::OPERATION_TYPE_DEFINITION => function(OperationTypeDefinitionNode $def) {
- return $def->operation . ': ' . $def->type;
- },
+ NodeKind::SELECTION_SET => function (SelectionSetNode $node) {
+ return $this->block($node->selections);
+ },
- NodeKind::SCALAR_TYPE_DEFINITION => $this->addDescription(function(ScalarTypeDefinitionNode $def) {
- return $this->join(['scalar', $def->name, $this->join($def->directives, ' ')], ' ');
- }),
- NodeKind::OBJECT_TYPE_DEFINITION => $this->addDescription(function(ObjectTypeDefinitionNode $def) {
- return $this->join([
- 'type',
- $def->name,
- $this->wrap('implements ', $this->join($def->interfaces, ' & ')),
- $this->join($def->directives, ' '),
- $this->block($def->fields)
- ], ' ');
- }),
- NodeKind::FIELD_DEFINITION => $this->addDescription(function(FieldDefinitionNode $def) {
- return $def->name
- . $this->wrap('(', $this->join($def->arguments, ', '), ')')
- . ': ' . $def->type
- . $this->wrap(' ', $this->join($def->directives, ' '));
- }),
- NodeKind::INPUT_VALUE_DEFINITION => $this->addDescription(function(InputValueDefinitionNode $def) {
- return $this->join([
- $def->name . ': ' . $def->type,
- $this->wrap('= ', $def->defaultValue),
- $this->join($def->directives, ' ')
- ], ' ');
- }),
- NodeKind::INTERFACE_TYPE_DEFINITION => $this->addDescription(function(InterfaceTypeDefinitionNode $def) {
- return $this->join([
- 'interface',
- $def->name,
- $this->join($def->directives, ' '),
- $this->block($def->fields)
- ], ' ');
- }),
- NodeKind::UNION_TYPE_DEFINITION => $this->addDescription(function(UnionTypeDefinitionNode $def) {
- return $this->join([
- 'union',
- $def->name,
- $this->join($def->directives, ' '),
- $def->types
- ? '= ' . $this->join($def->types, ' | ')
- : ''
- ], ' ');
- }),
- NodeKind::ENUM_TYPE_DEFINITION => $this->addDescription(function(EnumTypeDefinitionNode $def) {
- return $this->join([
- 'enum',
- $def->name,
- $this->join($def->directives, ' '),
- $this->block($def->values)
- ], ' ');
- }),
- NodeKind::ENUM_VALUE_DEFINITION => $this->addDescription(function(EnumValueDefinitionNode $def) {
- return $this->join([$def->name, $this->join($def->directives, ' ')], ' ');
- }),
- NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $this->addDescription(function(InputObjectTypeDefinitionNode $def) {
- return $this->join([
- 'input',
- $def->name,
- $this->join($def->directives, ' '),
- $this->block($def->fields)
- ], ' ');
- }),
- NodeKind::SCALAR_TYPE_EXTENSION => function(ScalarTypeExtensionNode $def) {
- return $this->join([
- 'extend scalar',
- $def->name,
- $this->join($def->directives, ' '),
- ], ' ');
- },
- NodeKind::OBJECT_TYPE_EXTENSION => function(ObjectTypeExtensionNode $def) {
- return $this->join([
- 'extend type',
- $def->name,
- $this->wrap('implements ', $this->join($def->interfaces, ' & ')),
- $this->join($def->directives, ' '),
- $this->block($def->fields),
- ], ' ');
- },
- NodeKind::INTERFACE_TYPE_EXTENSION => function(InterfaceTypeExtensionNode $def) {
- return $this->join([
- 'extend interface',
- $def->name,
- $this->join($def->directives, ' '),
- $this->block($def->fields),
- ], ' ');
- },
- NodeKind::UNION_TYPE_EXTENSION => function(UnionTypeExtensionNode $def) {
- return $this->join([
- 'extend union',
- $def->name,
- $this->join($def->directives, ' '),
- $def->types
- ? '= ' . $this->join($def->types, ' | ')
- : ''
- ], ' ');
- },
- NodeKind::ENUM_TYPE_EXTENSION => function(EnumTypeExtensionNode $def) {
- return $this->join([
- 'extend enum',
- $def->name,
- $this->join($def->directives, ' '),
- $this->block($def->values),
- ], ' ');
- },
- NodeKind::INPUT_OBJECT_TYPE_EXTENSION => function(InputObjectTypeExtensionNode $def) {
- return $this->join([
- 'extend input',
- $def->name,
- $this->join($def->directives, ' '),
- $this->block($def->fields),
- ], ' ');
- },
- NodeKind::DIRECTIVE_DEFINITION => $this->addDescription(function(DirectiveDefinitionNode $def) {
- return 'directive @'
- . $def->name
- . $this->wrap('(', $this->join($def->arguments, ', '), ')')
- . ' on ' . $this->join($def->locations, ' | ');
- })
+ NodeKind::FIELD => function (FieldNode $node) {
+ return $this->join(
+ [
+ $this->wrap('', $node->alias, ': ') . $node->name . $this->wrap(
+ '(',
+ $this->join($node->arguments, ', '),
+ ')'
+ ),
+ $this->join($node->directives, ' '),
+ $node->selectionSet,
+ ],
+ ' '
+ );
+ },
+
+ NodeKind::ARGUMENT => function (ArgumentNode $node) {
+ return $node->name . ': ' . $node->value;
+ },
+
+ NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) {
+ return '...' . $node->name . $this->wrap(' ', $this->join($node->directives, ' '));
+ },
+
+ NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) {
+ return $this->join(
+ [
+ '...',
+ $this->wrap('on ', $node->typeCondition),
+ $this->join($node->directives, ' '),
+ $node->selectionSet,
+ ],
+ ' '
+ );
+ },
+
+ NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) {
+ // Note: fragment variable definitions are experimental and may be changed or removed in the future.
+ return sprintf('fragment %s', $node->name)
+ . $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')')
+ . sprintf(' on %s ', $node->typeCondition)
+ . $this->wrap('', $this->join($node->directives, ' '), ' ')
+ . $node->selectionSet;
+ },
+
+ NodeKind::INT => function (IntValueNode $node) {
+ return $node->value;
+ },
+
+ NodeKind::FLOAT => function (FloatValueNode $node) {
+ return $node->value;
+ },
+
+ NodeKind::STRING => function (StringValueNode $node, $key) {
+ if ($node->block) {
+ return $this->printBlockString($node->value, $key === 'description');
+ }
+
+ return json_encode($node->value);
+ },
+
+ NodeKind::BOOLEAN => function (BooleanValueNode $node) {
+ return $node->value ? 'true' : 'false';
+ },
+
+ NodeKind::NULL => function (NullValueNode $node) {
+ return 'null';
+ },
+
+ NodeKind::ENUM => function (EnumValueNode $node) {
+ return $node->value;
+ },
+
+ NodeKind::LST => function (ListValueNode $node) {
+ return '[' . $this->join($node->values, ', ') . ']';
+ },
+
+ NodeKind::OBJECT => function (ObjectValueNode $node) {
+ return '{' . $this->join($node->fields, ', ') . '}';
+ },
+
+ NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) {
+ return $node->name . ': ' . $node->value;
+ },
+
+ NodeKind::DIRECTIVE => function (DirectiveNode $node) {
+ return '@' . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')');
+ },
+
+ NodeKind::NAMED_TYPE => function (NamedTypeNode $node) {
+ return $node->name;
+ },
+
+ NodeKind::LIST_TYPE => function (ListTypeNode $node) {
+ return '[' . $node->type . ']';
+ },
+
+ NodeKind::NON_NULL_TYPE => function (NonNullTypeNode $node) {
+ return $node->type . '!';
+ },
+
+ NodeKind::SCHEMA_DEFINITION => function (SchemaDefinitionNode $def) {
+ return $this->join(
+ [
+ 'schema',
+ $this->join($def->directives, ' '),
+ $this->block($def->operationTypes),
+ ],
+ ' '
+ );
+ },
+
+ NodeKind::OPERATION_TYPE_DEFINITION => function (OperationTypeDefinitionNode $def) {
+ return $def->operation . ': ' . $def->type;
+ },
+
+ NodeKind::SCALAR_TYPE_DEFINITION => $this->addDescription(function (ScalarTypeDefinitionNode $def) {
+ return $this->join(['scalar', $def->name, $this->join($def->directives, ' ')], ' ');
+ }),
+
+ NodeKind::OBJECT_TYPE_DEFINITION => $this->addDescription(function (ObjectTypeDefinitionNode $def) {
+ return $this->join(
+ [
+ 'type',
+ $def->name,
+ $this->wrap('implements ', $this->join($def->interfaces, ' & ')),
+ $this->join($def->directives, ' '),
+ $this->block($def->fields),
+ ],
+ ' '
+ );
+ }),
+
+ NodeKind::FIELD_DEFINITION => $this->addDescription(function (FieldDefinitionNode $def) {
+ return $def->name
+ . $this->wrap('(', $this->join($def->arguments, ', '), ')')
+ . ': ' . $def->type
+ . $this->wrap(' ', $this->join($def->directives, ' '));
+ }),
+
+ NodeKind::INPUT_VALUE_DEFINITION => $this->addDescription(function (InputValueDefinitionNode $def) {
+ return $this->join(
+ [
+ $def->name . ': ' . $def->type,
+ $this->wrap('= ', $def->defaultValue),
+ $this->join($def->directives, ' '),
+ ],
+ ' '
+ );
+ }),
+
+ NodeKind::INTERFACE_TYPE_DEFINITION => $this->addDescription(
+ function (InterfaceTypeDefinitionNode $def) {
+ return $this->join(
+ [
+ 'interface',
+ $def->name,
+ $this->join($def->directives, ' '),
+ $this->block($def->fields),
+ ],
+ ' '
+ );
+ }
+ ),
+
+ NodeKind::UNION_TYPE_DEFINITION => $this->addDescription(function (UnionTypeDefinitionNode $def) {
+ return $this->join(
+ [
+ 'union',
+ $def->name,
+ $this->join($def->directives, ' '),
+ $def->types
+ ? '= ' . $this->join($def->types, ' | ')
+ : '',
+ ],
+ ' '
+ );
+ }),
+
+ NodeKind::ENUM_TYPE_DEFINITION => $this->addDescription(function (EnumTypeDefinitionNode $def) {
+ return $this->join(
+ [
+ 'enum',
+ $def->name,
+ $this->join($def->directives, ' '),
+ $this->block($def->values),
+ ],
+ ' '
+ );
+ }),
+
+ NodeKind::ENUM_VALUE_DEFINITION => $this->addDescription(function (EnumValueDefinitionNode $def) {
+ return $this->join([$def->name, $this->join($def->directives, ' ')], ' ');
+ }),
+
+ NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $this->addDescription(function (
+ InputObjectTypeDefinitionNode $def
+ ) {
+ return $this->join(
+ [
+ 'input',
+ $def->name,
+ $this->join($def->directives, ' '),
+ $this->block($def->fields),
+ ],
+ ' '
+ );
+ }),
+
+ NodeKind::SCALAR_TYPE_EXTENSION => function (ScalarTypeExtensionNode $def) {
+ return $this->join(
+ [
+ 'extend scalar',
+ $def->name,
+ $this->join($def->directives, ' '),
+ ],
+ ' '
+ );
+ },
+
+ NodeKind::OBJECT_TYPE_EXTENSION => function (ObjectTypeExtensionNode $def) {
+ return $this->join(
+ [
+ 'extend type',
+ $def->name,
+ $this->wrap('implements ', $this->join($def->interfaces, ' & ')),
+ $this->join($def->directives, ' '),
+ $this->block($def->fields),
+ ],
+ ' '
+ );
+ },
+
+ NodeKind::INTERFACE_TYPE_EXTENSION => function (InterfaceTypeExtensionNode $def) {
+ return $this->join(
+ [
+ 'extend interface',
+ $def->name,
+ $this->join($def->directives, ' '),
+ $this->block($def->fields),
+ ],
+ ' '
+ );
+ },
+
+ NodeKind::UNION_TYPE_EXTENSION => function (UnionTypeExtensionNode $def) {
+ return $this->join(
+ [
+ 'extend union',
+ $def->name,
+ $this->join($def->directives, ' '),
+ $def->types
+ ? '= ' . $this->join($def->types, ' | ')
+ : '',
+ ],
+ ' '
+ );
+ },
+
+ NodeKind::ENUM_TYPE_EXTENSION => function (EnumTypeExtensionNode $def) {
+ return $this->join(
+ [
+ 'extend enum',
+ $def->name,
+ $this->join($def->directives, ' '),
+ $this->block($def->values),
+ ],
+ ' '
+ );
+ },
+
+ NodeKind::INPUT_OBJECT_TYPE_EXTENSION => function (InputObjectTypeExtensionNode $def) {
+ return $this->join(
+ [
+ 'extend input',
+ $def->name,
+ $this->join($def->directives, ' '),
+ $this->block($def->fields),
+ ],
+ ' '
+ );
+ },
+
+ NodeKind::DIRECTIVE_DEFINITION => $this->addDescription(function (DirectiveDefinitionNode $def) {
+ return 'directive @'
+ . $def->name
+ . $this->wrap('(', $this->join($def->arguments, ', '), ')')
+ . ' on ' . $this->join($def->locations, ' | ');
+ }),
+ ],
]
- ]);
+ );
}
public function addDescription(\Closure $cb)
@@ -371,7 +469,9 @@ class Printer
$separator,
Utils::filter(
$maybeArray,
- function($x) { return !!$x;}
+ function ($x) {
+ return ! ! $x;
+ }
)
)
: '';
@@ -382,8 +482,10 @@ class Printer
* trailing blank line. However, if a block string starts with whitespace and is
* a single-line, adding a leading blank line would strip that whitespace.
*/
- private function printBlockString($value, $isDescription) {
+ private function printBlockString($value, $isDescription)
+ {
$escaped = str_replace('"""', '\\"""', $value);
+
return (($value[0] === ' ' || $value[0] === "\t") && strpos($value, "\n") === false)
? ('"""' . preg_replace('/"$/', "\"\n", $escaped) . '"""')
: ("\"\"\"\n" . ($isDescription ? $escaped : $this->indent($escaped)) . "\n\"\"\"");
diff --git a/src/Language/Source.php b/src/Language/Source.php
index 899d450..1beb8bc 100644
--- a/src/Language/Source.php
+++ b/src/Language/Source.php
@@ -1,36 +1,33 @@
body = $body;
- $this->length = mb_strlen($body, 'UTF-8');
- $this->name = $name ?: 'GraphQL request';
+ $this->body = $body;
+ $this->length = mb_strlen($body, 'UTF-8');
+ $this->name = $name ?: 'GraphQL request';
$this->locationOffset = $location ?: new SourceLocation(1, 1);
Utils::invariant(
@@ -66,21 +62,22 @@ class Source
}
/**
- * @param $position
+ * @param int $position
* @return SourceLocation
*/
public function getLocation($position)
{
- $line = 1;
+ $line = 1;
$column = $position + 1;
- $utfChars = json_decode('"\u2028\u2029"');
- $lineRegexp = '/\r\n|[\n\r'.$utfChars.']/su';
- $matches = [];
+ $utfChars = json_decode('"\u2028\u2029"');
+ $lineRegexp = '/\r\n|[\n\r' . $utfChars . ']/su';
+ $matches = [];
preg_match_all($lineRegexp, mb_substr($this->body, 0, $position, 'UTF-8'), $matches, PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $index => $match) {
$line += 1;
+
$column = $position + 1 - ($match[1] + mb_strlen($match[0], 'UTF-8'));
}
diff --git a/src/Language/SourceLocation.php b/src/Language/SourceLocation.php
index d09363b..2c406ca 100644
--- a/src/Language/SourceLocation.php
+++ b/src/Language/SourceLocation.php
@@ -1,30 +1,40 @@
line = $line;
+ $this->line = $line;
$this->column = $col;
}
/**
- * @return array
+ * @return int[]
*/
public function toArray()
{
return [
- 'line' => $this->line,
- 'column' => $this->column
+ 'line' => $this->line,
+ 'column' => $this->column,
];
}
/**
- * @return array
+ * @return int[]
*/
public function toSerializableArray()
{
@@ -32,13 +42,9 @@ class SourceLocation implements \JsonSerializable
}
/**
- * Specify data which should be serialized to JSON
- * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
- * @return mixed data which can be serialized by json_encode,
- * which is a value of any type other than a resource.
- * @since 5.4.0
+ * @return int[]
*/
- function jsonSerialize()
+ public function jsonSerialize()
{
return $this->toSerializableArray();
}
diff --git a/src/Language/Token.php b/src/Language/Token.php
index 880cb0d..a1c966c 100644
--- a/src/Language/Token.php
+++ b/src/Language/Token.php
@@ -1,4 +1,7 @@
';
- const EOF = '';
- const BANG = '!';
- const DOLLAR = '$';
- const AMP = '&';
- const PAREN_L = '(';
- const PAREN_R = ')';
- const SPREAD = '...';
- const COLON = ':';
- const EQUALS = '=';
- const AT = '@';
- const BRACKET_L = '[';
- const BRACKET_R = ']';
- const BRACE_L = '{';
- const PIPE = '|';
- const BRACE_R = '}';
- const NAME = 'Name';
- const INT = 'Int';
- const FLOAT = 'Float';
- const STRING = 'String';
+ const SOF = '';
+ const EOF = '';
+ const BANG = '!';
+ const DOLLAR = '$';
+ const AMP = '&';
+ const PAREN_L = '(';
+ const PAREN_R = ')';
+ const SPREAD = '...';
+ const COLON = ':';
+ const EQUALS = '=';
+ const AT = '@';
+ const BRACKET_L = '[';
+ const BRACKET_R = ']';
+ const BRACE_L = '{';
+ const PIPE = '|';
+ const BRACE_R = '}';
+ const NAME = 'Name';
+ const INT = 'Int';
+ const FLOAT = 'Float';
+ const STRING = 'String';
const BLOCK_STRING = 'BlockString';
- const COMMENT = 'Comment';
+ const COMMENT = 'Comment';
/**
* The kind of Token (see one of constants above).
@@ -66,9 +69,7 @@ class Token
*/
public $column;
- /**
- * @var string|null
- */
+ /** @var string|null */
public $value;
/**
@@ -80,31 +81,28 @@ class Token
*/
public $prev;
- /**
- * @var Token
- */
+ /** @var Token */
public $next;
/**
- * Token constructor.
- * @param $kind
- * @param $start
- * @param $end
- * @param $line
- * @param $column
- * @param Token $previous
- * @param null $value
+ *
+ * @param string $kind
+ * @param int $start
+ * @param int $end
+ * @param int $line
+ * @param int $column
+ * @param mixed|null $value
*/
- public function __construct($kind, $start, $end, $line, $column, Token $previous = null, $value = null)
+ public function __construct($kind, $start, $end, $line, $column, ?Token $previous = null, $value = null)
{
- $this->kind = $kind;
- $this->start = (int) $start;
- $this->end = (int) $end;
- $this->line = (int) $line;
- $this->column = (int) $column;
- $this->prev = $previous;
- $this->next = null;
- $this->value = $value;
+ $this->kind = $kind;
+ $this->start = $start;
+ $this->end = $end;
+ $this->line = $line;
+ $this->column = $column;
+ $this->prev = $previous;
+ $this->next = null;
+ $this->value = $value;
}
/**
@@ -112,19 +110,19 @@ class Token
*/
public function getDescription()
{
- return $this->kind . ($this->value ? ' "' . $this->value . '"' : '');
+ return $this->kind . ($this->value ? ' "' . $this->value . '"' : '');
}
/**
- * @return array
+ * @return (string|int|null)[]
*/
public function toArray()
{
return [
- 'kind' => $this->kind,
- 'value' => $this->value,
- 'line' => $this->line,
- 'column' => $this->column
+ 'kind' => $this->kind,
+ 'value' => $this->value,
+ 'line' => $this->line,
+ 'column' => $this->column,
];
}
}
diff --git a/src/Language/Visitor.php b/src/Language/Visitor.php
index ab20d1e..e266f0f 100644
--- a/src/Language/Visitor.php
+++ b/src/Language/Visitor.php
@@ -1,19 +1,24 @@
[],
- NodeKind::DOCUMENT => ['definitions'],
+ NodeKind::NAME => [],
+ NodeKind::DOCUMENT => ['definitions'],
NodeKind::OPERATION_DEFINITION => ['name', 'variableDefinitions', 'directives', 'selectionSet'],
- NodeKind::VARIABLE_DEFINITION => ['variable', 'type', 'defaultValue'],
- NodeKind::VARIABLE => ['name'],
- NodeKind::SELECTION_SET => ['selections'],
- NodeKind::FIELD => ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
- NodeKind::ARGUMENT => ['name', 'value'],
- NodeKind::FRAGMENT_SPREAD => ['name', 'directives'],
- NodeKind::INLINE_FRAGMENT => ['typeCondition', 'directives', 'selectionSet'],
- NodeKind::FRAGMENT_DEFINITION => [
+ NodeKind::VARIABLE_DEFINITION => ['variable', 'type', 'defaultValue'],
+ NodeKind::VARIABLE => ['name'],
+ NodeKind::SELECTION_SET => ['selections'],
+ NodeKind::FIELD => ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
+ NodeKind::ARGUMENT => ['name', 'value'],
+ NodeKind::FRAGMENT_SPREAD => ['name', 'directives'],
+ NodeKind::INLINE_FRAGMENT => ['typeCondition', 'directives', 'selectionSet'],
+ NodeKind::FRAGMENT_DEFINITION => [
'name',
// Note: fragment variable definitions are experimental and may be changed
// or removed in the future.
'variableDefinitions',
'typeCondition',
'directives',
- 'selectionSet'
+ 'selectionSet',
],
- NodeKind::INT => [],
- NodeKind::FLOAT => [],
- NodeKind::STRING => [],
- NodeKind::BOOLEAN => [],
- NodeKind::NULL => [],
- NodeKind::ENUM => [],
- NodeKind::LST => ['values'],
- NodeKind::OBJECT => ['fields'],
- NodeKind::OBJECT_FIELD => ['name', 'value'],
- NodeKind::DIRECTIVE => ['name', 'arguments'],
- NodeKind::NAMED_TYPE => ['name'],
- NodeKind::LIST_TYPE => ['type'],
+ NodeKind::INT => [],
+ NodeKind::FLOAT => [],
+ NodeKind::STRING => [],
+ NodeKind::BOOLEAN => [],
+ NodeKind::NULL => [],
+ NodeKind::ENUM => [],
+ NodeKind::LST => ['values'],
+ NodeKind::OBJECT => ['fields'],
+ NodeKind::OBJECT_FIELD => ['name', 'value'],
+ NodeKind::DIRECTIVE => ['name', 'arguments'],
+ NodeKind::NAMED_TYPE => ['name'],
+ NodeKind::LIST_TYPE => ['type'],
NodeKind::NON_NULL_TYPE => ['type'],
- NodeKind::SCHEMA_DEFINITION => ['directives', 'operationTypes'],
- NodeKind::OPERATION_TYPE_DEFINITION => ['type'],
- NodeKind::SCALAR_TYPE_DEFINITION => ['description', 'name', 'directives'],
- NodeKind::OBJECT_TYPE_DEFINITION => ['description', 'name', 'interfaces', 'directives', 'fields'],
- NodeKind::FIELD_DEFINITION => ['description', 'name', 'arguments', 'type', 'directives'],
- NodeKind::INPUT_VALUE_DEFINITION => ['description', 'name', 'type', 'defaultValue', 'directives'],
- NodeKind::INTERFACE_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'],
- NodeKind::UNION_TYPE_DEFINITION => ['description', 'name', 'directives', 'types'],
- NodeKind::ENUM_TYPE_DEFINITION => ['description', 'name', 'directives', 'values'],
- NodeKind::ENUM_VALUE_DEFINITION => ['description', 'name', 'directives'],
+ NodeKind::SCHEMA_DEFINITION => ['directives', 'operationTypes'],
+ NodeKind::OPERATION_TYPE_DEFINITION => ['type'],
+ NodeKind::SCALAR_TYPE_DEFINITION => ['description', 'name', 'directives'],
+ NodeKind::OBJECT_TYPE_DEFINITION => ['description', 'name', 'interfaces', 'directives', 'fields'],
+ NodeKind::FIELD_DEFINITION => ['description', 'name', 'arguments', 'type', 'directives'],
+ NodeKind::INPUT_VALUE_DEFINITION => ['description', 'name', 'type', 'defaultValue', 'directives'],
+ NodeKind::INTERFACE_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'],
+ NodeKind::UNION_TYPE_DEFINITION => ['description', 'name', 'directives', 'types'],
+ NodeKind::ENUM_TYPE_DEFINITION => ['description', 'name', 'directives', 'values'],
+ NodeKind::ENUM_VALUE_DEFINITION => ['description', 'name', 'directives'],
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'],
- NodeKind::SCALAR_TYPE_EXTENSION => ['name', 'directives'],
- NodeKind::OBJECT_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'],
- NodeKind::INTERFACE_TYPE_EXTENSION => ['name', 'directives', 'fields'],
- NodeKind::UNION_TYPE_EXTENSION => ['name', 'directives', 'types'],
- NodeKind::ENUM_TYPE_EXTENSION => ['name', 'directives', 'values'],
+ NodeKind::SCALAR_TYPE_EXTENSION => ['name', 'directives'],
+ NodeKind::OBJECT_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'],
+ NodeKind::INTERFACE_TYPE_EXTENSION => ['name', 'directives', 'fields'],
+ NodeKind::UNION_TYPE_EXTENSION => ['name', 'directives', 'types'],
+ NodeKind::ENUM_TYPE_EXTENSION => ['name', 'directives', 'values'],
NodeKind::INPUT_OBJECT_TYPE_EXTENSION => ['name', 'directives', 'fields'],
- NodeKind::DIRECTIVE_DEFINITION => ['description', 'name', 'arguments', 'locations']
+ NodeKind::DIRECTIVE_DEFINITION => ['description', 'name', 'arguments', 'locations'],
];
/**
* Visit the AST (see class description for details)
*
* @api
- * @param Node $root
- * @param array $visitor
- * @param array $keyMap
+ * @param Node|ArrayObject|stdClass $root
+ * @param callable[] $visitor
+ * @param mixed[]|null $keyMap
* @return Node|mixed
* @throws \Exception
*/
@@ -175,28 +181,28 @@ class Visitor
{
$visitorKeys = $keyMap ?: self::$visitorKeys;
- $stack = null;
- $inArray = $root instanceof NodeList || is_array($root);
- $keys = [$root];
- $index = -1;
- $edits = [];
- $parent = null;
- $path = [];
+ $stack = null;
+ $inArray = $root instanceof NodeList || is_array($root);
+ $keys = [$root];
+ $index = -1;
+ $edits = [];
+ $parent = null;
+ $path = [];
$ancestors = [];
- $newRoot = $root;
+ $newRoot = $root;
$UNDEFINED = null;
do {
$index++;
$isLeaving = $index === count($keys);
- $key = null;
- $node = null;
- $isEdited = $isLeaving && count($edits) !== 0;
+ $key = null;
+ $node = null;
+ $isEdited = $isLeaving && count($edits) !== 0;
if ($isLeaving) {
- $key = !$ancestors ? $UNDEFINED : $path[count($path) - 1];
- $node = $parent;
+ $key = ! $ancestors ? $UNDEFINED : $path[count($path) - 1];
+ $node = $parent;
$parent = array_pop($ancestors);
if ($isEdited) {
@@ -210,7 +216,7 @@ class Visitor
}
$editOffset = 0;
for ($ii = 0; $ii < count($edits); $ii++) {
- $editKey = $edits[$ii][0];
+ $editKey = $edits[$ii][0];
$editValue = $edits[$ii][1];
if ($inArray) {
@@ -232,13 +238,13 @@ class Visitor
}
}
}
- $index = $stack['index'];
- $keys = $stack['keys'];
- $edits = $stack['edits'];
+ $index = $stack['index'];
+ $keys = $stack['keys'];
+ $edits = $stack['edits'];
$inArray = $stack['inArray'];
- $stack = $stack['prev'];
+ $stack = $stack['prev'];
} else {
- $key = $parent ? ($inArray ? $index : $keys[$index]) : $UNDEFINED;
+ $key = $parent ? ($inArray ? $index : $keys[$index]) : $UNDEFINED;
$node = $parent ? (($parent instanceof NodeList || is_array($parent)) ? $parent[$key] : $parent->{$key}) : $newRoot;
if ($node === null || $node === $UNDEFINED) {
continue;
@@ -249,8 +255,8 @@ class Visitor
}
$result = null;
- if (!$node instanceof NodeList && !is_array($node)) {
- if (!($node instanceof Node)) {
+ if (! $node instanceof NodeList && ! is_array($node)) {
+ if (! ($node instanceof Node)) {
throw new \Exception('Invalid AST Node: ' . json_encode($node));
}
@@ -264,7 +270,7 @@ class Visitor
if ($result->doBreak) {
break;
}
- if (!$isLeaving && $result->doContinue) {
+ if (! $isLeaving && $result->doContinue) {
array_pop($path);
continue;
}
@@ -276,13 +282,13 @@ class Visitor
}
$edits[] = [$key, $editValue];
- if (!$isLeaving) {
- if ($editValue instanceof Node) {
- $node = $editValue;
- } else {
+ if (! $isLeaving) {
+ if (! ($editValue instanceof Node)) {
array_pop($path);
continue;
}
+
+ $node = $editValue;
}
}
}
@@ -295,16 +301,16 @@ class Visitor
if ($isLeaving) {
array_pop($path);
} else {
- $stack = [
+ $stack = [
'inArray' => $inArray,
- 'index' => $index,
- 'keys' => $keys,
- 'edits' => $edits,
- 'prev' => $stack
+ 'index' => $index,
+ 'keys' => $keys,
+ 'edits' => $edits,
+ 'prev' => $stack,
];
$inArray = $node instanceof NodeList || is_array($node);
- $keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?: [];
+ $keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?: [];
$index = -1;
$edits = [];
if ($parent) {
@@ -312,7 +318,6 @@ class Visitor
}
$parent = $node;
}
-
} while ($stack);
if (count($edits) !== 0) {
@@ -330,8 +335,9 @@ class Visitor
*/
public static function stop()
{
- $r = new VisitorOperation();
+ $r = new VisitorOperation();
$r->doBreak = true;
+
return $r;
}
@@ -343,8 +349,9 @@ class Visitor
*/
public static function skipNode()
{
- $r = new VisitorOperation();
+ $r = new VisitorOperation();
$r->doContinue = true;
+
return $r;
}
@@ -356,66 +363,79 @@ class Visitor
*/
public static function removeNode()
{
- $r = new VisitorOperation();
+ $r = new VisitorOperation();
$r->removeNode = true;
+
return $r;
}
/**
- * @param $visitors
- * @return array
+ * @param callable[][] $visitors
+ * @return callable[][]
*/
- static function visitInParallel($visitors)
+ public static function visitInParallel($visitors)
{
$visitorsCount = count($visitors);
- $skipping = new \SplFixedArray($visitorsCount);
+ $skipping = new \SplFixedArray($visitorsCount);
return [
- 'enter' => function ($node) use ($visitors, $skipping, $visitorsCount) {
+ 'enter' => function (Node $node) use ($visitors, $skipping, $visitorsCount) {
for ($i = 0; $i < $visitorsCount; $i++) {
- if (empty($skipping[$i])) {
- $fn = self::getVisitFn($visitors[$i], $node->kind, /* isLeaving */ false);
+ if (! empty($skipping[$i])) {
+ continue;
+ }
- if ($fn) {
- $result = call_user_func_array($fn, func_get_args());
+ $fn = self::getVisitFn(
+ $visitors[$i],
+ $node->kind, /* isLeaving */
+ false
+ );
- if ($result instanceof VisitorOperation) {
- if ($result->doContinue) {
- $skipping[$i] = $node;
- } else if ($result->doBreak) {
- $skipping[$i] = $result;
- } else if ($result->removeNode) {
- return $result;
- }
- } else if ($result !== null) {
- return $result;
- }
+ if (! $fn) {
+ continue;
+ }
+
+ $result = call_user_func_array($fn, func_get_args());
+
+ if ($result instanceof VisitorOperation) {
+ if ($result->doContinue) {
+ $skipping[$i] = $node;
+ } elseif ($result->doBreak) {
+ $skipping[$i] = $result;
+ } elseif ($result->removeNode) {
+ return $result;
}
+ } elseif ($result !== null) {
+ return $result;
}
}
},
- 'leave' => function ($node) use ($visitors, $skipping, $visitorsCount) {
+ 'leave' => function (Node $node) use ($visitors, $skipping, $visitorsCount) {
for ($i = 0; $i < $visitorsCount; $i++) {
if (empty($skipping[$i])) {
- $fn = self::getVisitFn($visitors[$i], $node->kind, /* isLeaving */ true);
+ $fn = self::getVisitFn(
+ $visitors[$i],
+ $node->kind, /* isLeaving */
+ true
+ );
if ($fn) {
$result = call_user_func_array($fn, func_get_args());
if ($result instanceof VisitorOperation) {
if ($result->doBreak) {
$skipping[$i] = $result;
- } else if ($result->removeNode) {
+ } elseif ($result->removeNode) {
return $result;
}
- } else if ($result !== null) {
+ } elseif ($result !== null) {
return $result;
}
}
- } else if ($skipping[$i] === $node) {
+ } elseif ($skipping[$i] === $node) {
$skipping[$i] = null;
}
}
- }
+ },
];
}
@@ -423,10 +443,10 @@ class Visitor
* Creates a new visitor instance which maintains a provided TypeInfo instance
* along with visiting visitor.
*/
- static function visitWithTypeInfo(TypeInfo $typeInfo, $visitor)
+ public static function visitWithTypeInfo(TypeInfo $typeInfo, $visitor)
{
return [
- 'enter' => function ($node) use ($typeInfo, $visitor) {
+ 'enter' => function (Node $node) use ($typeInfo, $visitor) {
$typeInfo->enter($node);
$fn = self::getVisitFn($visitor, $node->kind, false);
@@ -438,52 +458,58 @@ class Visitor
$typeInfo->enter($result);
}
}
+
return $result;
}
+
return null;
},
- 'leave' => function ($node) use ($typeInfo, $visitor) {
- $fn = self::getVisitFn($visitor, $node->kind, true);
+ 'leave' => function (Node $node) use ($typeInfo, $visitor) {
+ $fn = self::getVisitFn($visitor, $node->kind, true);
$result = $fn ? call_user_func_array($fn, func_get_args()) : null;
$typeInfo->leave($node);
+
return $result;
- }
+ },
];
}
/**
- * @param $visitor
- * @param $kind
- * @param $isLeaving
- * @return null
+ * @param callable[]|null $visitor
+ * @param string $kind
+ * @param bool $isLeaving
+ * @return callable|null
*/
public static function getVisitFn($visitor, $kind, $isLeaving)
{
- if (!$visitor) {
+ if ($visitor === null) {
return null;
}
- $kindVisitor = isset($visitor[$kind]) ? $visitor[$kind] : null;
- if (!$isLeaving && is_callable($kindVisitor)) {
+ $kindVisitor = $visitor[$kind] ?? null;
+
+ if (! $isLeaving && is_callable($kindVisitor)) {
// { Kind() {} }
return $kindVisitor;
}
if (is_array($kindVisitor)) {
if ($isLeaving) {
- $kindSpecificVisitor = isset($kindVisitor['leave']) ? $kindVisitor['leave'] : null;
+ $kindSpecificVisitor = $kindVisitor['leave'] ?? null;
} else {
- $kindSpecificVisitor = isset($kindVisitor['enter']) ? $kindVisitor['enter'] : null;
+ $kindSpecificVisitor = $kindVisitor['enter'] ?? null;
}
if ($kindSpecificVisitor && is_callable($kindSpecificVisitor)) {
// { Kind: { enter() {}, leave() {} } }
return $kindSpecificVisitor;
}
+
return null;
}
$visitor += ['leave' => null, 'enter' => null];
+
$specificVisitor = $isLeaving ? $visitor['leave'] : $visitor['enter'];
if ($specificVisitor) {
@@ -491,13 +517,14 @@ class Visitor
// { enter() {}, leave() {} }
return $specificVisitor;
}
- $specificKindVisitor = isset($specificVisitor[$kind]) ? $specificVisitor[$kind] : null;
+ $specificKindVisitor = $specificVisitor[$kind] ?? null;
if (is_callable($specificKindVisitor)) {
// { enter: { Kind() {} }, leave: { Kind() {} } }
return $specificKindVisitor;
}
}
+
return null;
}
}
diff --git a/src/Language/VisitorOperation.php b/src/Language/VisitorOperation.php
new file mode 100644
index 0000000..2316261
--- /dev/null
+++ b/src/Language/VisitorOperation.php
@@ -0,0 +1,17 @@
+loc = Location::create($node['loc']['start'], $node['loc']['end']);
}
-
foreach ($node as $key => $value) {
- if ('loc' === $key || 'kind' === $key) {
- continue ;
+ if ($key === 'loc' || $key === 'kind') {
+ continue;
}
if (is_array($value)) {
if (isset($value[0]) || empty($value)) {
@@ -94,6 +110,7 @@ class AST
}
$instance->{$key} = $value;
}
+
return $instance;
}
@@ -101,8 +118,7 @@ class AST
* Convert AST node to serializable array
*
* @api
- * @param Node $node
- * @return array
+ * @return mixed[]
*/
public static function toArray(Node $node)
{
@@ -128,17 +144,17 @@ class AST
* | null | NullValue |
*
* @api
- * @param $value
- * @param InputType $type
+ * @param Type|mixed|null $value
* @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode
*/
- static function astFromValue($value, InputType $type)
+ public static function astFromValue($value, InputType $type)
{
if ($type instanceof NonNull) {
$astValue = self::astFromValue($value, $type->getWrappedType());
if ($astValue instanceof NullValueNode) {
return null;
}
+
return $astValue;
}
@@ -154,56 +170,65 @@ class AST
$valuesNodes = [];
foreach ($value as $item) {
$itemNode = self::astFromValue($item, $itemType);
- if ($itemNode) {
- $valuesNodes[] = $itemNode;
+ if (! $itemNode) {
+ continue;
}
+
+ $valuesNodes[] = $itemNode;
}
+
return new ListValueNode(['values' => $valuesNodes]);
}
+
return self::astFromValue($value, $itemType);
}
// Populate the fields of the input object by creating ASTs from each value
// in the PHP object according to the fields in the input type.
if ($type instanceof InputObjectType) {
- $isArray = is_array($value);
+ $isArray = is_array($value);
$isArrayLike = $isArray || $value instanceof \ArrayAccess;
- if ($value === null || (!$isArrayLike && !is_object($value))) {
+ if ($value === null || (! $isArrayLike && ! is_object($value))) {
return null;
}
- $fields = $type->getFields();
+ $fields = $type->getFields();
$fieldNodes = [];
foreach ($fields as $fieldName => $field) {
if ($isArrayLike) {
- $fieldValue = isset($value[$fieldName]) ? $value[$fieldName] : null;
+ $fieldValue = $value[$fieldName] ?? null;
} else {
- $fieldValue = isset($value->{$fieldName}) ? $value->{$fieldName} : null;
+ $fieldValue = $value->{$fieldName} ?? null;
}
// Have to check additionally if key exists, since we differentiate between
// "no key" and "value is null":
- if (null !== $fieldValue) {
+ if ($fieldValue !== null) {
$fieldExists = true;
- } else if ($isArray) {
+ } elseif ($isArray) {
$fieldExists = array_key_exists($fieldName, $value);
- } else if ($isArrayLike) {
+ } elseif ($isArrayLike) {
/** @var \ArrayAccess $value */
$fieldExists = $value->offsetExists($fieldName);
} else {
$fieldExists = property_exists($value, $fieldName);
}
- if ($fieldExists) {
- $fieldNode = self::astFromValue($fieldValue, $field->getType());
-
- if ($fieldNode) {
- $fieldNodes[] = new ObjectFieldNode([
- 'name' => new NameNode(['value' => $fieldName]),
- 'value' => $fieldNode
- ]);
- }
+ if (! $fieldExists) {
+ continue;
}
+
+ $fieldNode = self::astFromValue($fieldValue, $field->getType());
+
+ if (! $fieldNode) {
+ continue;
+ }
+
+ $fieldNodes[] = new ObjectFieldNode([
+ 'name' => new NameNode(['value' => $fieldName]),
+ 'value' => $fieldNode,
+ ]);
}
+
return new ObjectValueNode(['fields' => $fieldNodes]);
}
@@ -232,9 +257,12 @@ class AST
return new IntValueNode(['value' => $serialized]);
}
if (is_float($serialized)) {
+ // int cast with == used for performance reasons
+ // @codingStandardsIgnoreLine
if ((int) $serialized == $serialized) {
return new IntValueNode(['value' => $serialized]);
}
+
return new FloatValueNode(['value' => $serialized]);
}
if (is_string($serialized)) {
@@ -282,17 +310,16 @@ class AST
* | Null Value | null |
*
* @api
- * @param $valueNode
- * @param InputType $type
- * @param mixed[]|null $variables
- * @return array|null|\stdClass
+ * @param ValueNode|null $valueNode
+ * @param mixed[]|null $variables
+ * @return mixed[]|null|\stdClass
* @throws \Exception
*/
public static function valueFromAST($valueNode, InputType $type, $variables = null)
{
$undefined = Utils::undefined();
- if (!$valueNode) {
+ if ($valueNode === null) {
// When there is no AST, then there is also no value.
// Importantly, this is different from returning the GraphQL null value.
return $undefined;
@@ -303,6 +330,7 @@ class AST
// Invalid: intentionally return no value.
return $undefined;
}
+
return self::valueFromAST($valueNode, $type->getWrappedType(), $variables);
}
@@ -314,7 +342,7 @@ class AST
if ($valueNode instanceof VariableNode) {
$variableName = $valueNode->name->value;
- if (!$variables || !array_key_exists($variableName, $variables)) {
+ if (! $variables || ! array_key_exists($variableName, $variables)) {
// No valid return value.
return $undefined;
}
@@ -329,7 +357,7 @@ class AST
if ($valueNode instanceof ListValueNode) {
$coercedValues = [];
- $itemNodes = $valueNode->values;
+ $itemNodes = $valueNode->values;
foreach ($itemNodes as $itemNode) {
if (self::isMissingVariable($itemNode, $variables)) {
// If an array contains a missing variable, it is either coerced to
@@ -348,6 +376,7 @@ class AST
$coercedValues[] = $itemValue;
}
}
+
return $coercedValues;
}
$coercedValue = self::valueFromAST($valueNode, $itemType, $variables);
@@ -355,31 +384,37 @@ class AST
// Invalid: intentionally return no value.
return $undefined;
}
+
return [$coercedValue];
}
if ($type instanceof InputObjectType) {
- if (!$valueNode instanceof ObjectValueNode) {
+ if (! $valueNode instanceof ObjectValueNode) {
// Invalid: intentionally return no value.
return $undefined;
}
$coercedObj = [];
- $fields = $type->getFields();
- $fieldNodes = Utils::keyMap($valueNode->fields, function($field) {return $field->name->value;});
+ $fields = $type->getFields();
+ $fieldNodes = Utils::keyMap(
+ $valueNode->fields,
+ function ($field) {
+ return $field->name->value;
+ }
+ );
foreach ($fields as $field) {
/** @var ValueNode $fieldNode */
$fieldName = $field->name;
- $fieldNode = isset($fieldNodes[$fieldName]) ? $fieldNodes[$fieldName] : null;
+ $fieldNode = $fieldNodes[$fieldName] ?? null;
- if (!$fieldNode || self::isMissingVariable($fieldNode->value, $variables)) {
+ if (! $fieldNode || self::isMissingVariable($fieldNode->value, $variables)) {
if ($field->defaultValueExists()) {
$coercedObj[$fieldName] = $field->defaultValue;
- } else if ($field->getType() instanceof NonNull) {
+ } elseif ($field->getType() instanceof NonNull) {
// Invalid: intentionally return no value.
return $undefined;
}
- continue ;
+ continue;
}
$fieldValue = self::valueFromAST($fieldNode ? $fieldNode->value : null, $field->getType(), $variables);
@@ -390,15 +425,16 @@ class AST
}
$coercedObj[$fieldName] = $fieldValue;
}
+
return $coercedObj;
}
if ($type instanceof EnumType) {
- if (!$valueNode instanceof EnumValueNode) {
+ if (! $valueNode instanceof EnumValueNode) {
return $undefined;
}
$enumValue = $type->getValue($valueNode->value);
- if (!$enumValue) {
+ if (! $enumValue) {
return $undefined;
}
@@ -438,12 +474,13 @@ class AST
* | Null | null |
*
* @api
- * @param Node $valueNode
- * @param array|null $variables
+ * @param Node $valueNode
+ * @param mixed[]|null $variables
* @return mixed
* @throws \Exception
*/
- public static function valueFromASTUntyped($valueNode, array $variables = null) {
+ public static function valueFromASTUntyped($valueNode, ?array $variables = null)
+ {
switch (true) {
case $valueNode instanceof NullValueNode:
return null;
@@ -457,24 +494,29 @@ class AST
return $valueNode->value;
case $valueNode instanceof ListValueNode:
return array_map(
- function($node) use ($variables) {
+ function ($node) use ($variables) {
return self::valueFromASTUntyped($node, $variables);
},
iterator_to_array($valueNode->values)
);
case $valueNode instanceof ObjectValueNode:
- return array_combine(
- array_map(
- function($field) { return $field->name->value; },
- iterator_to_array($valueNode->fields)
- ),
- array_map(
- function($field) use ($variables) { return self::valueFromASTUntyped($field->value, $variables); },
- iterator_to_array($valueNode->fields)
- )
- );
+ return array_combine(
+ array_map(
+ function ($field) {
+ return $field->name->value;
+ },
+ iterator_to_array($valueNode->fields)
+ ),
+ array_map(
+ function ($field) use ($variables) {
+ return self::valueFromASTUntyped($field->value, $variables);
+ },
+ iterator_to_array($valueNode->fields)
+ )
+ );
case $valueNode instanceof VariableNode:
$variableName = $valueNode->name->value;
+
return ($variables && isset($variables[$variableName]))
? $variables[$variableName]
: null;
@@ -487,19 +529,20 @@ class AST
* Returns type definition for given AST Type node
*
* @api
- * @param Schema $schema
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
- * @return Type
+ * @return Type|null
* @throws \Exception
*/
public static function typeFromAST(Schema $schema, $inputTypeNode)
{
if ($inputTypeNode instanceof ListTypeNode) {
$innerType = self::typeFromAST($schema, $inputTypeNode->type);
+
return $innerType ? new ListOfType($innerType) : null;
}
if ($inputTypeNode instanceof NonNullTypeNode) {
$innerType = self::typeFromAST($schema, $inputTypeNode->type);
+
return $innerType ? new NonNull($innerType) : null;
}
if ($inputTypeNode instanceof NamedTypeNode) {
@@ -512,21 +555,20 @@ class AST
/**
* Returns true if the provided valueNode is a variable which is not defined
* in the set of variables.
- * @param $valueNode
- * @param $variables
+ * @param ValueNode $valueNode
+ * @param mixed[] $variables
* @return bool
*/
private static function isMissingVariable($valueNode, $variables)
{
return $valueNode instanceof VariableNode &&
- (!$variables || !array_key_exists($valueNode->name->value, $variables));
+ (count($variables) === 0 || ! array_key_exists($valueNode->name->value, $variables));
}
/**
* Returns operation type ("query", "mutation" or "subscription") given a document and operation name
*
* @api
- * @param DocumentNode $document
* @param string $operationName
* @return bool
*/
@@ -534,13 +576,16 @@ class AST
{
if ($document->definitions) {
foreach ($document->definitions as $def) {
- if ($def instanceof OperationDefinitionNode) {
- if (!$operationName || (isset($def->name->value) && $def->name->value === $operationName)) {
- return $def->operation;
- }
+ if (! ($def instanceof OperationDefinitionNode)) {
+ continue;
+ }
+
+ if (! $operationName || (isset($def->name->value) && $def->name->value === $operationName)) {
+ return $def->operation;
}
}
}
+
return false;
}
}
diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php
index 36c8ec8..8ca7d16 100644
--- a/src/Utils/ASTDefinitionBuilder.php
+++ b/src/Utils/ASTDefinitionBuilder.php
@@ -1,16 +1,21 @@
typeDefintionsMap = $typeDefintionsMap;
+ /**
+ * @param Node[] $typeDefintionsMap
+ * @param bool[] $options
+ */
+ public function __construct(
+ array $typeDefintionsMap,
+ $options,
+ callable $resolveType,
+ ?callable $typeConfigDecorator = null
+ ) {
+ $this->typeDefintionsMap = $typeDefintionsMap;
$this->typeConfigDecorator = $typeConfigDecorator;
- $this->options = $options;
- $this->resolveType = $resolveType;
+ $this->options = $options;
+ $this->resolveType = $resolveType;
$this->cache = Type::getAllBuiltInTypes();
}
/**
- * @param Type $innerType
* @param TypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
* @return Type
*/
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode)
{
- if ($inputTypeNode->kind == NodeKind::LIST_TYPE) {
+ if ($inputTypeNode->kind === NodeKind::LIST_TYPE) {
return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type));
}
- if ($inputTypeNode->kind == NodeKind::NON_NULL_TYPE) {
+ if ($inputTypeNode->kind === NodeKind::NON_NULL_TYPE) {
$wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
+
return Type::nonNull(NonNull::assertNullableType($wrappedType));
}
+
return $innerType;
}
@@ -95,37 +103,29 @@ class ASTDefinitionBuilder
while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) {
$namedType = $namedType->type;
}
+
return $namedType;
}
/**
- * @param string $typeName
+ * @param string $typeName
* @param NamedTypeNode|null $typeNode
* @return Type
* @throws Error
*/
- private function internalBuildType($typeName, $typeNode = null) {
- if (!isset($this->cache[$typeName])) {
+ private function internalBuildType($typeName, $typeNode = null)
+ {
+ if (! isset($this->cache[$typeName])) {
if (isset($this->typeDefintionsMap[$typeName])) {
$type = $this->makeSchemaDef($this->typeDefintionsMap[$typeName]);
if ($this->typeConfigDecorator) {
$fn = $this->typeConfigDecorator;
try {
$config = $fn($type->config, $this->typeDefintionsMap[$typeName], $this->typeDefintionsMap);
- } catch (\Exception $e) {
- throw new Error(
- "Type config decorator passed to " . (static::class) . " threw an error " .
- "when building $typeName type: {$e->getMessage()}",
- null,
- null,
- null,
- null,
- $e
- );
} catch (\Throwable $e) {
throw new Error(
- "Type config decorator passed to " . (static::class) . " threw an error " .
- "when building $typeName type: {$e->getMessage()}",
+ sprintf('Type config decorator passed to %s threw an error ', static::class) .
+ sprintf('when building %s type: %s', $typeName, $e->getMessage()),
null,
null,
null,
@@ -133,17 +133,20 @@ class ASTDefinitionBuilder
$e
);
}
- if (!is_array($config) || isset($config[0])) {
+ if (! is_array($config) || isset($config[0])) {
throw new Error(
- "Type config decorator passed to " . (static::class) . " is expected to return an array, but got " .
- Utils::getVariableType($config)
+ sprintf(
+ 'Type config decorator passed to %s is expected to return an array, but got %s',
+ static::class,
+ Utils::getVariableType($config)
+ )
);
}
$type = $this->makeSchemaDefFromConfig($this->typeDefintionsMap[$typeName], $config);
}
$this->cache[$typeName] = $type;
} else {
- $fn = $this->resolveType;
+ $fn = $this->resolveType;
$this->cache[$typeName] = $fn($typeName, $typeNode);
}
}
@@ -166,26 +169,29 @@ class ASTDefinitionBuilder
}
/**
- * @param TypeNode $typeNode
* @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,
+ '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,
+ 'locations' => Utils::map(
+ $directiveNode->locations,
+ function ($node) {
+ return $node->value;
+ }
+ ),
+ 'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
+ 'astNode' => $directiveNode,
]);
}
@@ -195,17 +201,22 @@ class ASTDefinitionBuilder
// 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,
+ 'type' => $this->internalBuildWrappedType($field->type),
+ 'description' => $this->getDescription($field),
+ 'args' => $field->arguments ? $this->makeInputValues($field->arguments) : null,
'deprecationReason' => $this->getDeprecationReason($field),
- 'astNode' => $field,
+ 'astNode' => $field,
];
}
+ /**
+ * @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeDefinitionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode|UnionTypeDefinitionNode $def
+ * @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType
+ * @throws Error
+ */
private function makeSchemaDef($def)
{
- if (!$def) {
+ if (! $def) {
throw new Error('def must be defined.');
}
switch ($def->kind) {
@@ -222,13 +233,19 @@ class ASTDefinitionBuilder
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
return $this->makeInputObjectDef($def);
default:
- throw new Error("Type kind of {$def->kind} not supported.");
+ throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
}
}
+ /**
+ * @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) {
+ if (! $def) {
throw new Error('def must be defined.');
}
switch ($def->kind) {
@@ -245,23 +262,24 @@ class ASTDefinitionBuilder
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
return new InputObjectType($config);
default:
- throw new Error("Type kind of {$def->kind} not supported.");
+ throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
}
}
private function makeTypeDef(ObjectTypeDefinitionNode $def)
{
$typeName = $def->name->value;
+
return new ObjectType([
- 'name' => $typeName,
+ 'name' => $typeName,
'description' => $this->getDescription($def),
- 'fields' => function () use ($def) {
+ 'fields' => function () use ($def) {
return $this->makeFieldDefMap($def);
},
- 'interfaces' => function () use ($def) {
+ 'interfaces' => function () use ($def) {
return $this->makeImplementedInterfaces($def);
},
- 'astNode' => $def
+ 'astNode' => $def,
]);
}
@@ -286,10 +304,14 @@ class ASTDefinitionBuilder
// Note: While this could make early assertions to get the correctly
// typed values, that would throw immediately while type system
// validation with validateSchema() will produce more actionable results.
- return Utils::map($def->interfaces, function ($iface) {
- return $this->buildType($iface);
- });
+ return Utils::map(
+ $def->interfaces,
+ function ($iface) {
+ return $this->buildType($iface);
+ }
+ );
}
+
return null;
}
@@ -304,16 +326,17 @@ class ASTDefinitionBuilder
// 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);
+ $type = $this->internalBuildWrappedType($value->type);
$config = [
- 'name' => $value->name->value,
- 'type' => $type,
+ 'name' => $value->name->value,
+ 'type' => $type,
'description' => $this->getDescription($value),
- 'astNode' => $value
+ 'astNode' => $value,
];
if (isset($value->defaultValue)) {
$config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type);
}
+
return $config;
}
);
@@ -322,22 +345,23 @@ class ASTDefinitionBuilder
private function makeInterfaceDef(InterfaceTypeDefinitionNode $def)
{
$typeName = $def->name->value;
+
return new InterfaceType([
- 'name' => $typeName,
+ 'name' => $typeName,
'description' => $this->getDescription($def),
- 'fields' => function () use ($def) {
+ 'fields' => function () use ($def) {
return $this->makeFieldDefMap($def);
},
- 'astNode' => $def
+ 'astNode' => $def,
]);
}
private function makeEnumDef(EnumTypeDefinitionNode $def)
{
return new EnumType([
- 'name' => $def->name->value,
+ 'name' => $def->name->value,
'description' => $this->getDescription($def),
- 'values' => $def->values
+ 'values' => $def->values
? Utils::keyValMap(
$def->values,
function ($enumValue) {
@@ -345,41 +369,44 @@ class ASTDefinitionBuilder
},
function ($enumValue) {
return [
- 'description' => $this->getDescription($enumValue),
+ 'description' => $this->getDescription($enumValue),
'deprecationReason' => $this->getDeprecationReason($enumValue),
- 'astNode' => $enumValue
+ 'astNode' => $enumValue,
];
}
)
: [],
- 'astNode' => $def,
+ 'astNode' => $def,
]);
}
private function makeUnionDef(UnionTypeDefinitionNode $def)
{
return new UnionType([
- 'name' => $def->name->value,
+ 'name' => $def->name->value,
'description' => $this->getDescription($def),
// Note: While this could make assertions to get the correctly typed
// values below, that would throw immediately while type system
// validation with validateSchema() will produce more actionable results.
- 'types' => $def->types
- ? Utils::map($def->types, function ($typeNode) {
- return $this->buildType($typeNode);
- }):
+ 'types' => $def->types
+ ? Utils::map(
+ $def->types,
+ function ($typeNode) {
+ return $this->buildType($typeNode);
+ }
+ ) :
[],
- 'astNode' => $def,
+ 'astNode' => $def,
]);
}
private function makeScalarDef(ScalarTypeDefinitionNode $def)
{
return new CustomScalarType([
- 'name' => $def->name->value,
+ 'name' => $def->name->value,
'description' => $this->getDescription($def),
- 'astNode' => $def,
- 'serialize' => function($value) {
+ 'astNode' => $def,
+ 'serialize' => function ($value) {
return $value;
},
]);
@@ -388,14 +415,14 @@ class ASTDefinitionBuilder
private function makeInputObjectDef(InputObjectTypeDefinitionNode $def)
{
return new InputObjectType([
- 'name' => $def->name->value,
+ 'name' => $def->name->value,
'description' => $this->getDescription($def),
- 'fields' => function () use ($def) {
+ 'fields' => function () use ($def) {
return $def->fields
? $this->makeInputValues($def->fields)
: [];
},
- 'astNode' => $def,
+ 'astNode' => $def,
]);
}
@@ -409,7 +436,8 @@ class ASTDefinitionBuilder
private function getDeprecationReason($node)
{
$deprecated = Values::getDirectiveValues(Directive::deprecatedDirective(), $node);
- return isset($deprecated['reason']) ? $deprecated['reason'] : null;
+
+ return $deprecated['reason'] ?? null;
}
/**
@@ -433,21 +461,20 @@ class ASTDefinitionBuilder
private function getLeadingCommentBlock($node)
{
$loc = $node->loc;
- if (!$loc || !$loc->startToken) {
+ if (! $loc || ! $loc->startToken) {
return null;
}
$comments = [];
- $token = $loc->startToken->prev;
- while (
- $token &&
+ $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;
+ $value = $token->value;
$comments[] = $value;
- $token = $token->prev;
+ $token = $token->prev;
}
return implode("\n", array_reverse($comments));
diff --git a/src/Utils/BlockString.php b/src/Utils/BlockString.php
index eac943d..5bc40e4 100644
--- a/src/Utils/BlockString.php
+++ b/src/Utils/BlockString.php
@@ -53,9 +53,9 @@ class BlockString {
private static function leadingWhitespace($str) {
$i = 0;
while ($i < mb_strlen($str) && ($str[$i] === ' ' || $str[$i] === '\t')) {
- $i++;
+ $i++;
}
return $i;
}
-}
\ No newline at end of file
+}
diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php
index a1773a0..7c6b2de 100644
--- a/src/Utils/TypeInfo.php
+++ b/src/Utils/TypeInfo.php
@@ -58,7 +58,7 @@ class TypeInfo
/**
* @param Schema $schema
* @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
- * @return Type
+ * @return Type|null
* @throws InvariantViolation
*/
public static function typeFromAST(Schema $schema, $inputTypeNode)
@@ -264,7 +264,7 @@ class TypeInfo
}
/**
- * @return CompositeType
+ * @return Type
*/
function getParentType()
{
diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php
index 201a850..5e5540a 100644
--- a/src/Validator/DocumentValidator.php
+++ b/src/Validator/DocumentValidator.php
@@ -1,14 +1,15 @@
new ExecutableDefinitions(),
- UniqueOperationNames::class => new UniqueOperationNames(),
- LoneAnonymousOperation::class => new LoneAnonymousOperation(),
- KnownTypeNames::class => new KnownTypeNames(),
- FragmentsOnCompositeTypes::class => new FragmentsOnCompositeTypes(),
- VariablesAreInputTypes::class => new VariablesAreInputTypes(),
- ScalarLeafs::class => new ScalarLeafs(),
- FieldsOnCorrectType::class => new FieldsOnCorrectType(),
- UniqueFragmentNames::class => new UniqueFragmentNames(),
- KnownFragmentNames::class => new KnownFragmentNames(),
- NoUnusedFragments::class => new NoUnusedFragments(),
- PossibleFragmentSpreads::class => new PossibleFragmentSpreads(),
- NoFragmentCycles::class => new NoFragmentCycles(),
- UniqueVariableNames::class => new UniqueVariableNames(),
- NoUndefinedVariables::class => new NoUndefinedVariables(),
- NoUnusedVariables::class => new NoUnusedVariables(),
- KnownDirectives::class => new KnownDirectives(),
- UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),
- KnownArgumentNames::class => new KnownArgumentNames(),
- UniqueArgumentNames::class => new UniqueArgumentNames(),
- ValuesOfCorrectType::class => new ValuesOfCorrectType(),
- ProvidedNonNullArguments::class => new ProvidedNonNullArguments(),
+ ExecutableDefinitions::class => new ExecutableDefinitions(),
+ UniqueOperationNames::class => new UniqueOperationNames(),
+ LoneAnonymousOperation::class => new LoneAnonymousOperation(),
+ KnownTypeNames::class => new KnownTypeNames(),
+ FragmentsOnCompositeTypes::class => new FragmentsOnCompositeTypes(),
+ VariablesAreInputTypes::class => new VariablesAreInputTypes(),
+ ScalarLeafs::class => new ScalarLeafs(),
+ FieldsOnCorrectType::class => new FieldsOnCorrectType(),
+ UniqueFragmentNames::class => new UniqueFragmentNames(),
+ KnownFragmentNames::class => new KnownFragmentNames(),
+ NoUnusedFragments::class => new NoUnusedFragments(),
+ PossibleFragmentSpreads::class => new PossibleFragmentSpreads(),
+ NoFragmentCycles::class => new NoFragmentCycles(),
+ UniqueVariableNames::class => new UniqueVariableNames(),
+ NoUndefinedVariables::class => new NoUndefinedVariables(),
+ NoUnusedVariables::class => new NoUnusedVariables(),
+ KnownDirectives::class => new KnownDirectives(),
+ UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),
+ KnownArgumentNames::class => new KnownArgumentNames(),
+ UniqueArgumentNames::class => new UniqueArgumentNames(),
+ ValuesOfCorrectType::class => new ValuesOfCorrectType(),
+ ProvidedNonNullArguments::class => new ProvidedNonNullArguments(),
VariablesDefaultValueAllowed::class => new VariablesDefaultValueAllowed(),
- VariablesInAllowedPosition::class => new VariablesInAllowedPosition(),
+ VariablesInAllowedPosition::class => new VariablesInAllowedPosition(),
OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(),
- UniqueInputFieldNames::class => new UniqueInputFieldNames(),
+ UniqueInputFieldNames::class => new UniqueInputFieldNames(),
];
}
@@ -151,7 +159,7 @@ class DocumentValidator
}
/**
- * @return array
+ * @return QuerySecurityRule[]
*/
public static function securityRules()
{
@@ -159,16 +167,36 @@ class DocumentValidator
// When custom security rule is required - it should be just added via DocumentValidator::addRule();
// TODO: deprecate this
- if (null === self::$securityRules) {
+ if (self::$securityRules === null) {
self::$securityRules = [
DisableIntrospection::class => new DisableIntrospection(DisableIntrospection::DISABLED), // DEFAULT DISABLED
- QueryDepth::class => new QueryDepth(QueryDepth::DISABLED), // default disabled
- QueryComplexity::class => new QueryComplexity(QueryComplexity::DISABLED), // default disabled
+ QueryDepth::class => new QueryDepth(QueryDepth::DISABLED), // default disabled
+ QueryComplexity::class => new QueryComplexity(QueryComplexity::DISABLED), // default disabled
];
}
+
return self::$securityRules;
}
+ /**
+ * This uses a specialized visitor which runs multiple visitors in parallel,
+ * while maintaining the visitor skip and break API.
+ *
+ * @param ValidationRule[] $rules
+ * @return Error[]
+ */
+ public static function visitUsingRules(Schema $schema, TypeInfo $typeInfo, DocumentNode $documentNode, array $rules)
+ {
+ $context = new ValidationContext($schema, $documentNode, $typeInfo);
+ $visitors = [];
+ foreach ($rules as $rule) {
+ $visitors[] = $rule->getVisitor($context);
+ }
+ Visitor::visit($documentNode, Visitor::visitWithTypeInfo($typeInfo, Visitor::visitInParallel($visitors)));
+
+ return $context->getErrors();
+ }
+
/**
* Returns global validation rule by name. Standard rules are named by class name, so
* example usage for such rules:
@@ -177,7 +205,7 @@ class DocumentValidator
*
* @api
* @param string $name
- * @return AbstractValidationRule
+ * @return ValidationRule
*/
public static function getRule($name)
{
@@ -187,17 +215,17 @@ class DocumentValidator
return $rules[$name];
}
- $name = "GraphQL\\Validator\\Rules\\$name";
- return isset($rules[$name]) ? $rules[$name] : null ;
+ $name = sprintf('GraphQL\\Validator\\Rules\\%s', $name);
+
+ return $rules[$name] ?? null;
}
/**
* Add rule to list of global validation rules
*
* @api
- * @param AbstractValidationRule $rule
*/
- public static function addRule(AbstractValidationRule $rule)
+ public static function addRule(ValidationRule $rule)
{
self::$rules[$rule->getName()] = $rule;
}
@@ -205,7 +233,12 @@ class DocumentValidator
public static function isError($value)
{
return is_array($value)
- ? count(array_filter($value, function($item) { return $item instanceof \Exception || $item instanceof \Throwable;})) === count($value)
+ ? count(array_filter(
+ $value,
+ function ($item) {
+ return $item instanceof \Exception || $item instanceof \Throwable;
+ }
+ )) === count($value)
: ($value instanceof \Exception || $value instanceof \Throwable);
}
@@ -216,6 +249,7 @@ class DocumentValidator
} else {
$arr[] = $items;
}
+
return $arr;
}
@@ -230,33 +264,13 @@ class DocumentValidator
public static function isValidLiteralValue(Type $type, $valueNode)
{
$emptySchema = new Schema([]);
- $emptyDoc = new DocumentNode(['definitions' => []]);
- $typeInfo = new TypeInfo($emptySchema, $type);
- $context = new ValidationContext($emptySchema, $emptyDoc, $typeInfo);
- $validator = new ValuesOfCorrectType();
- $visitor = $validator->getVisitor($context);
+ $emptyDoc = new DocumentNode(['definitions' => []]);
+ $typeInfo = new TypeInfo($emptySchema, $type);
+ $context = new ValidationContext($emptySchema, $emptyDoc, $typeInfo);
+ $validator = new ValuesOfCorrectType();
+ $visitor = $validator->getVisitor($context);
Visitor::visit($valueNode, Visitor::visitWithTypeInfo($typeInfo, $visitor));
- return $context->getErrors();
- }
- /**
- * This uses a specialized visitor which runs multiple visitors in parallel,
- * while maintaining the visitor skip and break API.
- *
- * @param Schema $schema
- * @param TypeInfo $typeInfo
- * @param DocumentNode $documentNode
- * @param AbstractValidationRule[] $rules
- * @return array
- */
- public static function visitUsingRules(Schema $schema, TypeInfo $typeInfo, DocumentNode $documentNode, array $rules)
- {
- $context = new ValidationContext($schema, $documentNode, $typeInfo);
- $visitors = [];
- foreach ($rules as $rule) {
- $visitors[] = $rule->getVisitor($context);
- }
- Visitor::visit($documentNode, Visitor::visitWithTypeInfo($typeInfo, Visitor::visitInParallel($visitors)));
return $context->getErrors();
}
}
diff --git a/src/Validator/Rules/CustomValidationRule.php b/src/Validator/Rules/CustomValidationRule.php
index 5ccc606..83101a1 100644
--- a/src/Validator/Rules/CustomValidationRule.php
+++ b/src/Validator/Rules/CustomValidationRule.php
@@ -1,26 +1,30 @@
name = $name;
+ $this->name = $name;
$this->visitorFn = $visitorFn;
}
/**
- * @param ValidationContext $context
* @return Error[]
*/
public function getVisitor(ValidationContext $context)
{
$fn = $this->visitorFn;
+
return $fn($context);
}
}
diff --git a/src/Validator/Rules/DisableIntrospection.php b/src/Validator/Rules/DisableIntrospection.php
index dec9f37..bdb7a7c 100644
--- a/src/Validator/Rules/DisableIntrospection.php
+++ b/src/Validator/Rules/DisableIntrospection.php
@@ -1,4 +1,7 @@
isEnabled = $enabled;
}
- static function introspectionDisabledMessage()
+ public function getVisitor(ValidationContext $context)
+ {
+ return $this->invokeIfNeeded(
+ $context,
+ [
+ NodeKind::FIELD => function (FieldNode $node) use ($context) {
+ if ($node->name->value !== '__type' && $node->name->value !== '__schema') {
+ return;
+ }
+
+ $context->reportError(new Error(
+ static::introspectionDisabledMessage(),
+ [$node]
+ ));
+ },
+ ]
+ );
+ }
+
+ public static function introspectionDisabledMessage()
{
return 'GraphQL introspection is not allowed, but the query contained __schema or __type';
}
@@ -30,21 +54,4 @@ class DisableIntrospection extends AbstractQuerySecurity
{
return $this->isEnabled !== static::DISABLED;
}
-
- public function getVisitor(ValidationContext $context)
- {
- return $this->invokeIfNeeded(
- $context,
- [
- NodeKind::FIELD => function (FieldNode $node) use ($context) {
- if ($node->name->value === '__type' || $node->name->value === '__schema') {
- $context->reportError(new Error(
- static::introspectionDisabledMessage(),
- [$node]
- ));
- }
- }
- ]
- );
- }
}
diff --git a/src/Validator/Rules/ExecutableDefinitions.php b/src/Validator/Rules/ExecutableDefinitions.php
index f512d6d..325b320 100644
--- a/src/Validator/Rules/ExecutableDefinitions.php
+++ b/src/Validator/Rules/ExecutableDefinitions.php
@@ -1,4 +1,7 @@
function (DocumentNode $node) use ($context) {
/** @var Node $definition */
foreach ($node->definitions as $definition) {
- if (
- !$definition instanceof OperationDefinitionNode &&
- !$definition instanceof FragmentDefinitionNode
+ if ($definition instanceof OperationDefinitionNode ||
+ $definition instanceof FragmentDefinitionNode
) {
- $context->reportError(new Error(
- self::nonExecutableDefinitionMessage($definition->name->value),
- [$definition->name]
- ));
+ continue;
}
+
+ $context->reportError(new Error(
+ self::nonExecutableDefinitionMessage($definition->name->value),
+ [$definition->name]
+ ));
}
return Visitor::skipNode();
- }
+ },
];
}
+
+ public static function nonExecutableDefinitionMessage($defName)
+ {
+ return sprintf('The "%s" definition is not executable.', $defName);
+ }
}
diff --git a/src/Validator/Rules/FieldsOnCorrectType.php b/src/Validator/Rules/FieldsOnCorrectType.php
index 1244a72..104849c 100644
--- a/src/Validator/Rules/FieldsOnCorrectType.php
+++ b/src/Validator/Rules/FieldsOnCorrectType.php
@@ -1,4 +1,7 @@
function(FieldNode $node) use ($context) {
+ NodeKind::FIELD => function (FieldNode $node) use ($context) {
$type = $context->getParentType();
- if ($type) {
- $fieldDef = $context->getFieldDef();
- if (!$fieldDef) {
- // This isn't valid. Let's find suggestions, if any.
- $schema = $context->getSchema();
- $fieldName = $node->name->value;
- // First determine if there are any suggested types to condition on.
- $suggestedTypeNames = $this->getSuggestedTypeNames(
- $schema,
- $type,
- $fieldName
- );
- // If there are no suggested types, then perhaps this was a typo?
- $suggestedFieldNames = $suggestedTypeNames
- ? []
- : $this->getSuggestedFieldNames(
- $schema,
- $type,
- $fieldName
- );
-
- // Report an error, including helpful suggestions.
- $context->reportError(new Error(
- static::undefinedFieldMessage(
- $node->name->value,
- $type->name,
- $suggestedTypeNames,
- $suggestedFieldNames
- ),
- [$node]
- ));
- }
+ if (! $type) {
+ return;
}
- }
+
+ $fieldDef = $context->getFieldDef();
+ if ($fieldDef) {
+ return;
+ }
+
+ // This isn't valid. Let's find suggestions, if any.
+ $schema = $context->getSchema();
+ $fieldName = $node->name->value;
+ // First determine if there are any suggested types to condition on.
+ $suggestedTypeNames = $this->getSuggestedTypeNames(
+ $schema,
+ $type,
+ $fieldName
+ );
+ // If there are no suggested types, then perhaps this was a typo?
+ $suggestedFieldNames = $suggestedTypeNames
+ ? []
+ : $this->getSuggestedFieldNames(
+ $schema,
+ $type,
+ $fieldName
+ );
+
+ // Report an error, including helpful suggestions.
+ $context->reportError(new Error(
+ static::undefinedFieldMessage(
+ $node->name->value,
+ $type->name,
+ $suggestedTypeNames,
+ $suggestedFieldNames
+ ),
+ [$node]
+ ));
+ },
];
}
@@ -75,32 +72,31 @@ class FieldsOnCorrectType extends AbstractValidationRule
* suggest them, sorted by how often the type is referenced, starting
* with Interfaces.
*
- * @param Schema $schema
- * @param $type
- * @param string $fieldName
- * @return array
+ * @param ObjectType|InterfaceType $type
+ * @param string $fieldName
+ * @return string[]
*/
private function getSuggestedTypeNames(Schema $schema, $type, $fieldName)
{
if (Type::isAbstractType($type)) {
$suggestedObjectTypes = [];
- $interfaceUsageCount = [];
+ $interfaceUsageCount = [];
- foreach($schema->getPossibleTypes($type) as $possibleType) {
+ foreach ($schema->getPossibleTypes($type) as $possibleType) {
$fields = $possibleType->getFields();
- if (!isset($fields[$fieldName])) {
+ if (! isset($fields[$fieldName])) {
continue;
}
// This object type defines this field.
$suggestedObjectTypes[] = $possibleType->name;
- foreach($possibleType->getInterfaces() as $possibleInterface) {
+ foreach ($possibleType->getInterfaces() as $possibleInterface) {
$fields = $possibleInterface->getFields();
- if (!isset($fields[$fieldName])) {
+ if (! isset($fields[$fieldName])) {
continue;
}
// This interface type defines this field.
$interfaceUsageCount[$possibleInterface->name] =
- !isset($interfaceUsageCount[$possibleInterface->name])
+ ! isset($interfaceUsageCount[$possibleInterface->name])
? 0
: $interfaceUsageCount[$possibleInterface->name] + 1;
}
@@ -122,18 +118,47 @@ class FieldsOnCorrectType extends AbstractValidationRule
* For the field name provided, determine if there are any similar field names
* that may be the result of a typo.
*
- * @param Schema $schema
- * @param $type
- * @param string $fieldName
+ * @param ObjectType|InterfaceType $type
+ * @param string $fieldName
* @return array|string[]
*/
private function getSuggestedFieldNames(Schema $schema, $type, $fieldName)
{
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
$possibleFieldNames = array_keys($type->getFields());
+
return Utils::suggestionList($fieldName, $possibleFieldNames);
}
+
// Otherwise, must be a Union type, which does not define fields.
return [];
}
+
+ /**
+ * @param string $fieldName
+ * @param string $type
+ * @param string[] $suggestedTypeNames
+ * @param string[] $suggestedFieldNames
+ * @return string
+ */
+ public static function undefinedFieldMessage(
+ $fieldName,
+ $type,
+ array $suggestedTypeNames,
+ array $suggestedFieldNames
+ ) {
+ $message = sprintf('Cannot query field "%s" on type "%s".', $fieldName, $type);
+
+ if ($suggestedTypeNames) {
+ $suggestions = Utils::quotedOrList($suggestedTypeNames);
+
+ $message .= sprintf(' Did you mean to use an inline fragment on %s?', $suggestions);
+ } elseif (! empty($suggestedFieldNames)) {
+ $suggestions = Utils::quotedOrList($suggestedFieldNames);
+
+ $message .= sprintf(' Did you mean %s?', $suggestions);
+ }
+
+ return $message;
+ }
}
diff --git a/src/Validator/Rules/FragmentsOnCompositeTypes.php b/src/Validator/Rules/FragmentsOnCompositeTypes.php
index f2731d0..e6f8f16 100644
--- a/src/Validator/Rules/FragmentsOnCompositeTypes.php
+++ b/src/Validator/Rules/FragmentsOnCompositeTypes.php
@@ -1,4 +1,7 @@
function(InlineFragmentNode $node) use ($context) {
- if ($node->typeCondition) {
- $type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition);
- if ($type && !Type::isCompositeType($type)) {
- $context->reportError(new Error(
- static::inlineFragmentOnNonCompositeErrorMessage($type),
- [$node->typeCondition]
- ));
- }
+ NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) use ($context) {
+ if (! $node->typeCondition) {
+ return;
}
+
+ $type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition);
+ if (! $type || Type::isCompositeType($type)) {
+ return;
+ }
+
+ $context->reportError(new Error(
+ static::inlineFragmentOnNonCompositeErrorMessage($type),
+ [$node->typeCondition]
+ ));
},
- NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) use ($context) {
+ NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
$type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition);
- if ($type && !Type::isCompositeType($type)) {
- $context->reportError(new Error(
- static::fragmentOnNonCompositeErrorMessage($node->name->value, Printer::doPrint($node->typeCondition)),
- [$node->typeCondition]
- ));
+ if (! $type || Type::isCompositeType($type)) {
+ return;
}
- }
+
+ $context->reportError(new Error(
+ static::fragmentOnNonCompositeErrorMessage(
+ $node->name->value,
+ Printer::doPrint($node->typeCondition)
+ ),
+ [$node->typeCondition]
+ ));
+ },
];
}
+
+ public static function inlineFragmentOnNonCompositeErrorMessage($type)
+ {
+ return sprintf('Fragment cannot condition on non composite type "%s".', $type);
+ }
+
+ public static function fragmentOnNonCompositeErrorMessage($fragName, $type)
+ {
+ return sprintf('Fragment "%s" cannot condition on non composite type "%s".', $fragName, $type);
+ }
}
diff --git a/src/Validator/Rules/KnownArgumentNames.php b/src/Validator/Rules/KnownArgumentNames.php
index 15a77ab..05a52ef 100644
--- a/src/Validator/Rules/KnownArgumentNames.php
+++ b/src/Validator/Rules/KnownArgumentNames.php
@@ -1,11 +1,19 @@
function(ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) {
+ NodeKind::ARGUMENT => function (ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) {
+ /** @var NodeList|Node[] $ancestors */
$argDef = $context->getArgument();
- if (!$argDef) {
- $argumentOf = $ancestors[count($ancestors) - 1];
- if ($argumentOf->kind === NodeKind::FIELD) {
- $fieldDef = $context->getFieldDef();
- $parentType = $context->getParentType();
- if ($fieldDef && $parentType) {
- $context->reportError(new Error(
- self::unknownArgMessage(
+ if ($argDef !== null) {
+ return;
+ }
+
+ $argumentOf = $ancestors[count($ancestors) - 1];
+ if ($argumentOf->kind === NodeKind::FIELD) {
+ $fieldDef = $context->getFieldDef();
+ $parentType = $context->getParentType();
+ if ($fieldDef && $parentType) {
+ $context->reportError(new Error(
+ self::unknownArgMessage(
+ $node->name->value,
+ $fieldDef->name,
+ $parentType->name,
+ Utils::suggestionList(
$node->name->value,
- $fieldDef->name,
- $parentType->name,
- Utils::suggestionList(
- $node->name->value,
- array_map(function ($arg) { return $arg->name; }, $fieldDef->args)
+ array_map(
+ function ($arg) {
+ return $arg->name;
+ },
+ $fieldDef->args
)
- ),
- [$node]
- ));
- }
- } else if ($argumentOf->kind === NodeKind::DIRECTIVE) {
- $directive = $context->getDirective();
- if ($directive) {
- $context->reportError(new Error(
- self::unknownDirectiveArgMessage(
+ )
+ ),
+ [$node]
+ ));
+ }
+ } elseif ($argumentOf->kind === NodeKind::DIRECTIVE) {
+ $directive = $context->getDirective();
+ if ($directive) {
+ $context->reportError(new Error(
+ self::unknownDirectiveArgMessage(
+ $node->name->value,
+ $directive->name,
+ Utils::suggestionList(
$node->name->value,
- $directive->name,
- Utils::suggestionList(
- $node->name->value,
- array_map(function ($arg) { return $arg->name; }, $directive->args)
+ array_map(
+ function ($arg) {
+ return $arg->name;
+ },
+ $directive->args
)
- ),
- [$node]
- ));
- }
+ )
+ ),
+ [$node]
+ ));
}
}
- }
+ },
];
}
+
+ /**
+ * @param string[] $suggestedArgs
+ */
+ public static function unknownArgMessage($argName, $fieldName, $typeName, array $suggestedArgs)
+ {
+ $message = sprintf('Unknown argument "%s" on field "%s" of type "%s".', $argName, $fieldName, $typeName);
+ if (! empty($suggestedArgs)) {
+ $message .= sprintf(' Did you mean %s?', Utils::quotedOrList($suggestedArgs));
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param string[] $suggestedArgs
+ */
+ public static function unknownDirectiveArgMessage($argName, $directiveName, array $suggestedArgs)
+ {
+ $message = sprintf('Unknown argument "%s" on directive "@%s".', $argName, $directiveName);
+ if (! empty($suggestedArgs)) {
+ $message .= sprintf(' Did you mean %s?', Utils::quotedOrList($suggestedArgs));
+ }
+
+ return $message;
+ }
}
diff --git a/src/Validator/Rules/KnownDirectives.php b/src/Validator/Rules/KnownDirectives.php
index 4ec3a01..ecccafd 100644
--- a/src/Validator/Rules/KnownDirectives.php
+++ b/src/Validator/Rules/KnownDirectives.php
@@ -1,4 +1,7 @@
reportError(new Error(
self::unknownDirectiveMessage($node->name->value),
[$node]
));
+
return;
}
$candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
- if (!$candidateLocation) {
+ if (! $candidateLocation) {
$context->reportError(new Error(
self::misplacedDirectiveMessage($node->name->value, $node->type),
[$node]
));
- } else if (!in_array($candidateLocation, $directiveDef->locations)) {
+ } elseif (! in_array($candidateLocation, $directiveDef->locations)) {
$context->reportError(new Error(
self::misplacedDirectiveMessage($node->name->value, $candidateLocation),
- [ $node ]
+ [$node]
));
}
- }
+ },
];
}
+ public static function unknownDirectiveMessage($directiveName)
+ {
+ return sprintf('Unknown directive "%s".', $directiveName);
+ }
+
+ /**
+ * @param (Node|NodeList)[] $ancestors
+ */
private function getDirectiveLocationForASTPath(array $ancestors)
{
$appliedTo = $ancestors[count($ancestors) - 1];
switch ($appliedTo->kind) {
case NodeKind::OPERATION_DEFINITION:
switch ($appliedTo->operation) {
- case 'query': return DirectiveLocation::QUERY;
- case 'mutation': return DirectiveLocation::MUTATION;
- case 'subscription': return DirectiveLocation::SUBSCRIPTION;
+ case 'query':
+ return DirectiveLocation::QUERY;
+ case 'mutation':
+ return DirectiveLocation::MUTATION;
+ case 'subscription':
+ return DirectiveLocation::SUBSCRIPTION;
}
break;
case NodeKind::FIELD:
@@ -101,9 +109,15 @@ class KnownDirectives extends AbstractValidationRule
return DirectiveLocation::INPUT_OBJECT;
case NodeKind::INPUT_VALUE_DEFINITION:
$parentNode = $ancestors[count($ancestors) - 3];
+
return $parentNode instanceof InputObjectTypeDefinitionNode
? DirectiveLocation::INPUT_FIELD_DEFINITION
: DirectiveLocation::ARGUMENT_DEFINITION;
}
}
+
+ public static function misplacedDirectiveMessage($directiveName, $location)
+ {
+ return sprintf('Directive "%s" may not be used on "%s".', $directiveName, $location);
+ }
}
diff --git a/src/Validator/Rules/KnownFragmentNames.php b/src/Validator/Rules/KnownFragmentNames.php
index ad5bc27..1b2b4e1 100644
--- a/src/Validator/Rules/KnownFragmentNames.php
+++ b/src/Validator/Rules/KnownFragmentNames.php
@@ -1,31 +1,40 @@
function(FragmentSpreadNode $node) use ($context) {
+ NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) use ($context) {
$fragmentName = $node->name->value;
- $fragment = $context->getFragment($fragmentName);
- if (!$fragment) {
- $context->reportError(new Error(
- self::unknownFragmentMessage($fragmentName),
- [$node->name]
- ));
+ $fragment = $context->getFragment($fragmentName);
+ if ($fragment) {
+ return;
}
- }
+
+ $context->reportError(new Error(
+ self::unknownFragmentMessage($fragmentName),
+ [$node->name]
+ ));
+ },
];
}
+
+ /**
+ * @param string $fragName
+ */
+ public static function unknownFragmentMessage($fragName)
+ {
+ return sprintf('Unknown fragment "%s".', $fragName);
+ }
}
diff --git a/src/Validator/Rules/KnownTypeNames.php b/src/Validator/Rules/KnownTypeNames.php
index 935b3ad..4700103 100644
--- a/src/Validator/Rules/KnownTypeNames.php
+++ b/src/Validator/Rules/KnownTypeNames.php
@@ -1,4 +1,7 @@
$skip,
- NodeKind::INTERFACE_TYPE_DEFINITION => $skip,
- NodeKind::UNION_TYPE_DEFINITION => $skip,
+ NodeKind::OBJECT_TYPE_DEFINITION => $skip,
+ NodeKind::INTERFACE_TYPE_DEFINITION => $skip,
+ NodeKind::UNION_TYPE_DEFINITION => $skip,
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $skip,
- NodeKind::NAMED_TYPE => function(NamedTypeNode $node) use ($context) {
- $schema = $context->getSchema();
+ NodeKind::NAMED_TYPE => function (NamedTypeNode $node) use ($context) {
+ $schema = $context->getSchema();
$typeName = $node->name->value;
- $type = $schema->getType($typeName);
- if (!$type) {
- $context->reportError(new Error(
- self::unknownTypeMessage(
- $typeName,
- Utils::suggestionList($typeName, array_keys($schema->getTypeMap()))
- ), [$node])
- );
+ $type = $schema->getType($typeName);
+ if ($type !== null) {
+ return;
}
- }
+
+ $context->reportError(new Error(
+ self::unknownTypeMessage(
+ $typeName,
+ Utils::suggestionList($typeName, array_keys($schema->getTypeMap()))
+ ),
+ [$node]
+ ));
+ },
];
}
+
+ /**
+ * @param string $type
+ * @param string[] $suggestedTypes
+ */
+ public static function unknownTypeMessage($type, array $suggestedTypes)
+ {
+ $message = sprintf('Unknown type "%s".', $type);
+ if (! empty($suggestedTypes)) {
+ $suggestions = Utils::quotedOrList($suggestedTypes);
+
+ $message .= sprintf(' Did you mean %s?', $suggestions);
+ }
+
+ return $message;
+ }
}
diff --git a/src/Validator/Rules/LoneAnonymousOperation.php b/src/Validator/Rules/LoneAnonymousOperation.php
index 7f848ef..06730f7 100644
--- a/src/Validator/Rules/LoneAnonymousOperation.php
+++ b/src/Validator/Rules/LoneAnonymousOperation.php
@@ -1,12 +1,17 @@
function(DocumentNode $node) use (&$operationCount) {
+ NodeKind::DOCUMENT => function (DocumentNode $node) use (&$operationCount) {
$tmp = Utils::filter(
$node->definitions,
- function ($definition) {
+ function (Node $definition) {
return $definition->kind === NodeKind::OPERATION_DEFINITION;
}
);
+
$operationCount = count($tmp);
},
- NodeKind::OPERATION_DEFINITION => function(OperationDefinitionNode $node) use (&$operationCount, $context) {
- if (!$node->name && $operationCount > 1) {
- $context->reportError(
- new Error(self::anonOperationNotAloneMessage(), [$node])
- );
+ NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use (
+ &$operationCount,
+ $context
+ ) {
+ if ($node->name || $operationCount <= 1) {
+ return;
}
- }
+
+ $context->reportError(
+ new Error(self::anonOperationNotAloneMessage(), [$node])
+ );
+ },
];
}
+
+ public static function anonOperationNotAloneMessage()
+ {
+ return 'This anonymous operation must be the only defined operation.';
+ }
}
diff --git a/src/Validator/Rules/NoFragmentCycles.php b/src/Validator/Rules/NoFragmentCycles.php
index df81b77..0b0a1e6 100644
--- a/src/Validator/Rules/NoFragmentCycles.php
+++ b/src/Validator/Rules/NoFragmentCycles.php
@@ -1,25 +1,33 @@
function () {
return Visitor::skipNode();
},
- NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
- if (!isset($this->visitedFrags[$node->name->value])) {
+ NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
+ if (! isset($this->visitedFrags[$node->name->value])) {
$this->detectCycleRecursive($node, $context);
}
+
return Visitor::skipNode();
- }
+ },
];
}
private function detectCycleRecursive(FragmentDefinitionNode $fragment, ValidationContext $context)
{
- $fragmentName = $fragment->name->value;
+ $fragmentName = $fragment->name->value;
$this->visitedFrags[$fragmentName] = true;
$spreadNodes = $context->getFragmentSpreads($fragment);
@@ -63,7 +72,7 @@ class NoFragmentCycles extends AbstractValidationRule
for ($i = 0; $i < count($spreadNodes); $i++) {
$spreadNode = $spreadNodes[$i];
$spreadName = $spreadNode->name->value;
- $cycleIndex = isset($this->spreadPathIndexByName[$spreadName]) ? $this->spreadPathIndexByName[$spreadName] : null;
+ $cycleIndex = $this->spreadPathIndexByName[$spreadName] ?? null;
if ($cycleIndex === null) {
$this->spreadPath[] = $spreadNode;
@@ -76,7 +85,7 @@ class NoFragmentCycles extends AbstractValidationRule
array_pop($this->spreadPath);
} else {
$cyclePath = array_slice($this->spreadPath, $cycleIndex);
- $nodes = $cyclePath;
+ $nodes = $cyclePath;
if (is_array($spreadNode)) {
$nodes = array_merge($nodes, $spreadNode);
@@ -87,9 +96,12 @@ class NoFragmentCycles extends AbstractValidationRule
$context->reportError(new Error(
self::cycleErrorMessage(
$spreadName,
- Utils::map($cyclePath, function ($s) {
- return $s->name->value;
- })
+ Utils::map(
+ $cyclePath,
+ function ($s) {
+ return $s->name->value;
+ }
+ )
),
$nodes
));
@@ -98,4 +110,16 @@ class NoFragmentCycles extends AbstractValidationRule
$this->spreadPathIndexByName[$fragmentName] = null;
}
+
+ /**
+ * @param string[] $spreadNames
+ */
+ public static function cycleErrorMessage($fragName, array $spreadNames = [])
+ {
+ return sprintf(
+ 'Cannot spread fragment "%s" within itself%s.',
+ $fragName,
+ ! empty($spreadNames) ? ' via ' . implode(', ', $spreadNames) : ''
+ );
+ }
}
diff --git a/src/Validator/Rules/NoUndefinedVariables.php b/src/Validator/Rules/NoUndefinedVariables.php
index 42aa3cc..af7bfd4 100644
--- a/src/Validator/Rules/NoUndefinedVariables.php
+++ b/src/Validator/Rules/NoUndefinedVariables.php
@@ -1,4 +1,7 @@
[
- 'enter' => function() use (&$variableNameDefined) {
+ 'enter' => function () use (&$variableNameDefined) {
$variableNameDefined = [];
},
- 'leave' => function(OperationDefinitionNode $operation) use (&$variableNameDefined, $context) {
+ 'leave' => function (OperationDefinitionNode $operation) use (&$variableNameDefined, $context) {
$usages = $context->getRecursiveVariableUsages($operation);
foreach ($usages as $usage) {
- $node = $usage['node'];
+ $node = $usage['node'];
$varName = $node->name->value;
- if (empty($variableNameDefined[$varName])) {
- $context->reportError(new Error(
- self::undefinedVarMessage(
- $varName,
- $operation->name ? $operation->name->value : null
- ),
- [ $node, $operation ]
- ));
+ if (! empty($variableNameDefined[$varName])) {
+ continue;
}
+
+ $context->reportError(new Error(
+ self::undefinedVarMessage(
+ $varName,
+ $operation->name ? $operation->name->value : null
+ ),
+ [$node, $operation]
+ ));
}
- }
+ },
],
- NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $def) use (&$variableNameDefined) {
+ NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $def) use (&$variableNameDefined) {
$variableNameDefined[$def->variable->name->value] = true;
- }
+ },
];
}
+
+ public static function undefinedVarMessage($varName, $opName = null)
+ {
+ return $opName
+ ? sprintf('Variable "$%s" is not defined by operation "%s".', $varName, $opName)
+ : sprintf('Variable "$%s" is not defined.', $varName);
+ }
}
diff --git a/src/Validator/Rules/NoUnusedFragments.php b/src/Validator/Rules/NoUnusedFragments.php
index 2168586..d1cd366 100644
--- a/src/Validator/Rules/NoUnusedFragments.php
+++ b/src/Validator/Rules/NoUnusedFragments.php
@@ -1,39 +1,43 @@
operationDefs = [];
- $this->fragmentDefs = [];
+ $this->fragmentDefs = [];
return [
- NodeKind::OPERATION_DEFINITION => function($node) {
+ NodeKind::OPERATION_DEFINITION => function ($node) {
$this->operationDefs[] = $node;
+
return Visitor::skipNode();
},
- NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $def) {
+ NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $def) {
$this->fragmentDefs[] = $def;
+
return Visitor::skipNode();
},
- NodeKind::DOCUMENT => [
- 'leave' => function() use ($context) {
+ NodeKind::DOCUMENT => [
+ 'leave' => function () use ($context) {
$fragmentNameUsed = [];
foreach ($this->operationDefs as $operation) {
@@ -44,15 +48,22 @@ class NoUnusedFragments extends AbstractValidationRule
foreach ($this->fragmentDefs as $fragmentDef) {
$fragName = $fragmentDef->name->value;
- if (empty($fragmentNameUsed[$fragName])) {
- $context->reportError(new Error(
- self::unusedFragMessage($fragName),
- [ $fragmentDef ]
- ));
+ if (! empty($fragmentNameUsed[$fragName])) {
+ continue;
}
+
+ $context->reportError(new Error(
+ self::unusedFragMessage($fragName),
+ [$fragmentDef]
+ ));
}
- }
- ]
+ },
+ ],
];
}
+
+ public static function unusedFragMessage($fragName)
+ {
+ return sprintf('Fragment "%s" is never used.', $fragName);
+ }
}
diff --git a/src/Validator/Rules/NoUnusedVariables.php b/src/Validator/Rules/NoUnusedVariables.php
index c004623..e8f7ff3 100644
--- a/src/Validator/Rules/NoUnusedVariables.php
+++ b/src/Validator/Rules/NoUnusedVariables.php
@@ -1,20 +1,19 @@
[
- 'enter' => function() {
+ 'enter' => function () {
$this->variableDefs = [];
},
- 'leave' => function(OperationDefinitionNode $operation) use ($context) {
+ 'leave' => function (OperationDefinitionNode $operation) use ($context) {
$variableNameUsed = [];
- $usages = $context->getRecursiveVariableUsages($operation);
- $opName = $operation->name ? $operation->name->value : null;
+ $usages = $context->getRecursiveVariableUsages($operation);
+ $opName = $operation->name ? $operation->name->value : null;
foreach ($usages as $usage) {
- $node = $usage['node'];
+ $node = $usage['node'];
$variableNameUsed[$node->name->value] = true;
}
foreach ($this->variableDefs as $variableDef) {
$variableName = $variableDef->variable->name->value;
- if (empty($variableNameUsed[$variableName])) {
- $context->reportError(new Error(
- self::unusedVariableMessage($variableName, $opName),
- [$variableDef]
- ));
+ if (! empty($variableNameUsed[$variableName])) {
+ continue;
}
+
+ $context->reportError(new Error(
+ self::unusedVariableMessage($variableName, $opName),
+ [$variableDef]
+ ));
}
- }
+ },
],
- NodeKind::VARIABLE_DEFINITION => function($def) {
+ NodeKind::VARIABLE_DEFINITION => function ($def) {
$this->variableDefs[] = $def;
- }
+ },
];
}
+
+ public static function unusedVariableMessage($varName, $opName = null)
+ {
+ return $opName
+ ? sprintf('Variable "$%s" is never used in operation "%s".', $varName, $opName)
+ : sprintf('Variable "$%s" is never used.', $varName);
+ }
}
diff --git a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php
index 7ab2838..9a4c56b 100644
--- a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php
+++ b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php
@@ -1,4 +1,7 @@
comparedFragmentPairs = new PairSet();
+ $this->comparedFragmentPairs = new PairSet();
$this->cachedFieldsAndFragmentNames = new \SplObjectStorage();
return [
- NodeKind::SELECTION_SET => function(SelectionSetNode $selectionSet) use ($context) {
+ NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) {
$conflicts = $this->findConflictsWithinSelectionSet(
$context,
$context->getParentType(),
@@ -74,20 +66,112 @@ class OverlappingFieldsCanBeMerged extends AbstractValidationRule
);
foreach ($conflicts as $conflict) {
- $responseName = $conflict[0][0];
- $reason = $conflict[0][1];
- $fields1 = $conflict[1];
- $fields2 = $conflict[2];
+ [[$responseName, $reason], $fields1, $fields2] = $conflict;
$context->reportError(new Error(
self::fieldsConflictMessage($responseName, $reason),
array_merge($fields1, $fields2)
));
}
- }
+ },
];
}
+ /**
+ * Find all conflicts found "within" a selection set, including those found
+ * via spreading in fragments. Called when visiting each SelectionSet in the
+ * GraphQL Document.
+ *
+ * @param CompositeType $parentType
+ * @return mixed[]
+ */
+ private function findConflictsWithinSelectionSet(
+ ValidationContext $context,
+ $parentType,
+ SelectionSetNode $selectionSet
+ ) {
+ [$fieldMap, $fragmentNames] = $this->getFieldsAndFragmentNames(
+ $context,
+ $parentType,
+ $selectionSet
+ );
+
+ $conflicts = [];
+
+ // (A) Find find all conflicts "within" the fields of this selection set.
+ // Note: this is the *only place* `collectConflictsWithin` is called.
+ $this->collectConflictsWithin(
+ $context,
+ $conflicts,
+ $fieldMap
+ );
+
+ $fragmentNamesLength = count($fragmentNames);
+ if ($fragmentNamesLength !== 0) {
+ // (B) Then collect conflicts between these fields and those represented by
+ // each spread fragment name found.
+ $comparedFragments = [];
+ for ($i = 0; $i < $fragmentNamesLength; $i++) {
+ $this->collectConflictsBetweenFieldsAndFragment(
+ $context,
+ $conflicts,
+ $comparedFragments,
+ false,
+ $fieldMap,
+ $fragmentNames[$i]
+ );
+ // (C) Then compare this fragment with all other fragments found in this
+ // selection set to collect conflicts between fragments spread together.
+ // This compares each item in the list of fragment names to every other item
+ // in that same list (except for itself).
+ for ($j = $i + 1; $j < $fragmentNamesLength; $j++) {
+ $this->collectConflictsBetweenFragments(
+ $context,
+ $conflicts,
+ false,
+ $fragmentNames[$i],
+ $fragmentNames[$j]
+ );
+ }
+ }
+ }
+
+ return $conflicts;
+ }
+
+ /**
+ * Given a selection set, return the collection of fields (a mapping of response
+ * name to field ASTs and definitions) as well as a list of fragment names
+ * referenced via fragment spreads.
+ *
+ * @param CompositeType $parentType
+ * @return mixed[]|\SplObjectStorage
+ */
+ private function getFieldsAndFragmentNames(
+ ValidationContext $context,
+ $parentType,
+ SelectionSetNode $selectionSet
+ ) {
+ if (isset($this->cachedFieldsAndFragmentNames[$selectionSet])) {
+ $cached = $this->cachedFieldsAndFragmentNames[$selectionSet];
+ } else {
+ $astAndDefs = [];
+ $fragmentNames = [];
+
+ $this->internalCollectFieldsAndFragmentNames(
+ $context,
+ $parentType,
+ $selectionSet,
+ $astAndDefs,
+ $fragmentNames
+ );
+ $cached = [$astAndDefs, array_keys($fragmentNames)];
+ $this->cachedFieldsAndFragmentNames[$selectionSet] = $cached;
+ }
+
+ return $cached;
+ }
+
/**
* Algorithm:
*
@@ -144,221 +228,271 @@ class OverlappingFieldsCanBeMerged extends AbstractValidationRule
*/
/**
- * Find all conflicts found "within" a selection set, including those found
- * via spreading in fragments. Called when visiting each SelectionSet in the
- * GraphQL Document.
+ * Given a reference to a fragment, return the represented collection of fields
+ * as well as a list of nested fragment names referenced via fragment spreads.
*
- * @param ValidationContext $context
* @param CompositeType $parentType
- * @param SelectionSetNode $selectionSet
- * @return array
+ * @param mixed[][][] $astAndDefs
+ * @param bool[] $fragmentNames
*/
- private function findConflictsWithinSelectionSet(
+ private function internalCollectFieldsAndFragmentNames(
ValidationContext $context,
$parentType,
- SelectionSetNode $selectionSet)
- {
- list($fieldMap, $fragmentNames) = $this->getFieldsAndFragmentNames(
- $context,
- $parentType,
- $selectionSet
- );
+ SelectionSetNode $selectionSet,
+ array &$astAndDefs,
+ array &$fragmentNames
+ ) {
+ foreach ($selectionSet->selections as $selection) {
+ switch (true) {
+ case $selection instanceof FieldNode:
+ $fieldName = $selection->name->value;
+ $fieldDef = null;
+ if ($parentType instanceof ObjectType ||
+ $parentType instanceof InterfaceType) {
+ $tmp = $parentType->getFields();
+ if (isset($tmp[$fieldName])) {
+ $fieldDef = $tmp[$fieldName];
+ }
+ }
+ $responseName = $selection->alias ? $selection->alias->value : $fieldName;
- $conflicts = [];
+ if (! isset($astAndDefs[$responseName])) {
+ $astAndDefs[$responseName] = [];
+ }
+ $astAndDefs[$responseName][] = [$parentType, $selection, $fieldDef];
+ break;
+ case $selection instanceof FragmentSpreadNode:
+ $fragmentNames[$selection->name->value] = true;
+ break;
+ case $selection instanceof InlineFragmentNode:
+ $typeCondition = $selection->typeCondition;
+ $inlineFragmentType = $typeCondition
+ ? TypeInfo::typeFromAST($context->getSchema(), $typeCondition)
+ : $parentType;
- // (A) Find find all conflicts "within" the fields of this selection set.
- // Note: this is the *only place* `collectConflictsWithin` is called.
- $this->collectConflictsWithin(
- $context,
- $conflicts,
- $fieldMap
- );
-
-
- $fragmentNamesLength = count($fragmentNames);
- if ($fragmentNamesLength !== 0) {
- // (B) Then collect conflicts between these fields and those represented by
- // each spread fragment name found.
- $comparedFragments = [];
- for ($i = 0; $i < $fragmentNamesLength; $i++) {
- $this->collectConflictsBetweenFieldsAndFragment(
- $context,
- $conflicts,
- $comparedFragments,
- false,
- $fieldMap,
- $fragmentNames[$i]
- );
- // (C) Then compare this fragment with all other fragments found in this
- // selection set to collect conflicts between fragments spread together.
- // This compares each item in the list of fragment names to every other item
- // in that same list (except for itself).
- for ($j = $i + 1; $j < $fragmentNamesLength; $j++) {
- $this->collectConflictsBetweenFragments(
+ $this->internalCollectFieldsAndFragmentNames(
$context,
- $conflicts,
- false,
- $fragmentNames[$i],
- $fragmentNames[$j]
+ $inlineFragmentType,
+ $selection->selectionSet,
+ $astAndDefs,
+ $fragmentNames
);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Collect all Conflicts "within" one collection of fields.
+ *
+ * @param mixed[][] $conflicts
+ * @param mixed[][] $fieldMap
+ */
+ private function collectConflictsWithin(
+ ValidationContext $context,
+ array &$conflicts,
+ array $fieldMap
+ ) {
+ // A field map is a keyed collection, where each key represents a response
+ // name and the value at that key is a list of all fields which provide that
+ // response name. For every response name, if there are multiple fields, they
+ // must be compared to find a potential conflict.
+ foreach ($fieldMap as $responseName => $fields) {
+ // This compares every field in the list to every other field in this list
+ // (except to itself). If the list only has one item, nothing needs to
+ // be compared.
+ $fieldsLength = count($fields);
+ if ($fieldsLength <= 1) {
+ continue;
+ }
+
+ for ($i = 0; $i < $fieldsLength; $i++) {
+ for ($j = $i + 1; $j < $fieldsLength; $j++) {
+ $conflict = $this->findConflict(
+ $context,
+ false, // within one collection is never mutually exclusive
+ $responseName,
+ $fields[$i],
+ $fields[$j]
+ );
+ if (! $conflict) {
+ continue;
+ }
+
+ $conflicts[] = $conflict;
}
}
}
-
- return $conflicts;
}
/**
- * Collect all conflicts found between a set of fields and a fragment reference
- * including via spreading in any nested fragments.
+ * Determines if there is a conflict between two particular fields, including
+ * comparing their sub-fields.
*
- * @param ValidationContext $context
- * @param array $conflicts
- * @param array $comparedFragments
- * @param bool $areMutuallyExclusive
- * @param array $fieldMap
- * @param string $fragmentName
+ * @param bool $parentFieldsAreMutuallyExclusive
+ * @param string $responseName
+ * @param mixed[] $field1
+ * @param mixed[] $field2
+ * @return mixed[]|null
*/
- private function collectConflictsBetweenFieldsAndFragment(
+ private function findConflict(
ValidationContext $context,
- array &$conflicts,
- array &$comparedFragments,
- $areMutuallyExclusive,
- array $fieldMap,
- $fragmentName
+ $parentFieldsAreMutuallyExclusive,
+ $responseName,
+ array $field1,
+ array $field2
) {
- if (isset($comparedFragments[$fragmentName])) {
- return;
- }
- $comparedFragments[$fragmentName] = true;
+ [$parentType1, $ast1, $def1] = $field1;
+ [$parentType2, $ast2, $def2] = $field2;
- $fragment = $context->getFragment($fragmentName);
- if (!$fragment) {
- return;
+ // If it is known that two fields could not possibly apply at the same
+ // time, due to the parent types, then it is safe to permit them to diverge
+ // in aliased field or arguments used as they will not present any ambiguity
+ // by differing.
+ // It is known that two parent types could never overlap if they are
+ // different Object types. Interface or Union types might overlap - if not
+ // in the current state of the schema, then perhaps in some future version,
+ // thus may not safely diverge.
+ $areMutuallyExclusive =
+ $parentFieldsAreMutuallyExclusive ||
+ (
+ $parentType1 !== $parentType2 &&
+ $parentType1 instanceof ObjectType &&
+ $parentType2 instanceof ObjectType
+ );
+
+ // The return type for each field.
+ $type1 = $def1 ? $def1->getType() : null;
+ $type2 = $def2 ? $def2->getType() : null;
+
+ if (! $areMutuallyExclusive) {
+ // Two aliases must refer to the same field.
+ $name1 = $ast1->name->value;
+ $name2 = $ast2->name->value;
+ if ($name1 !== $name2) {
+ return [
+ [$responseName, sprintf('%s and %s are different fields', $name1, $name2)],
+ [$ast1],
+ [$ast2],
+ ];
+ }
+
+ if (! $this->sameArguments($ast1->arguments ?: [], $ast2->arguments ?: [])) {
+ return [
+ [$responseName, 'they have differing arguments'],
+ [$ast1],
+ [$ast2],
+ ];
+ }
}
- list($fieldMap2, $fragmentNames2) = $this->getReferencedFieldsAndFragmentNames(
- $context,
- $fragment
- );
-
- if ($fieldMap === $fieldMap2) {
- return;
+ if ($type1 && $type2 && $this->doTypesConflict($type1, $type2)) {
+ return [
+ [$responseName, sprintf('they return conflicting types %s and %s', $type1, $type2)],
+ [$ast1],
+ [$ast2],
+ ];
}
- // (D) First collect any conflicts between the provided collection of fields
- // and the collection of fields represented by the given fragment.
- $this->collectConflictsBetween(
- $context,
- $conflicts,
- $areMutuallyExclusive,
- $fieldMap,
- $fieldMap2
- );
-
- // (E) Then collect any conflicts between the provided collection of fields
- // and any fragment names found in the given fragment.
- $fragmentNames2Length = count($fragmentNames2);
- for ($i = 0; $i < $fragmentNames2Length; $i++) {
- $this->collectConflictsBetweenFieldsAndFragment(
+ // Collect and compare sub-fields. Use the same "visited fragment names" list
+ // for both collections so fields in a fragment reference are never
+ // compared to themselves.
+ $selectionSet1 = $ast1->selectionSet;
+ $selectionSet2 = $ast2->selectionSet;
+ if ($selectionSet1 && $selectionSet2) {
+ $conflicts = $this->findConflictsBetweenSubSelectionSets(
$context,
- $conflicts,
- $comparedFragments,
$areMutuallyExclusive,
- $fieldMap,
- $fragmentNames2[$i]
+ Type::getNamedType($type1),
+ $selectionSet1,
+ Type::getNamedType($type2),
+ $selectionSet2
+ );
+
+ return $this->subfieldConflicts(
+ $conflicts,
+ $responseName,
+ $ast1,
+ $ast2
);
}
+
+ return null;
}
/**
- * Collect all conflicts found between two fragments, including via spreading in
- * any nested fragments.
+ * @param ArgumentNode[] $arguments1
+ * @param ArgumentNode[] $arguments2
*
- * @param ValidationContext $context
- * @param array $conflicts
- * @param bool $areMutuallyExclusive
- * @param string $fragmentName1
- * @param string $fragmentName2
+ * @return bool
*/
- private function collectConflictsBetweenFragments(
- ValidationContext $context,
- array &$conflicts,
- $areMutuallyExclusive,
- $fragmentName1,
- $fragmentName2
- ) {
- // No need to compare a fragment to itself.
- if ($fragmentName1 === $fragmentName2) {
- return;
+ private function sameArguments($arguments1, $arguments2)
+ {
+ if (count($arguments1) !== count($arguments2)) {
+ return false;
+ }
+ foreach ($arguments1 as $argument1) {
+ $argument2 = null;
+ foreach ($arguments2 as $argument) {
+ if ($argument->name->value === $argument1->name->value) {
+ $argument2 = $argument;
+ break;
+ }
+ }
+ if (! $argument2) {
+ return false;
+ }
+
+ if (! $this->sameValue($argument1->value, $argument2->value)) {
+ return false;
+ }
}
- // Memoize so two fragments are not compared for conflicts more than once.
- if (
- $this->comparedFragmentPairs->has(
- $fragmentName1,
- $fragmentName2,
- $areMutuallyExclusive
- )
- ) {
- return;
- }
- $this->comparedFragmentPairs->add(
- $fragmentName1,
- $fragmentName2,
- $areMutuallyExclusive
- );
+ return true;
+ }
- $fragment1 = $context->getFragment($fragmentName1);
- $fragment2 = $context->getFragment($fragmentName2);
- if (!$fragment1 || !$fragment2) {
- return;
+ /**
+ * @return bool
+ */
+ private function sameValue(Node $value1, Node $value2)
+ {
+ return (! $value1 && ! $value2) || (Printer::doPrint($value1) === Printer::doPrint($value2));
+ }
+
+ /**
+ * Two types conflict if both types could not apply to a value simultaneously.
+ * Composite types are ignored as their individual field types will be compared
+ * later recursively. However List and Non-Null types must match.
+ *
+ * @return bool
+ */
+ private function doTypesConflict(OutputType $type1, OutputType $type2)
+ {
+ if ($type1 instanceof ListOfType) {
+ return $type2 instanceof ListOfType ?
+ $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
+ true;
+ }
+ if ($type2 instanceof ListOfType) {
+ return $type1 instanceof ListOfType ?
+ $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
+ true;
+ }
+ if ($type1 instanceof NonNull) {
+ return $type2 instanceof NonNull ?
+ $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
+ true;
+ }
+ if ($type2 instanceof NonNull) {
+ return $type1 instanceof NonNull ?
+ $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
+ true;
+ }
+ if (Type::isLeafType($type1) || Type::isLeafType($type2)) {
+ return $type1 !== $type2;
}
- list($fieldMap1, $fragmentNames1) = $this->getReferencedFieldsAndFragmentNames(
- $context,
- $fragment1
- );
- list($fieldMap2, $fragmentNames2) = $this->getReferencedFieldsAndFragmentNames(
- $context,
- $fragment2
- );
-
- // (F) First, collect all conflicts between these two collections of fields
- // (not including any nested fragments).
- $this->collectConflictsBetween(
- $context,
- $conflicts,
- $areMutuallyExclusive,
- $fieldMap1,
- $fieldMap2
- );
-
- // (G) Then collect conflicts between the first fragment and any nested
- // fragments spread in the second fragment.
- $fragmentNames2Length = count($fragmentNames2);
- for ($j = 0; $j < $fragmentNames2Length; $j++) {
- $this->collectConflictsBetweenFragments(
- $context,
- $conflicts,
- $areMutuallyExclusive,
- $fragmentName1,
- $fragmentNames2[$j]
- );
- }
-
- // (G) Then collect conflicts between the second fragment and any nested
- // fragments spread in the first fragment.
- $fragmentNames1Length = count($fragmentNames1);
- for ($i = 0; $i < $fragmentNames1Length; $i++) {
- $this->collectConflictsBetweenFragments(
- $context,
- $conflicts,
- $areMutuallyExclusive,
- $fragmentNames1[$i],
- $fragmentName2
- );
- }
+ return false;
}
/**
@@ -366,13 +500,10 @@ class OverlappingFieldsCanBeMerged extends AbstractValidationRule
* via spreading in fragments. Called when determining if conflicts exist
* between the sub-fields of two overlapping fields.
*
- * @param ValidationContext $context
- * @param bool $areMutuallyExclusive
+ * @param bool $areMutuallyExclusive
* @param CompositeType $parentType1
- * @param $selectionSet1
* @param CompositeType $parentType2
- * @param $selectionSet2
- * @return array
+ * @return mixed[][]
*/
private function findConflictsBetweenSubSelectionSets(
ValidationContext $context,
@@ -384,12 +515,12 @@ class OverlappingFieldsCanBeMerged extends AbstractValidationRule
) {
$conflicts = [];
- list($fieldMap1, $fragmentNames1) = $this->getFieldsAndFragmentNames(
+ [$fieldMap1, $fragmentNames1] = $this->getFieldsAndFragmentNames(
$context,
$parentType1,
$selectionSet1
);
- list($fieldMap2, $fragmentNames2) = $this->getFieldsAndFragmentNames(
+ [$fieldMap2, $fragmentNames2] = $this->getFieldsAndFragmentNames(
$context,
$parentType2,
$selectionSet2
@@ -452,48 +583,8 @@ class OverlappingFieldsCanBeMerged extends AbstractValidationRule
);
}
}
- return $conflicts;
- }
- /**
- * Collect all Conflicts "within" one collection of fields.
- *
- * @param ValidationContext $context
- * @param array $conflicts
- * @param array $fieldMap
- */
- private function collectConflictsWithin(
- ValidationContext $context,
- array &$conflicts,
- array $fieldMap
- )
- {
- // A field map is a keyed collection, where each key represents a response
- // name and the value at that key is a list of all fields which provide that
- // response name. For every response name, if there are multiple fields, they
- // must be compared to find a potential conflict.
- foreach ($fieldMap as $responseName => $fields) {
- // This compares every field in the list to every other field in this list
- // (except to itself). If the list only has one item, nothing needs to
- // be compared.
- $fieldsLength = count($fields);
- if ($fieldsLength > 1) {
- for ($i = 0; $i < $fieldsLength; $i++) {
- for ($j = $i + 1; $j < $fieldsLength; $j++) {
- $conflict = $this->findConflict(
- $context,
- false, // within one collection is never mutually exclusive
- $responseName,
- $fields[$i],
- $fields[$j]
- );
- if ($conflict) {
- $conflicts[] = $conflict;
- }
- }
- }
- }
- }
+ return $conflicts;
}
/**
@@ -503,11 +594,10 @@ class OverlappingFieldsCanBeMerged extends AbstractValidationRule
* provided collection of fields. This is true because this validator traverses
* each individual selection set.
*
- * @param ValidationContext $context
- * @param array $conflicts
- * @param bool $parentFieldsAreMutuallyExclusive
- * @param array $fieldMap1
- * @param array $fieldMap2
+ * @param mixed[][] $conflicts
+ * @param bool $parentFieldsAreMutuallyExclusive
+ * @param mixed[] $fieldMap1
+ * @param mixed[] $fieldMap2
*/
private function collectConflictsBetween(
ValidationContext $context,
@@ -522,253 +612,111 @@ class OverlappingFieldsCanBeMerged extends AbstractValidationRule
// maps, each field from the first field map must be compared to every field
// in the second field map to find potential conflicts.
foreach ($fieldMap1 as $responseName => $fields1) {
- if (isset($fieldMap2[$responseName])) {
- $fields2 = $fieldMap2[$responseName];
- $fields1Length = count($fields1);
- $fields2Length = count($fields2);
- for ($i = 0; $i < $fields1Length; $i++) {
- for ($j = 0; $j < $fields2Length; $j++) {
- $conflict = $this->findConflict(
- $context,
- $parentFieldsAreMutuallyExclusive,
- $responseName,
- $fields1[$i],
- $fields2[$j]
- );
- if ($conflict) {
- $conflicts[] = $conflict;
- }
+ if (! isset($fieldMap2[$responseName])) {
+ continue;
+ }
+
+ $fields2 = $fieldMap2[$responseName];
+ $fields1Length = count($fields1);
+ $fields2Length = count($fields2);
+ for ($i = 0; $i < $fields1Length; $i++) {
+ for ($j = 0; $j < $fields2Length; $j++) {
+ $conflict = $this->findConflict(
+ $context,
+ $parentFieldsAreMutuallyExclusive,
+ $responseName,
+ $fields1[$i],
+ $fields2[$j]
+ );
+ if (! $conflict) {
+ continue;
}
+
+ $conflicts[] = $conflict;
}
}
}
}
/**
- * Determines if there is a conflict between two particular fields, including
- * comparing their sub-fields.
+ * Collect all conflicts found between a set of fields and a fragment reference
+ * including via spreading in any nested fragments.
*
- * @param ValidationContext $context
- * @param bool $parentFieldsAreMutuallyExclusive
- * @param string $responseName
- * @param array $field1
- * @param array $field2
- * @return array|null
+ * @param mixed[][] $conflicts
+ * @param bool[] $comparedFragments
+ * @param bool $areMutuallyExclusive
+ * @param mixed[][] $fieldMap
+ * @param string $fragmentName
*/
- private function findConflict(
+ private function collectConflictsBetweenFieldsAndFragment(
ValidationContext $context,
- $parentFieldsAreMutuallyExclusive,
- $responseName,
- array $field1,
- array $field2
- )
- {
- list($parentType1, $ast1, $def1) = $field1;
- list($parentType2, $ast2, $def2) = $field2;
-
- // If it is known that two fields could not possibly apply at the same
- // time, due to the parent types, then it is safe to permit them to diverge
- // in aliased field or arguments used as they will not present any ambiguity
- // by differing.
- // It is known that two parent types could never overlap if they are
- // different Object types. Interface or Union types might overlap - if not
- // in the current state of the schema, then perhaps in some future version,
- // thus may not safely diverge.
- $areMutuallyExclusive =
- $parentFieldsAreMutuallyExclusive ||
- $parentType1 !== $parentType2 &&
- $parentType1 instanceof ObjectType &&
- $parentType2 instanceof ObjectType;
-
- // The return type for each field.
- $type1 = $def1 ? $def1->getType() : null;
- $type2 = $def2 ? $def2->getType() : null;
-
- if (!$areMutuallyExclusive) {
- // Two aliases must refer to the same field.
- $name1 = $ast1->name->value;
- $name2 = $ast2->name->value;
- if ($name1 !== $name2) {
- return [
- [$responseName, "$name1 and $name2 are different fields"],
- [$ast1],
- [$ast2]
- ];
- }
-
- if (!$this->sameArguments($ast1->arguments ?: [], $ast2->arguments ?: [])) {
- return [
- [$responseName, 'they have differing arguments'],
- [$ast1],
- [$ast2]
- ];
- }
- }
-
- if ($type1 && $type2 && $this->doTypesConflict($type1, $type2)) {
- return [
- [$responseName, "they return conflicting types $type1 and $type2"],
- [$ast1],
- [$ast2]
- ];
- }
-
- // Collect and compare sub-fields. Use the same "visited fragment names" list
- // for both collections so fields in a fragment reference are never
- // compared to themselves.
- $selectionSet1 = $ast1->selectionSet;
- $selectionSet2 = $ast2->selectionSet;
- if ($selectionSet1 && $selectionSet2) {
- $conflicts = $this->findConflictsBetweenSubSelectionSets(
- $context,
- $areMutuallyExclusive,
- Type::getNamedType($type1),
- $selectionSet1,
- Type::getNamedType($type2),
- $selectionSet2
- );
- return $this->subfieldConflicts(
- $conflicts,
- $responseName,
- $ast1,
- $ast2
- );
- }
-
- return null;
- }
-
- /**
- * @param ArgumentNode[] $arguments1
- * @param ArgumentNode[] $arguments2
- *
- * @return bool
- */
- private function sameArguments($arguments1, $arguments2)
- {
- if (count($arguments1) !== count($arguments2)) {
- return false;
- }
- foreach ($arguments1 as $argument1) {
- $argument2 = null;
- foreach ($arguments2 as $argument) {
- if ($argument->name->value === $argument1->name->value) {
- $argument2 = $argument;
- break;
- }
- }
- if (!$argument2) {
- return false;
- }
-
- if (!$this->sameValue($argument1->value, $argument2->value)) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * @param Node $value1
- * @param Node $value2
- * @return bool
- */
- private function sameValue(Node $value1, Node $value2)
- {
- return (!$value1 && !$value2) || (Printer::doPrint($value1) === Printer::doPrint($value2));
- }
-
- /**
- * Two types conflict if both types could not apply to a value simultaneously.
- * Composite types are ignored as their individual field types will be compared
- * later recursively. However List and Non-Null types must match.
- *
- * @param OutputType $type1
- * @param OutputType $type2
- * @return bool
- */
- private function doTypesConflict(OutputType $type1, OutputType $type2)
- {
- if ($type1 instanceof ListOfType) {
- return $type2 instanceof ListOfType ?
- $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
- true;
- }
- if ($type2 instanceof ListOfType) {
- return $type1 instanceof ListOfType ?
- $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
- true;
- }
- if ($type1 instanceof NonNull) {
- return $type2 instanceof NonNull ?
- $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
- true;
- }
- if ($type2 instanceof NonNull) {
- return $type1 instanceof NonNull ?
- $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
- true;
- }
- if (Type::isLeafType($type1) || Type::isLeafType($type2)) {
- return $type1 !== $type2;
- }
- return false;
- }
-
- /**
- * Given a selection set, return the collection of fields (a mapping of response
- * name to field ASTs and definitions) as well as a list of fragment names
- * referenced via fragment spreads.
- *
- * @param ValidationContext $context
- * @param CompositeType $parentType
- * @param SelectionSetNode $selectionSet
- * @return array
- */
- private function getFieldsAndFragmentNames(
- ValidationContext $context,
- $parentType,
- SelectionSetNode $selectionSet
+ array &$conflicts,
+ array &$comparedFragments,
+ $areMutuallyExclusive,
+ array $fieldMap,
+ $fragmentName
) {
- if (!isset($this->cachedFieldsAndFragmentNames[$selectionSet])) {
- $astAndDefs = [];
- $fragmentNames = [];
-
- $this->internalCollectFieldsAndFragmentNames(
- $context,
- $parentType,
- $selectionSet,
- $astAndDefs,
- $fragmentNames
- );
- $cached = [$astAndDefs, array_keys($fragmentNames)];
- $this->cachedFieldsAndFragmentNames[$selectionSet] = $cached;
- } else {
- $cached = $this->cachedFieldsAndFragmentNames[$selectionSet];
+ if (isset($comparedFragments[$fragmentName])) {
+ return;
+ }
+ $comparedFragments[$fragmentName] = true;
+
+ $fragment = $context->getFragment($fragmentName);
+ if (! $fragment) {
+ return;
+ }
+
+ [$fieldMap2, $fragmentNames2] = $this->getReferencedFieldsAndFragmentNames(
+ $context,
+ $fragment
+ );
+
+ if ($fieldMap === $fieldMap2) {
+ return;
+ }
+
+ // (D) First collect any conflicts between the provided collection of fields
+ // and the collection of fields represented by the given fragment.
+ $this->collectConflictsBetween(
+ $context,
+ $conflicts,
+ $areMutuallyExclusive,
+ $fieldMap,
+ $fieldMap2
+ );
+
+ // (E) Then collect any conflicts between the provided collection of fields
+ // and any fragment names found in the given fragment.
+ $fragmentNames2Length = count($fragmentNames2);
+ for ($i = 0; $i < $fragmentNames2Length; $i++) {
+ $this->collectConflictsBetweenFieldsAndFragment(
+ $context,
+ $conflicts,
+ $comparedFragments,
+ $areMutuallyExclusive,
+ $fieldMap,
+ $fragmentNames2[$i]
+ );
}
- return $cached;
}
/**
* Given a reference to a fragment, return the represented collection of fields
* as well as a list of nested fragment names referenced via fragment spreads.
*
- * @param ValidationContext $context
- * @param FragmentDefinitionNode $fragment
- * @return array|object
+ * @return mixed[]|\SplObjectStorage
*/
private function getReferencedFieldsAndFragmentNames(
ValidationContext $context,
FragmentDefinitionNode $fragment
- )
- {
+ ) {
// Short-circuit building a type from the AST if possible.
if (isset($this->cachedFieldsAndFragmentNames[$fragment->selectionSet])) {
return $this->cachedFieldsAndFragmentNames[$fragment->selectionSet];
}
$fragmentType = TypeInfo::typeFromAST($context->getSchema(), $fragment->typeCondition);
+
return $this->getFieldsAndFragmentNames(
$context,
$fragmentType,
@@ -777,63 +725,90 @@ class OverlappingFieldsCanBeMerged extends AbstractValidationRule
}
/**
- * Given a reference to a fragment, return the represented collection of fields
- * as well as a list of nested fragment names referenced via fragment spreads.
+ * Collect all conflicts found between two fragments, including via spreading in
+ * any nested fragments.
*
- * @param ValidationContext $context
- * @param CompositeType $parentType
- * @param SelectionSetNode $selectionSet
- * @param array $astAndDefs
- * @param array $fragmentNames
+ * @param mixed[][] $conflicts
+ * @param bool $areMutuallyExclusive
+ * @param string $fragmentName1
+ * @param string $fragmentName2
*/
- private function internalCollectFieldsAndFragmentNames(
+ private function collectConflictsBetweenFragments(
ValidationContext $context,
- $parentType,
- SelectionSetNode $selectionSet,
- array &$astAndDefs,
- array &$fragmentNames
- )
- {
- $selectionSetLength = count($selectionSet->selections);
- for ($i = 0; $i < $selectionSetLength; $i++) {
- $selection = $selectionSet->selections[$i];
+ array &$conflicts,
+ $areMutuallyExclusive,
+ $fragmentName1,
+ $fragmentName2
+ ) {
+ // No need to compare a fragment to itself.
+ if ($fragmentName1 === $fragmentName2) {
+ return;
+ }
- switch (true) {
- case $selection instanceof FieldNode:
- $fieldName = $selection->name->value;
- $fieldDef = null;
- if ($parentType instanceof ObjectType ||
- $parentType instanceof InterfaceType) {
- $tmp = $parentType->getFields();
- if (isset($tmp[$fieldName])) {
- $fieldDef = $tmp[$fieldName];
- }
- }
- $responseName = $selection->alias ? $selection->alias->value : $fieldName;
+ // Memoize so two fragments are not compared for conflicts more than once.
+ if ($this->comparedFragmentPairs->has(
+ $fragmentName1,
+ $fragmentName2,
+ $areMutuallyExclusive
+ )
+ ) {
+ return;
+ }
+ $this->comparedFragmentPairs->add(
+ $fragmentName1,
+ $fragmentName2,
+ $areMutuallyExclusive
+ );
- if (!isset($astAndDefs[$responseName])) {
- $astAndDefs[$responseName] = [];
- }
- $astAndDefs[$responseName][] = [$parentType, $selection, $fieldDef];
- break;
- case $selection instanceof FragmentSpreadNode:
- $fragmentNames[$selection->name->value] = true;
- break;
- case $selection instanceof InlineFragmentNode:
- $typeCondition = $selection->typeCondition;
- $inlineFragmentType = $typeCondition
- ? TypeInfo::typeFromAST($context->getSchema(), $typeCondition)
- : $parentType;
+ $fragment1 = $context->getFragment($fragmentName1);
+ $fragment2 = $context->getFragment($fragmentName2);
+ if (! $fragment1 || ! $fragment2) {
+ return;
+ }
- $this->internalcollectFieldsAndFragmentNames(
- $context,
- $inlineFragmentType,
- $selection->selectionSet,
- $astAndDefs,
- $fragmentNames
- );
- break;
- }
+ [$fieldMap1, $fragmentNames1] = $this->getReferencedFieldsAndFragmentNames(
+ $context,
+ $fragment1
+ );
+ [$fieldMap2, $fragmentNames2] = $this->getReferencedFieldsAndFragmentNames(
+ $context,
+ $fragment2
+ );
+
+ // (F) First, collect all conflicts between these two collections of fields
+ // (not including any nested fragments).
+ $this->collectConflictsBetween(
+ $context,
+ $conflicts,
+ $areMutuallyExclusive,
+ $fieldMap1,
+ $fieldMap2
+ );
+
+ // (G) Then collect conflicts between the first fragment and any nested
+ // fragments spread in the second fragment.
+ $fragmentNames2Length = count($fragmentNames2);
+ for ($j = 0; $j < $fragmentNames2Length; $j++) {
+ $this->collectConflictsBetweenFragments(
+ $context,
+ $conflicts,
+ $areMutuallyExclusive,
+ $fragmentName1,
+ $fragmentNames2[$j]
+ );
+ }
+
+ // (G) Then collect conflicts between the second fragment and any nested
+ // fragments spread in the first fragment.
+ $fragmentNames1Length = count($fragmentNames1);
+ for ($i = 0; $i < $fragmentNames1Length; $i++) {
+ $this->collectConflictsBetweenFragments(
+ $context,
+ $conflicts,
+ $areMutuallyExclusive,
+ $fragmentNames1[$i],
+ $fragmentName2
+ );
}
}
@@ -841,42 +816,79 @@ class OverlappingFieldsCanBeMerged extends AbstractValidationRule
* Given a series of Conflicts which occurred between two sub-fields, generate
* a single Conflict.
*
- * @param array $conflicts
- * @param string $responseName
- * @param FieldNode $ast1
- * @param FieldNode $ast2
- * @return array|null
+ * @param mixed[][] $conflicts
+ * @param string $responseName
+ * @return mixed[]|null
*/
private function subfieldConflicts(
array $conflicts,
$responseName,
FieldNode $ast1,
FieldNode $ast2
- )
- {
- if (count($conflicts) > 0) {
- return [
- [
- $responseName,
- array_map(function ($conflict) {
- return $conflict[0];
- }, $conflicts),
- ],
- array_reduce(
- $conflicts,
- function ($allFields, $conflict) {
- return array_merge($allFields, $conflict[1]);
- },
- [$ast1]
- ),
- array_reduce(
- $conflicts,
- function ($allFields, $conflict) {
- return array_merge($allFields, $conflict[2]);
- },
- [$ast2]
- ),
- ];
+ ) {
+ if (count($conflicts) === 0) {
+ return null;
}
+
+ return [
+ [
+ $responseName,
+ array_map(
+ function ($conflict) {
+ return $conflict[0];
+ },
+ $conflicts
+ ),
+ ],
+ array_reduce(
+ $conflicts,
+ function ($allFields, $conflict) {
+ return array_merge($allFields, $conflict[1]);
+ },
+ [$ast1]
+ ),
+ array_reduce(
+ $conflicts,
+ function ($allFields, $conflict) {
+ return array_merge($allFields, $conflict[2]);
+ },
+ [$ast2]
+ ),
+ ];
+ }
+
+ /**
+ * @param string $responseName
+ * @param string $reason
+ */
+ public static function fieldsConflictMessage($responseName, $reason)
+ {
+ $reasonMessage = self::reasonMessage($reason);
+
+ return sprintf(
+ 'Fields "%s" conflict because %s. Use different aliases on the fields to fetch both if this was intentional.',
+ $responseName,
+ $reasonMessage
+ );
+ }
+
+ public static function reasonMessage($reason)
+ {
+ if (is_array($reason)) {
+ $tmp = array_map(
+ function ($tmp) {
+ [$responseName, $subReason] = $tmp;
+
+ $reasonMessage = self::reasonMessage($subReason);
+
+ return sprintf('subfields "%s" conflict because %s', $responseName, $reasonMessage);
+ },
+ $reason
+ );
+
+ return implode(' and ', $tmp);
+ }
+
+ return $reason;
}
}
diff --git a/src/Validator/Rules/PossibleFragmentSpreads.php b/src/Validator/Rules/PossibleFragmentSpreads.php
index 0647e6f..26611e6 100644
--- a/src/Validator/Rules/PossibleFragmentSpreads.php
+++ b/src/Validator/Rules/PossibleFragmentSpreads.php
@@ -1,72 +1,61 @@
function(InlineFragmentNode $node) use ($context) {
- $fragType = $context->getType();
+ NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) use ($context) {
+ $fragType = $context->getType();
$parentType = $context->getParentType();
- if ($fragType instanceof CompositeType &&
- $parentType instanceof CompositeType &&
- !$this->doTypesOverlap($context->getSchema(), $fragType, $parentType)) {
- $context->reportError(new Error(
- self::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
- [$node]
- ));
+ if (! ($fragType instanceof CompositeType) ||
+ ! ($parentType instanceof CompositeType) ||
+ $this->doTypesOverlap($context->getSchema(), $fragType, $parentType)) {
+ return;
}
+
+ $context->reportError(new Error(
+ self::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
+ [$node]
+ ));
},
- NodeKind::FRAGMENT_SPREAD => function(FragmentSpreadNode $node) use ($context) {
- $fragName = $node->name->value;
- $fragType = $this->getFragmentType($context, $fragName);
+ NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) use ($context) {
+ $fragName = $node->name->value;
+ $fragType = $this->getFragmentType($context, $fragName);
$parentType = $context->getParentType();
- if ($fragType && $parentType && !$this->doTypesOverlap($context->getSchema(), $fragType, $parentType)) {
- $context->reportError(new Error(
- self::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
- [$node]
- ));
+ if (! $fragType ||
+ ! $parentType ||
+ $this->doTypesOverlap($context->getSchema(), $fragType, $parentType)
+ ) {
+ return;
}
- }
- ];
- }
- private function getFragmentType(ValidationContext $context, $name)
- {
- $frag = $context->getFragment($name);
- if ($frag) {
- $type = TypeInfo::typeFromAST($context->getSchema(), $frag->typeCondition);
- if ($type instanceof CompositeType) {
- return $type;
- }
- }
- return null;
+ $context->reportError(new Error(
+ self::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
+ [$node]
+ ));
+ },
+ ];
}
private function doTypesOverlap(Schema $schema, CompositeType $fragType, CompositeType $parentType)
@@ -136,4 +125,36 @@ class PossibleFragmentSpreads extends AbstractValidationRule
return false;
}
+
+ public static function typeIncompatibleAnonSpreadMessage($parentType, $fragType)
+ {
+ return sprintf(
+ 'Fragment cannot be spread here as objects of type "%s" can never be of type "%s".',
+ $parentType,
+ $fragType
+ );
+ }
+
+ private function getFragmentType(ValidationContext $context, $name)
+ {
+ $frag = $context->getFragment($name);
+ if ($frag) {
+ $type = TypeInfo::typeFromAST($context->getSchema(), $frag->typeCondition);
+ if ($type instanceof CompositeType) {
+ return $type;
+ }
+ }
+
+ return null;
+ }
+
+ public static function typeIncompatibleSpreadMessage($fragName, $parentType, $fragType)
+ {
+ return sprintf(
+ 'Fragment "%s" cannot be spread here as objects of type "%s" can never be of type "%s".',
+ $fragName,
+ $parentType,
+ $fragType
+ );
+ }
}
diff --git a/src/Validator/Rules/ProvidedNonNullArguments.php b/src/Validator/Rules/ProvidedNonNullArguments.php
index 4d0c63c..56254af 100644
--- a/src/Validator/Rules/ProvidedNonNullArguments.php
+++ b/src/Validator/Rules/ProvidedNonNullArguments.php
@@ -1,4 +1,7 @@
[
- 'leave' => function(FieldNode $fieldNode) use ($context) {
+ NodeKind::FIELD => [
+ 'leave' => function (FieldNode $fieldNode) use ($context) {
$fieldDef = $context->getFieldDef();
- if (!$fieldDef) {
+ if (! $fieldDef) {
return Visitor::skipNode();
}
$argNodes = $fieldNode->arguments ?: [];
@@ -38,39 +32,67 @@ class ProvidedNonNullArguments extends AbstractValidationRule
$argNodeMap[$argNode->name->value] = $argNodes;
}
foreach ($fieldDef->args as $argDef) {
- $argNode = isset($argNodeMap[$argDef->name]) ? $argNodeMap[$argDef->name] : null;
- if (!$argNode && $argDef->getType() instanceof NonNull) {
- $context->reportError(new Error(
- self::missingFieldArgMessage($fieldNode->name->value, $argDef->name, $argDef->getType()),
- [$fieldNode]
- ));
+ $argNode = $argNodeMap[$argDef->name] ?? null;
+ if ($argNode || ! ($argDef->getType() instanceof NonNull)) {
+ continue;
}
+
+ $context->reportError(new Error(
+ self::missingFieldArgMessage($fieldNode->name->value, $argDef->name, $argDef->getType()),
+ [$fieldNode]
+ ));
}
- }
+ },
],
NodeKind::DIRECTIVE => [
- 'leave' => function(DirectiveNode $directiveNode) use ($context) {
+ 'leave' => function (DirectiveNode $directiveNode) use ($context) {
$directiveDef = $context->getDirective();
- if (!$directiveDef) {
+ if (! $directiveDef) {
return Visitor::skipNode();
}
- $argNodes = $directiveNode->arguments ?: [];
+ $argNodes = $directiveNode->arguments ?: [];
$argNodeMap = [];
foreach ($argNodes as $argNode) {
$argNodeMap[$argNode->name->value] = $argNodes;
}
foreach ($directiveDef->args as $argDef) {
- $argNode = isset($argNodeMap[$argDef->name]) ? $argNodeMap[$argDef->name] : null;
- if (!$argNode && $argDef->getType() instanceof NonNull) {
- $context->reportError(new Error(
- self::missingDirectiveArgMessage($directiveNode->name->value, $argDef->name, $argDef->getType()),
- [$directiveNode]
- ));
+ $argNode = $argNodeMap[$argDef->name] ?? null;
+ if ($argNode || ! ($argDef->getType() instanceof NonNull)) {
+ continue;
}
+
+ $context->reportError(new Error(
+ self::missingDirectiveArgMessage(
+ $directiveNode->name->value,
+ $argDef->name,
+ $argDef->getType()
+ ),
+ [$directiveNode]
+ ));
}
- }
- ]
+ },
+ ],
];
}
+
+ public static function missingFieldArgMessage($fieldName, $argName, $type)
+ {
+ return sprintf(
+ 'Field "%s" argument "%s" of type "%s" is required but not provided.',
+ $fieldName,
+ $argName,
+ $type
+ );
+ }
+
+ public static function missingDirectiveArgMessage($directiveName, $argName, $type)
+ {
+ return sprintf(
+ 'Directive "@%s" argument "%s" of type "%s" is required but not provided.',
+ $directiveName,
+ $argName,
+ $type
+ );
+ }
}
diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php
index 638010d..f126238 100644
--- a/src/Validator/Rules/QueryComplexity.php
+++ b/src/Validator/Rules/QueryComplexity.php
@@ -1,4 +1,7 @@
setMaxQueryComplexity($maxQueryComplexity);
}
- public static function maxQueryComplexityErrorMessage($max, $count)
- {
- return sprintf('Max query complexity should be %d but got %d.', $max, $count);
- }
-
- /**
- * Set max query complexity. If equal to 0 no check is done. Must be greater or equal to 0.
- *
- * @param $maxQueryComplexity
- */
- public function setMaxQueryComplexity($maxQueryComplexity)
- {
- $this->checkIfGreaterOrEqualToZero('maxQueryComplexity', $maxQueryComplexity);
-
- $this->maxQueryComplexity = (int) $maxQueryComplexity;
- }
-
- public function getMaxQueryComplexity()
- {
- return $this->maxQueryComplexity;
- }
-
- public function setRawVariableValues(array $rawVariableValues = null)
- {
- $this->rawVariableValues = $rawVariableValues ?: [];
- }
-
- public function getRawVariableValues()
- {
- return $this->rawVariableValues;
- }
-
public function getVisitor(ValidationContext $context)
{
$this->context = $context;
- $this->variableDefs = new \ArrayObject();
+ $this->variableDefs = new \ArrayObject();
$this->fieldNodeAndDefs = new \ArrayObject();
- $complexity = 0;
+ $complexity = 0;
return $this->invokeIfNeeded(
$context,
[
- NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) {
+ NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) {
$this->fieldNodeAndDefs = $this->collectFieldASTsAndDefs(
$context,
$context->getParentType(),
@@ -87,23 +65,31 @@ class QueryComplexity extends AbstractQuerySecurity
$this->fieldNodeAndDefs
);
},
- NodeKind::VARIABLE_DEFINITION => function ($def) {
+ NodeKind::VARIABLE_DEFINITION => function ($def) {
$this->variableDefs[] = $def;
+
return Visitor::skipNode();
},
NodeKind::OPERATION_DEFINITION => [
'leave' => function (OperationDefinitionNode $operationDefinition) use ($context, &$complexity) {
$errors = $context->getErrors();
- if (empty($errors)) {
- $complexity = $this->fieldComplexity($operationDefinition, $complexity);
-
- if ($complexity > $this->getMaxQueryComplexity()) {
- $context->reportError(
- new Error($this->maxQueryComplexityErrorMessage($this->getMaxQueryComplexity(), $complexity))
- );
- }
+ if (! empty($errors)) {
+ return;
}
+
+ $complexity = $this->fieldComplexity($operationDefinition, $complexity);
+
+ if ($complexity <= $this->getMaxQueryComplexity()) {
+ return;
+ }
+
+ $context->reportError(
+ new Error($this->maxQueryComplexityErrorMessage(
+ $this->getMaxQueryComplexity(),
+ $complexity
+ ))
+ );
},
],
]
@@ -125,9 +111,9 @@ class QueryComplexity extends AbstractQuerySecurity
{
switch ($node->kind) {
case NodeKind::FIELD:
- /* @var FieldNode $node */
+ /** @var FieldNode $node */
// default values
- $args = [];
+ $args = [];
$complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN;
// calculate children complexity if needed
@@ -139,7 +125,7 @@ class QueryComplexity extends AbstractQuerySecurity
}
$astFieldInfo = $this->astFieldInfo($node);
- $fieldDef = $astFieldInfo[1];
+ $fieldDef = $astFieldInfo[1];
if ($fieldDef instanceof FieldDefinition) {
if ($this->directiveExcludesField($node)) {
@@ -157,7 +143,7 @@ class QueryComplexity extends AbstractQuerySecurity
break;
case NodeKind::INLINE_FRAGMENT:
- /* @var InlineFragmentNode $node */
+ /** @var InlineFragmentNode $node */
// node has children?
if (isset($node->selectionSet)) {
$complexity = $this->fieldComplexity($node, $complexity);
@@ -165,10 +151,10 @@ class QueryComplexity extends AbstractQuerySecurity
break;
case NodeKind::FRAGMENT_SPREAD:
- /* @var FragmentSpreadNode $node */
+ /** @var FragmentSpreadNode $node */
$fragment = $this->getFragment($node);
- if (null !== $fragment) {
+ if ($fragment !== null) {
$complexity = $this->fieldComplexity($fragment, $complexity);
}
break;
@@ -179,11 +165,11 @@ class QueryComplexity extends AbstractQuerySecurity
private function astFieldInfo(FieldNode $field)
{
- $fieldName = $this->getFieldName($field);
+ $fieldName = $this->getFieldName($field);
$astFieldInfo = [null, null];
if (isset($this->fieldNodeAndDefs[$fieldName])) {
foreach ($this->fieldNodeAndDefs[$fieldName] as $astAndDef) {
- if ($astAndDef[0] == $field) {
+ if ($astAndDef[0] === $field) {
$astFieldInfo = $astAndDef;
break;
}
@@ -193,37 +179,8 @@ class QueryComplexity extends AbstractQuerySecurity
return $astFieldInfo;
}
- private function buildFieldArguments(FieldNode $node)
+ private function directiveExcludesField(FieldNode $node)
{
- $rawVariableValues = $this->getRawVariableValues();
- $astFieldInfo = $this->astFieldInfo($node);
- $fieldDef = $astFieldInfo[1];
-
- $args = [];
-
- if ($fieldDef instanceof FieldDefinition) {
- $variableValuesResult = Values::getVariableValues(
- $this->context->getSchema(),
- $this->variableDefs,
- $rawVariableValues
- );
-
- if ($variableValuesResult['errors']) {
- throw new Error(implode("\n\n", array_map(
- function ($error) {
- return $error->getMessage();
- }
- , $variableValuesResult['errors'])));
- }
- $variableValues = $variableValuesResult['coerced'];
-
- $args = Values::getArgumentValues($fieldDef, $node, $variableValues);
- }
-
- return $args;
- }
-
- private function directiveExcludesField(FieldNode $node) {
foreach ($node->directives as $directiveNode) {
if ($directiveNode->name->value === 'deprecated') {
return false;
@@ -236,28 +193,99 @@ class QueryComplexity extends AbstractQuerySecurity
);
if ($variableValuesResult['errors']) {
- throw new Error(implode("\n\n", array_map(
- function ($error) {
- return $error->getMessage();
- }
- , $variableValuesResult['errors'])));
+ throw new Error(implode(
+ "\n\n",
+ array_map(
+ function ($error) {
+ return $error->getMessage();
+ },
+ $variableValuesResult['errors']
+ )
+ ));
}
$variableValues = $variableValuesResult['coerced'];
if ($directiveNode->name->value === 'include') {
- $directive = Directive::includeDirective();
+ $directive = Directive::includeDirective();
$directiveArgs = Values::getArgumentValues($directive, $directiveNode, $variableValues);
- return !$directiveArgs['if'];
- } else {
- $directive = Directive::skipDirective();
- $directiveArgs = Values::getArgumentValues($directive, $directiveNode, $variableValues);
-
- return $directiveArgs['if'];
+ return ! $directiveArgs['if'];
}
+
+ $directive = Directive::skipDirective();
+ $directiveArgs = Values::getArgumentValues($directive, $directiveNode, $variableValues);
+
+ return $directiveArgs['if'];
}
}
+ public function getRawVariableValues()
+ {
+ return $this->rawVariableValues;
+ }
+
+ /**
+ * @param mixed[]|null $rawVariableValues
+ */
+ public function setRawVariableValues(?array $rawVariableValues = null)
+ {
+ $this->rawVariableValues = $rawVariableValues ?: [];
+ }
+
+ private function buildFieldArguments(FieldNode $node)
+ {
+ $rawVariableValues = $this->getRawVariableValues();
+ $astFieldInfo = $this->astFieldInfo($node);
+ $fieldDef = $astFieldInfo[1];
+
+ $args = [];
+
+ if ($fieldDef instanceof FieldDefinition) {
+ $variableValuesResult = Values::getVariableValues(
+ $this->context->getSchema(),
+ $this->variableDefs,
+ $rawVariableValues
+ );
+
+ if ($variableValuesResult['errors']) {
+ throw new Error(implode(
+ "\n\n",
+ array_map(
+ function ($error) {
+ return $error->getMessage();
+ },
+ $variableValuesResult['errors']
+ )
+ ));
+ }
+ $variableValues = $variableValuesResult['coerced'];
+
+ $args = Values::getArgumentValues($fieldDef, $node, $variableValues);
+ }
+
+ return $args;
+ }
+
+ public function getMaxQueryComplexity()
+ {
+ return $this->maxQueryComplexity;
+ }
+
+ /**
+ * Set max query complexity. If equal to 0 no check is done. Must be greater or equal to 0.
+ */
+ public function setMaxQueryComplexity($maxQueryComplexity)
+ {
+ $this->checkIfGreaterOrEqualToZero('maxQueryComplexity', $maxQueryComplexity);
+
+ $this->maxQueryComplexity = (int) $maxQueryComplexity;
+ }
+
+ public static function maxQueryComplexityErrorMessage($max, $count)
+ {
+ return sprintf('Max query complexity should be %d but got %d.', $max, $count);
+ }
+
protected function isEnabled()
{
return $this->getMaxQueryComplexity() !== static::DISABLED;
diff --git a/src/Validator/Rules/QueryDepth.php b/src/Validator/Rules/QueryDepth.php
index 5fb7065..1408eee 100644
--- a/src/Validator/Rules/QueryDepth.php
+++ b/src/Validator/Rules/QueryDepth.php
@@ -1,21 +1,20 @@
setMaxQueryDepth($maxQueryDepth);
}
- /**
- * Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
- *
- * @param $maxQueryDepth
- */
- public function setMaxQueryDepth($maxQueryDepth)
- {
- $this->checkIfGreaterOrEqualToZero('maxQueryDepth', $maxQueryDepth);
-
- $this->maxQueryDepth = (int) $maxQueryDepth;
- }
-
- public function getMaxQueryDepth()
- {
- return $this->maxQueryDepth;
- }
-
- public static function maxQueryDepthErrorMessage($max, $count)
- {
- return sprintf('Max query depth should be %d but got %d.', $max, $count);
- }
-
public function getVisitor(ValidationContext $context)
{
return $this->invokeIfNeeded(
@@ -54,22 +31,19 @@ class QueryDepth extends AbstractQuerySecurity
'leave' => function (OperationDefinitionNode $operationDefinition) use ($context) {
$maxDepth = $this->fieldDepth($operationDefinition);
- if ($maxDepth > $this->getMaxQueryDepth()) {
- $context->reportError(
- new Error($this->maxQueryDepthErrorMessage($this->getMaxQueryDepth(), $maxDepth))
- );
+ if ($maxDepth <= $this->getMaxQueryDepth()) {
+ return;
}
+
+ $context->reportError(
+ new Error($this->maxQueryDepthErrorMessage($this->getMaxQueryDepth(), $maxDepth))
+ );
},
],
]
);
}
- protected function isEnabled()
- {
- return $this->getMaxQueryDepth() !== static::DISABLED;
- }
-
private function fieldDepth($node, $depth = 0, $maxDepth = 0)
{
if (isset($node->selectionSet) && $node->selectionSet instanceof SelectionSetNode) {
@@ -85,9 +59,9 @@ class QueryDepth extends AbstractQuerySecurity
{
switch ($node->kind) {
case NodeKind::FIELD:
- /* @var FieldNode $node */
+ /** @var FieldNode $node */
// node has children?
- if (null !== $node->selectionSet) {
+ if ($node->selectionSet !== null) {
// update maxDepth if needed
if ($depth > $maxDepth) {
$maxDepth = $depth;
@@ -97,18 +71,18 @@ class QueryDepth extends AbstractQuerySecurity
break;
case NodeKind::INLINE_FRAGMENT:
- /* @var InlineFragmentNode $node */
+ /** @var InlineFragmentNode $node */
// node has children?
- if (null !== $node->selectionSet) {
+ if ($node->selectionSet !== null) {
$maxDepth = $this->fieldDepth($node, $depth, $maxDepth);
}
break;
case NodeKind::FRAGMENT_SPREAD:
- /* @var FragmentSpreadNode $node */
+ /** @var FragmentSpreadNode $node */
$fragment = $this->getFragment($node);
- if (null !== $fragment) {
+ if ($fragment !== null) {
$maxDepth = $this->fieldDepth($fragment, $depth, $maxDepth);
}
break;
@@ -116,4 +90,31 @@ class QueryDepth extends AbstractQuerySecurity
return $maxDepth;
}
+
+ public function getMaxQueryDepth()
+ {
+ return $this->maxQueryDepth;
+ }
+
+ /**
+ * Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
+ *
+ * @param int $maxQueryDepth
+ */
+ public function setMaxQueryDepth($maxQueryDepth)
+ {
+ $this->checkIfGreaterOrEqualToZero('maxQueryDepth', $maxQueryDepth);
+
+ $this->maxQueryDepth = (int) $maxQueryDepth;
+ }
+
+ public static function maxQueryDepthErrorMessage($max, $count)
+ {
+ return sprintf('Max query depth should be %d but got %d.', $max, $count);
+ }
+
+ protected function isEnabled()
+ {
+ return $this->getMaxQueryDepth() !== static::DISABLED;
+ }
}
diff --git a/src/Validator/Rules/AbstractQuerySecurity.php b/src/Validator/Rules/QuerySecurityRule.php
similarity index 73%
rename from src/Validator/Rules/AbstractQuerySecurity.php
rename to src/Validator/Rules/QuerySecurityRule.php
index cfdc7bc..810a4d0 100644
--- a/src/Validator/Rules/AbstractQuerySecurity.php
+++ b/src/Validator/Rules/QuerySecurityRule.php
@@ -1,40 +1,35 @@
fragments;
- }
-
/**
* check if equal to 0 no check is done. Must be greater or equal to 0.
*
- * @param $value
+ * @param string $name
+ * @param int $value
*/
protected function checkIfGreaterOrEqualToZero($name, $value)
{
@@ -43,30 +38,30 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
}
}
- protected function gatherFragmentDefinition(ValidationContext $context)
- {
- // Gather all the fragment definition.
- // Importantly this does not include inline fragments.
- $definitions = $context->getDocument()->definitions;
- foreach ($definitions as $node) {
- if ($node instanceof FragmentDefinitionNode) {
- $this->fragments[$node->name->value] = $node;
- }
- }
- }
-
protected function getFragment(FragmentSpreadNode $fragmentSpread)
{
$spreadName = $fragmentSpread->name->value;
- $fragments = $this->getFragments();
+ $fragments = $this->getFragments();
- return isset($fragments[$spreadName]) ? $fragments[$spreadName] : null;
+ return $fragments[$spreadName] ?? null;
}
+ /**
+ * @return FragmentDefinitionNode[]
+ */
+ protected function getFragments()
+ {
+ return $this->fragments;
+ }
+
+ /**
+ * @param Closure[] $validators
+ * @return Closure[]
+ */
protected function invokeIfNeeded(ValidationContext $context, array $validators)
{
// is disabled?
- if (!$this->isEnabled()) {
+ if (! $this->isEnabled()) {
return [];
}
@@ -75,6 +70,22 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
return $validators;
}
+ abstract protected function isEnabled();
+
+ protected function gatherFragmentDefinition(ValidationContext $context)
+ {
+ // Gather all the fragment definition.
+ // Importantly this does not include inline fragments.
+ $definitions = $context->getDocument()->definitions;
+ foreach ($definitions as $node) {
+ if (! ($node instanceof FragmentDefinitionNode)) {
+ continue;
+ }
+
+ $this->fragments[$node->name->value] = $node;
+ }
+ }
+
/**
* Given a selectionSet, adds all of the fields in that selection to
* the passed in map of fields, and returns it at the end.
@@ -85,29 +96,30 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
*
* @see \GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged
*
- * @param ValidationContext $context
- * @param Type|null $parentType
- * @param SelectionSetNode $selectionSet
- * @param \ArrayObject $visitedFragmentNames
- * @param \ArrayObject $astAndDefs
+ * @param Type|null $parentType
*
* @return \ArrayObject
*/
- protected function collectFieldASTsAndDefs(ValidationContext $context, $parentType, SelectionSetNode $selectionSet, \ArrayObject $visitedFragmentNames = null, \ArrayObject $astAndDefs = null)
- {
+ protected function collectFieldASTsAndDefs(
+ ValidationContext $context,
+ $parentType,
+ SelectionSetNode $selectionSet,
+ ?\ArrayObject $visitedFragmentNames = null,
+ ?\ArrayObject $astAndDefs = null
+ ) {
$_visitedFragmentNames = $visitedFragmentNames ?: new \ArrayObject();
- $_astAndDefs = $astAndDefs ?: new \ArrayObject();
+ $_astAndDefs = $astAndDefs ?: new \ArrayObject();
foreach ($selectionSet->selections as $selection) {
switch ($selection->kind) {
case NodeKind::FIELD:
- /* @var FieldNode $selection */
+ /** @var FieldNode $selection */
$fieldName = $selection->name->value;
- $fieldDef = null;
+ $fieldDef = null;
if ($parentType && method_exists($parentType, 'getFields')) {
- $tmp = $parentType->getFields();
- $schemaMetaFieldDef = Introspection::schemaMetaFieldDef();
- $typeMetaFieldDef = Introspection::typeMetaFieldDef();
+ $tmp = $parentType->getFields();
+ $schemaMetaFieldDef = Introspection::schemaMetaFieldDef();
+ $typeMetaFieldDef = Introspection::typeMetaFieldDef();
$typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef();
if ($fieldName === $schemaMetaFieldDef->name && $context->getSchema()->getQueryType() === $parentType) {
@@ -121,14 +133,14 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
}
}
$responseName = $this->getFieldName($selection);
- if (!isset($_astAndDefs[$responseName])) {
+ if (! isset($_astAndDefs[$responseName])) {
$_astAndDefs[$responseName] = new \ArrayObject();
}
// create field context
$_astAndDefs[$responseName][] = [$selection, $fieldDef];
break;
case NodeKind::INLINE_FRAGMENT:
- /* @var InlineFragmentNode $selection */
+ /** @var InlineFragmentNode $selection */
$_astAndDefs = $this->collectFieldASTsAndDefs(
$context,
TypeInfo::typeFromAST($context->getSchema(), $selection->typeCondition),
@@ -138,12 +150,12 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
);
break;
case NodeKind::FRAGMENT_SPREAD:
- /* @var FragmentSpreadNode $selection */
+ /** @var FragmentSpreadNode $selection */
$fragName = $selection->name->value;
if (empty($_visitedFragmentNames[$fragName])) {
$_visitedFragmentNames[$fragName] = true;
- $fragment = $context->getFragment($fragName);
+ $fragment = $context->getFragment($fragName);
if ($fragment) {
$_astAndDefs = $this->collectFieldASTsAndDefs(
@@ -165,10 +177,9 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
protected function getFieldName(FieldNode $node)
{
$fieldName = $node->name->value;
- $responseName = $node->alias ? $node->alias->value : $fieldName;
- return $responseName;
+ return $node->alias ? $node->alias->value : $fieldName;
}
-
- abstract protected function isEnabled();
}
+
+class_alias(QuerySecurityRule::class, 'GraphQL\Validator\Rules\AbstractQuerySecurity');
diff --git a/src/Validator/Rules/ScalarLeafs.php b/src/Validator/Rules/ScalarLeafs.php
index 670403d..a96b5d8 100644
--- a/src/Validator/Rules/ScalarLeafs.php
+++ b/src/Validator/Rules/ScalarLeafs.php
@@ -1,4 +1,7 @@
function(FieldNode $node) use ($context) {
+ NodeKind::FIELD => function (FieldNode $node) use ($context) {
$type = $context->getType();
- if ($type) {
- if (Type::isLeafType(Type::getNamedType($type))) {
- if ($node->selectionSet) {
- $context->reportError(new Error(
- self::noSubselectionAllowedMessage($node->name->value, $type),
- [$node->selectionSet]
- ));
- }
- } else if (!$node->selectionSet) {
+ if (! $type) {
+ return;
+ }
+
+ if (Type::isLeafType(Type::getNamedType($type))) {
+ if ($node->selectionSet) {
$context->reportError(new Error(
- self::requiredSubselectionMessage($node->name->value, $type),
- [$node]
+ self::noSubselectionAllowedMessage($node->name->value, $type),
+ [$node->selectionSet]
));
}
+ } elseif (! $node->selectionSet) {
+ $context->reportError(new Error(
+ self::requiredSubselectionMessage($node->name->value, $type),
+ [$node]
+ ));
}
- }
+ },
];
}
+
+ public static function noSubselectionAllowedMessage($field, $type)
+ {
+ return sprintf('Field "%s" of type "%s" must not have a sub selection.', $field, $type);
+ }
+
+ public static function requiredSubselectionMessage($field, $type)
+ {
+ return sprintf('Field "%s" of type "%s" must have a sub selection.', $field, $type);
+ }
}
diff --git a/src/Validator/Rules/UniqueArgumentNames.php b/src/Validator/Rules/UniqueArgumentNames.php
index 7c6eef1..2e83d43 100644
--- a/src/Validator/Rules/UniqueArgumentNames.php
+++ b/src/Validator/Rules/UniqueArgumentNames.php
@@ -1,20 +1,20 @@
knownArgNames = [];
return [
- NodeKind::FIELD => function () {
- $this->knownArgNames = [];;
+ NodeKind::FIELD => function () {
+ $this->knownArgNames = [];
},
NodeKind::DIRECTIVE => function () {
$this->knownArgNames = [];
},
- NodeKind::ARGUMENT => function (ArgumentNode $node) use ($context) {
+ NodeKind::ARGUMENT => function (ArgumentNode $node) use ($context) {
$argName = $node->name->value;
- if (!empty($this->knownArgNames[$argName])) {
+ if (! empty($this->knownArgNames[$argName])) {
$context->reportError(new Error(
self::duplicateArgMessage($argName),
[$this->knownArgNames[$argName], $node->name]
@@ -38,8 +38,14 @@ class UniqueArgumentNames extends AbstractValidationRule
} else {
$this->knownArgNames[$argName] = $node->name;
}
+
return Visitor::skipNode();
- }
+ },
];
}
+
+ public static function duplicateArgMessage($argName)
+ {
+ return sprintf('There can be only one argument named "%s".', $argName);
+ }
}
diff --git a/src/Validator/Rules/UniqueDirectivesPerLocation.php b/src/Validator/Rules/UniqueDirectivesPerLocation.php
index 08d778a..c049c82 100644
--- a/src/Validator/Rules/UniqueDirectivesPerLocation.php
+++ b/src/Validator/Rules/UniqueDirectivesPerLocation.php
@@ -1,38 +1,44 @@
function(Node $node) use ($context) {
- if (isset($node->directives)) {
- $knownDirectives = [];
- foreach ($node->directives as $directive) {
- /** @var DirectiveNode $directive */
- $directiveName = $directive->name->value;
- if (isset($knownDirectives[$directiveName])) {
- $context->reportError(new Error(
- self::duplicateDirectiveMessage($directiveName),
- [$knownDirectives[$directiveName], $directive]
- ));
- } else {
- $knownDirectives[$directiveName] = $directive;
- }
+ 'enter' => function (Node $node) use ($context) {
+ if (! isset($node->directives)) {
+ return;
+ }
+
+ $knownDirectives = [];
+ foreach ($node->directives as $directive) {
+ /** @var DirectiveNode $directive */
+ $directiveName = $directive->name->value;
+ if (isset($knownDirectives[$directiveName])) {
+ $context->reportError(new Error(
+ self::duplicateDirectiveMessage($directiveName),
+ [$knownDirectives[$directiveName], $directive]
+ ));
+ } else {
+ $knownDirectives[$directiveName] = $directive;
}
}
- }
+ },
];
}
+
+ public static function duplicateDirectiveMessage($directiveName)
+ {
+ return sprintf('The directive "%s" can only be used once at this location.', $directiveName);
+ }
}
diff --git a/src/Validator/Rules/UniqueFragmentNames.php b/src/Validator/Rules/UniqueFragmentNames.php
index ebeb0b2..e29de7b 100644
--- a/src/Validator/Rules/UniqueFragmentNames.php
+++ b/src/Validator/Rules/UniqueFragmentNames.php
@@ -1,19 +1,20 @@
function () {
return Visitor::skipNode();
},
- NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
+ NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
$fragmentName = $node->name->value;
- if (!empty($this->knownFragmentNames[$fragmentName])) {
+ if (empty($this->knownFragmentNames[$fragmentName])) {
+ $this->knownFragmentNames[$fragmentName] = $node->name;
+ } else {
$context->reportError(new Error(
self::duplicateFragmentNameMessage($fragmentName),
- [ $this->knownFragmentNames[$fragmentName], $node->name ]
+ [$this->knownFragmentNames[$fragmentName], $node->name]
));
- } else {
- $this->knownFragmentNames[$fragmentName] = $node->name;
}
+
return Visitor::skipNode();
- }
+ },
];
}
+
+ public static function duplicateFragmentNameMessage($fragName)
+ {
+ return sprintf('There can be only one fragment named "%s".', $fragName);
+ }
}
diff --git a/src/Validator/Rules/UniqueInputFieldNames.php b/src/Validator/Rules/UniqueInputFieldNames.php
index 1e48d48..6426b43 100644
--- a/src/Validator/Rules/UniqueInputFieldNames.php
+++ b/src/Validator/Rules/UniqueInputFieldNames.php
@@ -1,4 +1,7 @@
knownNames = [];
+ $this->knownNames = [];
$this->knownNameStack = [];
return [
- NodeKind::OBJECT => [
- 'enter' => function() {
+ NodeKind::OBJECT => [
+ 'enter' => function () {
$this->knownNameStack[] = $this->knownNames;
- $this->knownNames = [];
+ $this->knownNames = [];
},
- 'leave' => function() {
+ 'leave' => function () {
$this->knownNames = array_pop($this->knownNameStack);
- }
+ },
],
- NodeKind::OBJECT_FIELD => function(ObjectFieldNode $node) use ($context) {
+ NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use ($context) {
$fieldName = $node->name->value;
- if (!empty($this->knownNames[$fieldName])) {
+ if (! empty($this->knownNames[$fieldName])) {
$context->reportError(new Error(
self::duplicateInputFieldMessage($fieldName),
- [ $this->knownNames[$fieldName], $node->name ]
+ [$this->knownNames[$fieldName], $node->name]
));
} else {
$this->knownNames[$fieldName] = $node->name;
}
+
return Visitor::skipNode();
- }
+ },
];
}
+
+ public static function duplicateInputFieldMessage($fieldName)
+ {
+ return sprintf('There can be only one input field named "%s".', $fieldName);
+ }
}
diff --git a/src/Validator/Rules/UniqueOperationNames.php b/src/Validator/Rules/UniqueOperationNames.php
index 80a352c..232380d 100644
--- a/src/Validator/Rules/UniqueOperationNames.php
+++ b/src/Validator/Rules/UniqueOperationNames.php
@@ -1,19 +1,20 @@
knownOperationNames = [];
return [
- NodeKind::OPERATION_DEFINITION => function(OperationDefinitionNode $node) use ($context) {
+ NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use ($context) {
$operationName = $node->name;
if ($operationName) {
- if (!empty($this->knownOperationNames[$operationName->value])) {
+ if (empty($this->knownOperationNames[$operationName->value])) {
+ $this->knownOperationNames[$operationName->value] = $operationName;
+ } else {
$context->reportError(new Error(
self::duplicateOperationNameMessage($operationName->value),
- [ $this->knownOperationNames[$operationName->value], $operationName ]
+ [$this->knownOperationNames[$operationName->value], $operationName]
));
- } else {
- $this->knownOperationNames[$operationName->value] = $operationName;
}
}
+
return Visitor::skipNode();
},
- NodeKind::FRAGMENT_DEFINITION => function() {
+ NodeKind::FRAGMENT_DEFINITION => function () {
return Visitor::skipNode();
- }
+ },
];
}
+
+ public static function duplicateOperationNameMessage($operationName)
+ {
+ return sprintf('There can be only one operation named "%s".', $operationName);
+ }
}
diff --git a/src/Validator/Rules/UniqueVariableNames.php b/src/Validator/Rules/UniqueVariableNames.php
index 4329721..f050a73 100644
--- a/src/Validator/Rules/UniqueVariableNames.php
+++ b/src/Validator/Rules/UniqueVariableNames.php
@@ -1,18 +1,19 @@
knownVariableNames = [];
return [
- NodeKind::OPERATION_DEFINITION => function() {
+ NodeKind::OPERATION_DEFINITION => function () {
$this->knownVariableNames = [];
},
- NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $node) use ($context) {
+ NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use ($context) {
$variableName = $node->variable->name->value;
- if (!empty($this->knownVariableNames[$variableName])) {
+ if (empty($this->knownVariableNames[$variableName])) {
+ $this->knownVariableNames[$variableName] = $node->variable->name;
+ } else {
$context->reportError(new Error(
self::duplicateVariableMessage($variableName),
- [ $this->knownVariableNames[$variableName], $node->variable->name ]
+ [$this->knownVariableNames[$variableName], $node->variable->name]
));
- } else {
- $this->knownVariableNames[$variableName] = $node->variable->name;
}
- }
+ },
];
}
+
+ public static function duplicateVariableMessage($variableName)
+ {
+ return sprintf('There can be only one variable named "%s".', $variableName);
+ }
}
diff --git a/src/Validator/Rules/AbstractValidationRule.php b/src/Validator/Rules/ValidationRule.php
similarity index 67%
rename from src/Validator/Rules/AbstractValidationRule.php
rename to src/Validator/Rules/ValidationRule.php
index 0926c29..c35388f 100644
--- a/src/Validator/Rules/AbstractValidationRule.php
+++ b/src/Validator/Rules/ValidationRule.php
@@ -1,10 +1,16 @@
function(NullValueNode $node) use ($context) {
+ NodeKind::NULL => function (NullValueNode $node) use ($context) {
$type = $context->getInputType();
- if ($type instanceof NonNull) {
- $context->reportError(
- new Error(
- self::badValueMessage((string) $type, Printer::doPrint($node)),
- $node
- )
- );
+ if (! ($type instanceof NonNull)) {
+ return;
}
+
+ $context->reportError(
+ new Error(
+ self::badValueMessage((string) $type, Printer::doPrint($node)),
+ $node
+ )
+ );
},
- NodeKind::LST => function(ListValueNode $node) use ($context) {
+ NodeKind::LST => function (ListValueNode $node) use ($context) {
// Note: TypeInfo will traverse into a list's item type, so look to the
// parent input type to check if it is a list.
$type = Type::getNullableType($context->getParentInputType());
- if (!$type instanceof ListOfType) {
+ if (! $type instanceof ListOfType) {
$this->isValidScalar($context, $node);
+
return Visitor::skipNode();
}
},
- NodeKind::OBJECT => function(ObjectValueNode $node) use ($context) {
+ NodeKind::OBJECT => function (ObjectValueNode $node) use ($context) {
// Note: TypeInfo will traverse into a list's item type, so look to the
// parent input type to check if it is a list.
$type = Type::getNamedType($context->getInputType());
- if (!$type instanceof InputObjectType) {
+ if (! $type instanceof InputObjectType) {
$this->isValidScalar($context, $node);
+
return Visitor::skipNode();
}
// Ensure every required field exists.
- $inputFields = $type->getFields();
- $nodeFields = iterator_to_array($node->fields);
+ $inputFields = $type->getFields();
+ $nodeFields = iterator_to_array($node->fields);
$fieldNodeMap = array_combine(
- array_map(function ($field) { return $field->name->value; }, $nodeFields),
+ array_map(
+ function ($field) {
+ return $field->name->value;
+ },
+ $nodeFields
+ ),
array_values($nodeFields)
);
foreach ($inputFields as $fieldName => $fieldDef) {
$fieldType = $fieldDef->getType();
- if (!isset($fieldNodeMap[$fieldName]) && $fieldType instanceof NonNull) {
- $context->reportError(
- new Error(
- self::requiredFieldMessage($type->name, $fieldName, (string) $fieldType),
- $node
- )
- );
+ if (isset($fieldNodeMap[$fieldName]) || ! ($fieldType instanceof NonNull)) {
+ continue;
}
- }
- },
- NodeKind::OBJECT_FIELD => function(ObjectFieldNode $node) use ($context) {
- $parentType = Type::getNamedType($context->getParentInputType());
- $fieldType = $context->getInputType();
- if (!$fieldType && $parentType instanceof InputObjectType) {
- $suggestions = Utils::suggestionList(
- $node->name->value,
- array_keys($parentType->getFields())
- );
- $didYouMean = $suggestions
- ? "Did you mean " . Utils::orList($suggestions) . "?"
- : null;
$context->reportError(
new Error(
- self::unknownFieldMessage($parentType->name, $node->name->value, $didYouMean),
+ self::requiredFieldMessage($type->name, $fieldName, (string) $fieldType),
$node
)
);
}
},
- NodeKind::ENUM => function(EnumValueNode $node) use ($context) {
+ NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use ($context) {
+ $parentType = Type::getNamedType($context->getParentInputType());
+ $fieldType = $context->getInputType();
+ if ($fieldType || ! ($parentType instanceof InputObjectType)) {
+ return;
+ }
+
+ $suggestions = Utils::suggestionList(
+ $node->name->value,
+ array_keys($parentType->getFields())
+ );
+ $didYouMean = $suggestions
+ ? 'Did you mean ' . Utils::orList($suggestions) . '?'
+ : null;
+
+ $context->reportError(
+ new Error(
+ self::unknownFieldMessage($parentType->name, $node->name->value, $didYouMean),
+ $node
+ )
+ );
+ },
+ NodeKind::ENUM => function (EnumValueNode $node) use ($context) {
$type = Type::getNamedType($context->getInputType());
- if (!$type instanceof EnumType) {
+ if (! $type instanceof EnumType) {
$this->isValidScalar($context, $node);
- } else if (!$type->getValue($node->value)) {
+ } elseif (! $type->getValue($node->value)) {
$context->reportError(
new Error(
self::badValueMessage(
@@ -140,25 +142,39 @@ class ValuesOfCorrectType extends AbstractValidationRule
);
}
},
- NodeKind::INT => function (IntValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
- NodeKind::FLOAT => function (FloatValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
- NodeKind::STRING => function (StringValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
- NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
+ NodeKind::INT => function (IntValueNode $node) use ($context) {
+ $this->isValidScalar($context, $node);
+ },
+ NodeKind::FLOAT => function (FloatValueNode $node) use ($context) {
+ $this->isValidScalar($context, $node);
+ },
+ NodeKind::STRING => function (StringValueNode $node) use ($context) {
+ $this->isValidScalar($context, $node);
+ },
+ NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context) {
+ $this->isValidScalar($context, $node);
+ },
];
}
+ public static function badValueMessage($typeName, $valueName, $message = null)
+ {
+ return sprintf('Expected type %s, found %s', $typeName, $valueName) .
+ ($message ? "; ${message}" : '.');
+ }
+
private function isValidScalar(ValidationContext $context, ValueNode $node)
{
// Report any error at the full type expected by the location.
$locationType = $context->getInputType();
- if (!$locationType) {
+ if (! $locationType) {
return;
}
$type = Type::getNamedType($locationType);
- if (!$type instanceof ScalarType) {
+ if (! $type instanceof ScalarType) {
$context->reportError(
new Error(
self::badValueMessage(
@@ -169,6 +185,7 @@ class ValuesOfCorrectType extends AbstractValidationRule
$node
)
);
+
return;
}
@@ -216,12 +233,26 @@ class ValuesOfCorrectType extends AbstractValidationRule
if ($type instanceof EnumType) {
$suggestions = Utils::suggestionList(
Printer::doPrint($node),
- array_map(function (EnumValueDefinition $value) {
- return $value->name;
- }, $type->getValues())
+ array_map(
+ function (EnumValueDefinition $value) {
+ return $value->name;
+ },
+ $type->getValues()
+ )
);
return $suggestions ? 'Did you mean the enum value ' . Utils::orList($suggestions) . '?' : null;
}
}
+
+ public static function requiredFieldMessage($typeName, $fieldName, $fieldTypeName)
+ {
+ return sprintf('Field %s.%s of required type %s was not provided.', $typeName, $fieldName, $fieldTypeName);
+ }
+
+ public static function unknownFieldMessage($typeName, $fieldName, $message = null)
+ {
+ return sprintf('Field "%s" is not defined by type %s', $fieldName, $typeName) .
+ ($message ? sprintf('; %s', $message) : '.');
+ }
}
diff --git a/src/Validator/Rules/VariablesAreInputTypes.php b/src/Validator/Rules/VariablesAreInputTypes.php
index a8f1bbf..4abc2e5 100644
--- a/src/Validator/Rules/VariablesAreInputTypes.php
+++ b/src/Validator/Rules/VariablesAreInputTypes.php
@@ -1,39 +1,42 @@
function(VariableDefinitionNode $node) use ($context) {
+ NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use ($context) {
$type = TypeInfo::typeFromAST($context->getSchema(), $node->type);
// If the variable type is not an input type, return an error.
- if ($type && !Type::isInputType($type)) {
- $variableName = $node->variable->name->value;
- $context->reportError(new Error(
- self::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)),
- [ $node->type ]
- ));
+ if (! $type || Type::isInputType($type)) {
+ return;
}
- }
+
+ $variableName = $node->variable->name->value;
+ $context->reportError(new Error(
+ self::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)),
+ [$node->type]
+ ));
+ },
];
}
+
+ public static function nonInputTypeOnVarMessage($variableName, $typeName)
+ {
+ return sprintf('Variable "$%s" cannot be non-input type "%s".', $variableName, $typeName);
+ }
}
diff --git a/src/Validator/Rules/VariablesDefaultValueAllowed.php b/src/Validator/Rules/VariablesDefaultValueAllowed.php
index fcbbef4..64808fc 100644
--- a/src/Validator/Rules/VariablesDefaultValueAllowed.php
+++ b/src/Validator/Rules/VariablesDefaultValueAllowed.php
@@ -1,4 +1,7 @@
function(VariableDefinitionNode $node) use ($context) {
- $name = $node->variable->name->value;
+ NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use ($context) {
+ $name = $node->variable->name->value;
$defaultValue = $node->defaultValue;
- $type = $context->getInputType();
+ $type = $context->getInputType();
if ($type instanceof NonNull && $defaultValue) {
$context->reportError(
- new Error(
- self::defaultForRequiredVarMessage(
- $name,
- $type,
- $type->getWrappedType()
- ),
- [$defaultValue]
- )
+ new Error(
+ self::defaultForRequiredVarMessage(
+ $name,
+ $type,
+ $type->getWrappedType()
+ ),
+ [$defaultValue]
+ )
);
}
return Visitor::skipNode();
},
- NodeKind::SELECTION_SET => function(SelectionSetNode $node) use ($context) {
+ NodeKind::SELECTION_SET => function (SelectionSetNode $node) use ($context) {
return Visitor::skipNode();
},
- NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) use ($context) {
+ NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
return Visitor::skipNode();
},
];
}
+
+ public static function defaultForRequiredVarMessage($varName, $type, $guessType)
+ {
+ return sprintf(
+ 'Variable "$%s" of type "%s" is required and will not use the default value. Perhaps you meant to use type "%s".',
+ $varName,
+ $type,
+ $guessType
+ );
+ }
}
diff --git a/src/Validator/Rules/VariablesInAllowedPosition.php b/src/Validator/Rules/VariablesInAllowedPosition.php
index c0608ff..a593610 100644
--- a/src/Validator/Rules/VariablesInAllowedPosition.php
+++ b/src/Validator/Rules/VariablesInAllowedPosition.php
@@ -1,4 +1,7 @@
function () {
$this->varDefMap = [];
},
- 'leave' => function(OperationDefinitionNode $operation) use ($context) {
+ 'leave' => function (OperationDefinitionNode $operation) use ($context) {
$usages = $context->getRecursiveVariableUsages($operation);
foreach ($usages as $usage) {
- $node = $usage['node'];
- $type = $usage['type'];
+ $node = $usage['node'];
+ $type = $usage['type'];
$varName = $node->name->value;
- $varDef = isset($this->varDefMap[$varName]) ? $this->varDefMap[$varName] : null;
+ $varDef = $this->varDefMap[$varName] ?? null;
- if ($varDef && $type) {
- // A var type is allowed if it is the same or more strict (e.g. is
- // a subtype of) than the expected type. It can be more strict if
- // the variable type is non-null when the expected type is nullable.
- // If both are list types, the variable item type can be more strict
- // than the expected item type (contravariant).
- $schema = $context->getSchema();
- $varType = TypeInfo::typeFromAST($schema, $varDef->type);
-
- if ($varType && !TypeComparators::isTypeSubTypeOf($schema, $this->effectiveType($varType, $varDef), $type)) {
- $context->reportError(new Error(
- self::badVarPosMessage($varName, $varType, $type),
- [$varDef, $node]
- ));
- }
+ if (! $varDef || ! $type) {
+ continue;
}
+
+ // A var type is allowed if it is the same or more strict (e.g. is
+ // a subtype of) than the expected type. It can be more strict if
+ // the variable type is non-null when the expected type is nullable.
+ // If both are list types, the variable item type can be more strict
+ // than the expected item type (contravariant).
+ $schema = $context->getSchema();
+ $varType = TypeInfo::typeFromAST($schema, $varDef->type);
+
+ if (! $varType || TypeComparators::isTypeSubTypeOf(
+ $schema,
+ $this->effectiveType($varType, $varDef),
+ $type
+ )) {
+ continue;
+ }
+
+ $context->reportError(new Error(
+ self::badVarPosMessage($varName, $varType, $type),
+ [$varDef, $node]
+ ));
}
- }
+ },
],
- NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $varDefNode) {
+ NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $varDefNode) {
$this->varDefMap[$varDefNode->variable->name->value] = $varDefNode;
- }
+ },
];
}
- // A var type is allowed if it is the same or more strict than the expected
- // type. It can be more strict if the variable type is non-null when the
- // expected type is nullable. If both are list types, the variable item type can
- // be more strict than the expected item type.
+ private function effectiveType($varType, $varDef)
+ {
+ return (! $varDef->defaultValue || $varType instanceof NonNull) ? $varType : new NonNull($varType);
+ }
+
+ /**
+ * A var type is allowed if it is the same or more strict than the expected
+ * type. It can be more strict if the variable type is non-null when the
+ * expected type is nullable. If both are list types, the variable item type can
+ * be more strict than the expected item type.
+ */
+ public static function badVarPosMessage($varName, $varType, $expectedType)
+ {
+ return sprintf(
+ 'Variable "$%s" of type "%s" used in position expecting type "%s".',
+ $varName,
+ $varType,
+ $expectedType
+ );
+ }
+
+ /** If a variable definition has a default value, it's effectively non-null. */
private function varTypeAllowedForType($varType, $expectedType)
{
if ($expectedType instanceof NonNull) {
if ($varType instanceof NonNull) {
return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType());
}
+
return false;
}
if ($varType instanceof NonNull) {
@@ -80,13 +106,7 @@ class VariablesInAllowedPosition extends AbstractValidationRule
if ($varType instanceof ListOfType && $expectedType instanceof ListOfType) {
return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType());
}
+
return $varType === $expectedType;
}
-
- // If a variable definition has a default value, it's effectively non-null.
- private function effectiveType($varType, $varDef)
- {
- return (!$varDef->defaultValue || $varType instanceof NonNull) ? $varType : new NonNull($varType);
- }
-
}
diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php
index 4d82ce4..d8a834e 100644
--- a/src/Validator/ValidationContext.php
+++ b/src/Validator/ValidationContext.php
@@ -1,22 +1,28 @@
schema = $schema;
- $this->ast = $ast;
- $this->typeInfo = $typeInfo;
- $this->errors = [];
- $this->fragmentSpreads = new SplObjectStorage();
+ $this->schema = $schema;
+ $this->ast = $ast;
+ $this->typeInfo = $typeInfo;
+ $this->errors = [];
+ $this->fragmentSpreads = new SplObjectStorage();
$this->recursivelyReferencedFragments = new SplObjectStorage();
- $this->variableUsages = new SplObjectStorage();
- $this->recursiveVariableUsages = new SplObjectStorage();
+ $this->variableUsages = new SplObjectStorage();
+ $this->recursiveVariableUsages = new SplObjectStorage();
}
- /**
- * @param Error $error
- */
- function reportError(Error $error)
+ public function reportError(Error $error)
{
$this->errors[] = $error;
}
@@ -100,7 +78,7 @@ class ValidationContext
/**
* @return Error[]
*/
- function getErrors()
+ public function getErrors()
{
return $this->errors;
}
@@ -108,159 +86,176 @@ class ValidationContext
/**
* @return Schema
*/
- function getSchema()
+ public function getSchema()
{
return $this->schema;
}
/**
- * @return DocumentNode
+ * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
*/
- function getDocument()
+ public function getRecursiveVariableUsages(OperationDefinitionNode $operation)
{
- return $this->ast;
- }
+ $usages = $this->recursiveVariableUsages[$operation] ?? null;
- /**
- * @param string $name
- * @return FragmentDefinitionNode|null
- */
- function getFragment($name)
- {
- $fragments = $this->fragments;
- if (!$fragments) {
- $fragments = [];
- foreach ($this->getDocument()->definitions as $statement) {
- if ($statement->kind === NodeKind::FRAGMENT_DEFINITION) {
- $fragments[$statement->name->value] = $statement;
- }
- }
- $this->fragments = $fragments;
- }
- return isset($fragments[$name]) ? $fragments[$name] : null;
- }
-
- /**
- * @param HasSelectionSet $node
- * @return FragmentSpreadNode[]
- */
- function getFragmentSpreads(HasSelectionSet $node)
- {
- $spreads = isset($this->fragmentSpreads[$node]) ? $this->fragmentSpreads[$node] : null;
- if (!$spreads) {
- $spreads = [];
- $setsToVisit = [$node->selectionSet];
- while (!empty($setsToVisit)) {
- $set = array_pop($setsToVisit);
-
- for ($i = 0; $i < count($set->selections); $i++) {
- $selection = $set->selections[$i];
- if ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
- $spreads[] = $selection;
- } else if ($selection->selectionSet) {
- $setsToVisit[] = $selection->selectionSet;
- }
- }
- }
- $this->fragmentSpreads[$node] = $spreads;
- }
- return $spreads;
- }
-
- /**
- * @param OperationDefinitionNode $operation
- * @return FragmentDefinitionNode[]
- */
- function getRecursivelyReferencedFragments(OperationDefinitionNode $operation)
- {
- $fragments = isset($this->recursivelyReferencedFragments[$operation]) ? $this->recursivelyReferencedFragments[$operation] : null;
-
- if (!$fragments) {
- $fragments = [];
- $collectedNames = [];
- $nodesToVisit = [$operation];
- while (!empty($nodesToVisit)) {
- $node = array_pop($nodesToVisit);
- $spreads = $this->getFragmentSpreads($node);
- for ($i = 0; $i < count($spreads); $i++) {
- $fragName = $spreads[$i]->name->value;
-
- if (empty($collectedNames[$fragName])) {
- $collectedNames[$fragName] = true;
- $fragment = $this->getFragment($fragName);
- if ($fragment) {
- $fragments[] = $fragment;
- $nodesToVisit[] = $fragment;
- }
- }
- }
- }
- $this->recursivelyReferencedFragments[$operation] = $fragments;
- }
- return $fragments;
- }
-
- /**
- * @param HasSelectionSet $node
- * @return array List of ['node' => VariableNode, 'type' => ?InputObjectType]
- */
- function getVariableUsages(HasSelectionSet $node)
- {
- $usages = isset($this->variableUsages[$node]) ? $this->variableUsages[$node] : null;
-
- if (!$usages) {
- $newUsages = [];
- $typeInfo = new TypeInfo($this->schema);
- Visitor::visit($node, Visitor::visitWithTypeInfo($typeInfo, [
- NodeKind::VARIABLE_DEFINITION => function () {
- return false;
- },
- NodeKind::VARIABLE => function (VariableNode $variable) use (&$newUsages, $typeInfo) {
- $newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
- }
- ]));
- $usages = $newUsages;
- $this->variableUsages[$node] = $usages;
- }
- return $usages;
- }
-
- /**
- * @param OperationDefinitionNode $operation
- * @return array List of ['node' => VariableNode, 'type' => ?InputObjectType]
- */
- function getRecursiveVariableUsages(OperationDefinitionNode $operation)
- {
- $usages = isset($this->recursiveVariableUsages[$operation]) ? $this->recursiveVariableUsages[$operation] : null;
-
- if (!$usages) {
- $usages = $this->getVariableUsages($operation);
+ if (! $usages) {
+ $usages = $this->getVariableUsages($operation);
$fragments = $this->getRecursivelyReferencedFragments($operation);
$tmp = [$usages];
for ($i = 0; $i < count($fragments); $i++) {
$tmp[] = $this->getVariableUsages($fragments[$i]);
}
- $usages = call_user_func_array('array_merge', $tmp);
+ $usages = call_user_func_array('array_merge', $tmp);
$this->recursiveVariableUsages[$operation] = $usages;
}
+
return $usages;
}
+ /**
+ * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
+ */
+ private function getVariableUsages(HasSelectionSet $node)
+ {
+ $usages = $this->variableUsages[$node] ?? null;
+
+ if (! $usages) {
+ $newUsages = [];
+ $typeInfo = new TypeInfo($this->schema);
+ Visitor::visit(
+ $node,
+ Visitor::visitWithTypeInfo(
+ $typeInfo,
+ [
+ NodeKind::VARIABLE_DEFINITION => function () {
+ return false;
+ },
+ NodeKind::VARIABLE => function (VariableNode $variable) use (
+ &$newUsages,
+ $typeInfo
+ ) {
+ $newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
+ },
+ ]
+ )
+ );
+ $usages = $newUsages;
+ $this->variableUsages[$node] = $usages;
+ }
+
+ return $usages;
+ }
+
+ /**
+ * @return FragmentDefinitionNode[]
+ */
+ public function getRecursivelyReferencedFragments(OperationDefinitionNode $operation)
+ {
+ $fragments = $this->recursivelyReferencedFragments[$operation] ?? null;
+
+ if (! $fragments) {
+ $fragments = [];
+ $collectedNames = [];
+ $nodesToVisit = [$operation];
+ while (! empty($nodesToVisit)) {
+ $node = array_pop($nodesToVisit);
+ $spreads = $this->getFragmentSpreads($node);
+ for ($i = 0; $i < count($spreads); $i++) {
+ $fragName = $spreads[$i]->name->value;
+
+ if (! empty($collectedNames[$fragName])) {
+ continue;
+ }
+
+ $collectedNames[$fragName] = true;
+ $fragment = $this->getFragment($fragName);
+ if (! $fragment) {
+ continue;
+ }
+
+ $fragments[] = $fragment;
+ $nodesToVisit[] = $fragment;
+ }
+ }
+ $this->recursivelyReferencedFragments[$operation] = $fragments;
+ }
+
+ return $fragments;
+ }
+
+ /**
+ * @return FragmentSpreadNode[]
+ */
+ public function getFragmentSpreads(HasSelectionSet $node)
+ {
+ $spreads = $this->fragmentSpreads[$node] ?? null;
+ if (! $spreads) {
+ $spreads = [];
+ /** @var SelectionSetNode[] $setsToVisit */
+ $setsToVisit = [$node->selectionSet];
+ while (! empty($setsToVisit)) {
+ $set = array_pop($setsToVisit);
+
+ for ($i = 0; $i < count($set->selections); $i++) {
+ $selection = $set->selections[$i];
+ if ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
+ $spreads[] = $selection;
+ } elseif ($selection->selectionSet) {
+ $setsToVisit[] = $selection->selectionSet;
+ }
+ }
+ }
+ $this->fragmentSpreads[$node] = $spreads;
+ }
+
+ return $spreads;
+ }
+
+ /**
+ * @param string $name
+ * @return FragmentDefinitionNode|null
+ */
+ public function getFragment($name)
+ {
+ $fragments = $this->fragments;
+ if (! $fragments) {
+ $fragments = [];
+ foreach ($this->getDocument()->definitions as $statement) {
+ if ($statement->kind !== NodeKind::FRAGMENT_DEFINITION) {
+ continue;
+ }
+
+ $fragments[$statement->name->value] = $statement;
+ }
+ $this->fragments = $fragments;
+ }
+
+ return $fragments[$name] ?? null;
+ }
+
+ /**
+ * @return DocumentNode
+ */
+ public function getDocument()
+ {
+ return $this->ast;
+ }
+
/**
* Returns OutputType
*
* @return Type
*/
- function getType()
+ public function getType()
{
return $this->typeInfo->getType();
}
/**
- * @return CompositeType
+ * @return Type
*/
- function getParentType()
+ public function getParentType()
{
return $this->typeInfo->getParentType();
}
@@ -268,7 +263,7 @@ class ValidationContext
/**
* @return InputType
*/
- function getInputType()
+ public function getInputType()
{
return $this->typeInfo->getInputType();
}
@@ -276,7 +271,7 @@ class ValidationContext
/**
* @return InputType
*/
- function getParentInputType()
+ public function getParentInputType()
{
return $this->typeInfo->getParentInputType();
}
@@ -284,17 +279,17 @@ class ValidationContext
/**
* @return FieldDefinition
*/
- function getFieldDef()
+ public function getFieldDef()
{
return $this->typeInfo->getFieldDef();
}
- function getDirective()
+ public function getDirective()
{
return $this->typeInfo->getDirective();
}
- function getArgument()
+ public function getArgument()
{
return $this->typeInfo->getArgument();
}
diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php
index 4ed2805..fd7bb20 100644
--- a/tests/Language/VisitorTest.php
+++ b/tests/Language/VisitorTest.php
@@ -1,4 +1,7 @@
toArray()) : $parent;
+ $parentArray = $parent && ! is_array($parent) ? ($parent instanceof NodeList ? iterator_to_array($parent) : $parent->toArray()) : $parent;
$this->assertInstanceOf(Node::class, $node);
$this->assertContains($node->kind, array_keys(NodeKind::$classMap));
$isRoot = $key === null;
if ($isRoot) {
- if (!$isEdited) {
+ if (! $isEdited) {
$this->assertEquals($ast, $node);
}
$this->assertEquals(null, $parent);
@@ -60,14 +71,16 @@ class VisitorTest extends ValidatorTestCase
$this->assertInternalType('array', $ancestors);
$this->assertCount(count($path) - 1, $ancestors);
- if (!$isEdited) {
- $this->assertEquals($node, $parentArray[$key]);
- $this->assertEquals($node, $this->getNodeByPath($ast, $path));
- $ancestorsLength = count($ancestors);
- for ($i = 0; $i < $ancestorsLength; ++$i) {
- $ancestorPath = array_slice($path, 0, $i);
- $this->assertEquals($ancestors[$i], $this->getNodeByPath($ast, $ancestorPath));
- }
+ if ($isEdited) {
+ return;
+ }
+
+ $this->assertEquals($node, $parentArray[$key]);
+ $this->assertEquals($node, $this->getNodeByPath($ast, $path));
+ $ancestorsLength = count($ancestors);
+ for ($i = 0; $i < $ancestorsLength; ++$i) {
+ $ancestorPath = array_slice($path, 0, $i);
+ $this->assertEquals($ancestors[$i], $this->getNodeByPath($ast, $ancestorPath));
}
}
@@ -104,94 +117,84 @@ class VisitorTest extends ValidatorTestCase
$this->assertEquals($expected, $visited);
}
- /**
- * @it allows editing a node both on enter and on leave
- */
public function testAllowsEditingNodeOnEnterAndOnLeave()
{
$ast = Parser::parse('{ a, b, c { a, b, c } }', [ 'noLocation' => true ]);
$selectionSet = null;
- $editedAst = Visitor::visit($ast, [
+ $editedAst = Visitor::visit($ast, [
NodeKind::OPERATION_DEFINITION => [
- 'enter' => function(OperationDefinitionNode $node) use (&$selectionSet, $ast) {
+ 'enter' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$selectionSet = $node->selectionSet;
- $newNode = clone $node;
+ $newNode = clone $node;
$newNode->selectionSet = new SelectionSetNode([
- 'selections' => []
+ 'selections' => [],
]);
- $newNode->didEnter = true;
+ $newNode->didEnter = true;
return $newNode;
},
- 'leave' => function(OperationDefinitionNode $node) use (&$selectionSet, $ast) {
+ 'leave' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
- $newNode = clone $node;
+ $newNode = clone $node;
$newNode->selectionSet = $selectionSet;
- $newNode->didLeave = true;
+ $newNode->didLeave = true;
return $newNode;
- }
- ]
+ },
+ ],
]);
$this->assertNotEquals($ast, $editedAst);
- $expected = $ast->cloneDeep();
+ $expected = $ast->cloneDeep();
$expected->definitions[0]->didEnter = true;
$expected->definitions[0]->didLeave = true;
$this->assertEquals($expected, $editedAst);
}
- /**
- * @it allows editing the root node on enter and on leave
- */
public function testAllowsEditingRootNodeOnEnterAndLeave()
{
- $ast = Parser::parse('{ a, b, c { a, b, c } }', [ 'noLocation' => true ]);
+ $ast = Parser::parse('{ a, b, c { a, b, c } }', [ 'noLocation' => true ]);
$definitions = $ast->definitions;
$editedAst = Visitor::visit($ast, [
NodeKind::DOCUMENT => [
'enter' => function (DocumentNode $node) use ($ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $tmp = clone $node;
+ $tmp = clone $node;
$tmp->definitions = [];
- $tmp->didEnter = true;
+ $tmp->didEnter = true;
return $tmp;
},
- 'leave' => function(DocumentNode $node) use ($definitions, $ast) {
+ 'leave' => function (DocumentNode $node) use ($definitions, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
- $tmp = clone $node;
$node->definitions = $definitions;
- $node->didLeave = true;
- }
- ]
+ $node->didLeave = true;
+ },
+ ],
]);
$this->assertNotEquals($ast, $editedAst);
- $tmp = $ast->cloneDeep();
+ $tmp = $ast->cloneDeep();
$tmp->didEnter = true;
$tmp->didLeave = true;
$this->assertEquals($tmp, $editedAst);
}
- /**
- * @it allows for editing on enter
- */
public function testAllowsForEditingOnEnter()
{
- $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
+ $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
$editedAst = Visitor::visit($ast, [
- 'enter' => function($node) use ($ast) {
+ 'enter' => function ($node) use ($ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
if ($node instanceof FieldNode && $node->name->value === 'b') {
return Visitor::removeNode();
}
- }
+ },
]);
$this->assertEquals(
@@ -204,19 +207,16 @@ class VisitorTest extends ValidatorTestCase
);
}
- /**
- * @it allows for editing on leave
- */
public function testAllowsForEditingOnLeave()
{
- $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
+ $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
$editedAst = Visitor::visit($ast, [
- 'leave' => function($node) use ($ast) {
+ 'leave' => function ($node) use ($ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
if ($node instanceof FieldNode && $node->name->value === 'b') {
return Visitor::removeNode();
}
- }
+ },
]);
$this->assertEquals(
@@ -230,60 +230,54 @@ class VisitorTest extends ValidatorTestCase
);
}
- /**
- * @it visits edited node
- */
public function testVisitsEditedNode()
{
- $addedField = new FieldNode(array(
- 'name' => new NameNode(array(
- 'value' => '__typename'
- ))
- ));
+ $addedField = new FieldNode([
+ 'name' => new NameNode(['value' => '__typename']),
+ ]);
$didVisitAddedField = false;
$ast = Parser::parse('{ a { x } }', ['noLocation' => true]);
Visitor::visit($ast, [
- 'enter' => function($node) use ($addedField, &$didVisitAddedField, $ast) {
+ 'enter' => function ($node) use ($addedField, &$didVisitAddedField, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
if ($node instanceof FieldNode && $node->name->value === 'a') {
return new FieldNode([
- 'selectionSet' => new SelectionSetNode(array(
- 'selections' => NodeList::create([$addedField])->merge($node->selectionSet->selections)
- ))
+ 'selectionSet' => new SelectionSetNode([
+ 'selections' => NodeList::create([$addedField])->merge($node->selectionSet->selections),
+ ]),
]);
}
- if ($node === $addedField) {
- $didVisitAddedField = true;
+ if ($node !== $addedField) {
+ return;
}
- }
+
+ $didVisitAddedField = true;
+ },
]);
$this->assertTrue($didVisitAddedField);
}
- /**
- * @it allows skipping a sub-tree
- */
public function testAllowsSkippingASubTree()
{
$visited = [];
- $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
+ $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
Visitor::visit($ast, [
- 'enter' => function(Node $node) use (&$visited, $ast) {
+ 'enter' => function (Node $node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['enter', $node->kind, $node->value ?? null];
if ($node instanceof FieldNode && $node->name->value === 'b') {
return Visitor::skipNode();
}
},
'leave' => function (Node $node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null];
- }
+ $visited[] = ['leave', $node->kind, $node->value ?? null];
+ },
]);
$expected = [
@@ -301,32 +295,29 @@ class VisitorTest extends ValidatorTestCase
[ 'leave', 'Field', null ],
[ 'leave', 'SelectionSet', null ],
[ 'leave', 'OperationDefinition', null ],
- [ 'leave', 'Document', null ]
+ [ 'leave', 'Document', null ],
];
$this->assertEquals($expected, $visited);
}
- /**
- * @it allows early exit while visiting
- */
public function testAllowsEarlyExitWhileVisiting()
{
$visited = [];
- $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
+ $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
Visitor::visit($ast, [
- 'enter' => function(Node $node) use (&$visited, $ast) {
+ 'enter' => function (Node $node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['enter', $node->kind, $node->value ?? null];
if ($node instanceof NameNode && $node->value === 'x') {
return Visitor::stop();
}
},
- 'leave' => function(Node $node) use (&$visited, $ast) {
+ 'leave' => function (Node $node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null];
- }
+ $visited[] = ['leave', $node->kind, $node->value ?? null];
+ },
]);
$expected = [
@@ -342,33 +333,30 @@ class VisitorTest extends ValidatorTestCase
[ 'leave', 'Name', 'b' ],
[ 'enter', 'SelectionSet', null ],
[ 'enter', 'Field', null ],
- [ 'enter', 'Name', 'x' ]
+ [ 'enter', 'Name', 'x' ],
];
$this->assertEquals($expected, $visited);
}
- /**
- * @it allows early exit while leaving
- */
public function testAllowsEarlyExitWhileLeaving()
{
$visited = [];
$ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
Visitor::visit($ast, [
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['enter', $node->kind, $node->value ?? null];
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['leave', $node->kind, $node->value ?? null];
if ($node->kind === NodeKind::NAME && $node->value === 'x') {
return Visitor::stop();
}
- }
+ },
]);
$this->assertEquals($visited, [
@@ -385,33 +373,30 @@ class VisitorTest extends ValidatorTestCase
[ 'enter', 'SelectionSet', null ],
[ 'enter', 'Field', null ],
[ 'enter', 'Name', 'x' ],
- [ 'leave', 'Name', 'x' ]
+ [ 'leave', 'Name', 'x' ],
]);
}
- /**
- * @it allows a named functions visitor API
- */
public function testAllowsANamedFunctionsVisitorAPI()
{
$visited = [];
- $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
+ $ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
Visitor::visit($ast, [
- NodeKind::NAME => function(NameNode $node) use (&$visited, $ast) {
+ NodeKind::NAME => function (NameNode $node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['enter', $node->kind, $node->value];
},
NodeKind::SELECTION_SET => [
- 'enter' => function(SelectionSetNode $node) use (&$visited, $ast) {
+ 'enter' => function (SelectionSetNode $node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['enter', $node->kind, null];
},
- 'leave' => function(SelectionSetNode $node) use (&$visited, $ast) {
+ 'leave' => function (SelectionSetNode $node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['leave', $node->kind, null];
- }
- ]
+ },
+ ],
]);
$expected = [
@@ -428,12 +413,9 @@ class VisitorTest extends ValidatorTestCase
$this->assertEquals($expected, $visited);
}
- /**
- * @it Experimental: visits variables defined in fragments
- */
public function testExperimentalVisitsVariablesDefinedInFragments()
{
- $ast = Parser::parse(
+ $ast = Parser::parse(
'fragment a($v: Boolean = false) on t { f }',
[
'noLocation' => true,
@@ -443,13 +425,13 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
Visitor::visit($ast, [
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['enter', $node->kind, $node->value ?? null];
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['leave', $node->kind, $node->value ?? null];
},
]);
@@ -487,26 +469,23 @@ class VisitorTest extends ValidatorTestCase
$this->assertEquals($expected, $visited);
}
- /**
- * @it visits kitchen sink
- */
public function testVisitsKitchenSink()
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
- $ast = Parser::parse($kitchenSink);
+ $ast = Parser::parse($kitchenSink);
$visited = [];
Visitor::visit($ast, [
- 'enter' => function(Node $node, $key, $parent) use (&$visited, $ast) {
+ 'enter' => function (Node $node, $key, $parent) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $r = ['enter', $node->kind, $key, $parent instanceof Node ? $parent->kind : null];
+ $r = ['enter', $node->kind, $key, $parent instanceof Node ? $parent->kind : null];
$visited[] = $r;
},
- 'leave' => function(Node $node, $key, $parent) use (&$visited, $ast) {
+ 'leave' => function (Node $node, $key, $parent) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $r = ['leave', $node->kind, $key, $parent instanceof Node ? $parent->kind : null];
+ $r = ['leave', $node->kind, $key, $parent instanceof Node ? $parent->kind : null];
$visited[] = $r;
- }
+ },
]);
$expected = [
@@ -819,17 +798,15 @@ class VisitorTest extends ValidatorTestCase
[ 'leave', 'Field', 1, null ],
[ 'leave', 'SelectionSet', 'selectionSet', 'OperationDefinition' ],
[ 'leave', 'OperationDefinition', 4, null ],
- [ 'leave', 'Document', null, null ]
+ [ 'leave', 'Document', null, null ],
];
$this->assertEquals($expected, $visited);
}
- // Describe: visitInParallel
- // Note: nearly identical to the above test of the same test but using visitInParallel.
-
/**
- * @it allows skipping a sub-tree
+ * Describe: visitInParallel
+ * Note: nearly identical to the above test of the same test but using visitInParallel.
*/
public function testAllowsSkippingSubTree()
{
@@ -838,20 +815,20 @@ class VisitorTest extends ValidatorTestCase
$ast = Parser::parse('{ a, b { x }, c }');
Visitor::visit($ast, Visitor::visitInParallel([
[
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = [ 'enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = [ 'enter', $node->kind, $node->value ?? null];
if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
return Visitor::skipNode();
}
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null];
- }
- ]
+ $visited[] = ['leave', $node->kind, $node->value ?? null];
+ },
+ ],
]));
$this->assertEquals([
@@ -873,9 +850,6 @@ class VisitorTest extends ValidatorTestCase
], $visited);
}
- /**
- * @it allows skipping different sub-trees
- */
public function testAllowsSkippingDifferentSubTrees()
{
$visited = [];
@@ -883,31 +857,31 @@ class VisitorTest extends ValidatorTestCase
$ast = Parser::parse('{ a { x }, b { y} }');
Visitor::visit($ast, Visitor::visitInParallel([
[
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['no-a', 'enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['no-a', 'enter', $node->kind, $node->value ?? null];
if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') {
return Visitor::skipNode();
}
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = [ 'no-a', 'leave', $node->kind, isset($node->value) ? $node->value : null ];
- }
+ $visited[] = [ 'no-a', 'leave', $node->kind, $node->value ?? null ];
+ },
],
[
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['no-b', 'enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['no-b', 'enter', $node->kind, $node->value ?? null];
if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
return Visitor::skipNode();
}
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['no-b', 'leave', $node->kind, isset($node->value) ? $node->value : null];
- }
- ]
+ $visited[] = ['no-b', 'leave', $node->kind, $node->value ?? null];
+ },
+ ],
]));
$this->assertEquals([
@@ -948,28 +922,26 @@ class VisitorTest extends ValidatorTestCase
], $visited);
}
- /**
- * @it allows early exit while visiting
- */
public function testAllowsEarlyExitWhileVisiting2()
{
$visited = [];
$ast = Parser::parse('{ a, b { x }, c }');
Visitor::visit($ast, Visitor::visitInParallel([ [
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $value = isset($node->value) ? $node->value : null;
+ $value = $node->value ?? null;
$visited[] = ['enter', $node->kind, $value];
if ($node->kind === 'Name' && $value === 'x') {
return Visitor::stop();
}
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null];
- }
- ] ]));
+ $visited[] = ['leave', $node->kind, $node->value ?? null];
+ },
+ ],
+ ]));
$this->assertEquals([
[ 'enter', 'Document', null ],
@@ -984,13 +956,10 @@ class VisitorTest extends ValidatorTestCase
[ 'leave', 'Name', 'b' ],
[ 'enter', 'SelectionSet', null ],
[ 'enter', 'Field', null ],
- [ 'enter', 'Name', 'x' ]
+ [ 'enter', 'Name', 'x' ],
], $visited);
}
- /**
- * @it allows early exit from different points
- */
public function testAllowsEarlyExitFromDifferentPoints()
{
$visited = [];
@@ -998,32 +967,32 @@ class VisitorTest extends ValidatorTestCase
$ast = Parser::parse('{ a { y }, b { x } }');
Visitor::visit($ast, Visitor::visitInParallel([
[
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $value = isset($node->value) ? $node->value : null;
+ $value = $node->value ?? null;
$visited[] = ['break-a', 'enter', $node->kind, $value];
if ($node->kind === 'Name' && $value === 'a') {
return Visitor::stop();
}
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = [ 'break-a', 'leave', $node->kind, isset($node->value) ? $node->value : null ];
- }
+ $visited[] = [ 'break-a', 'leave', $node->kind, $node->value ?? null ];
+ },
],
[
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $value = isset($node->value) ? $node->value : null;
+ $value = $node->value ?? null;
$visited[] = ['break-b', 'enter', $node->kind, $value];
if ($node->kind === 'Name' && $value === 'b') {
return Visitor::stop();
}
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['break-b', 'leave', $node->kind, isset($node->value) ? $node->value : null];
- }
+ $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null];
+ },
],
]));
@@ -1047,32 +1016,30 @@ class VisitorTest extends ValidatorTestCase
[ 'break-b', 'leave', 'SelectionSet', null ],
[ 'break-b', 'leave', 'Field', null ],
[ 'break-b', 'enter', 'Field', null ],
- [ 'break-b', 'enter', 'Name', 'b' ]
+ [ 'break-b', 'enter', 'Name', 'b' ],
], $visited);
}
- /**
- * @it allows early exit while leaving
- */
public function testAllowsEarlyExitWhileLeaving2()
{
$visited = [];
$ast = Parser::parse('{ a, b { x }, c }');
Visitor::visit($ast, Visitor::visitInParallel([ [
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['enter', $node->kind, $node->value ?? null];
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $value = isset($node->value) ? $node->value : null;
+ $value = $node->value ?? null;
$visited[] = ['leave', $node->kind, $value];
if ($node->kind === 'Name' && $value === 'x') {
return Visitor::stop();
}
- }
- ] ]));
+ },
+ ],
+ ]));
$this->assertEquals([
[ 'enter', 'Document', null ],
@@ -1088,13 +1055,10 @@ class VisitorTest extends ValidatorTestCase
[ 'enter', 'SelectionSet', null ],
[ 'enter', 'Field', null ],
[ 'enter', 'Name', 'x' ],
- [ 'leave', 'Name', 'x' ]
+ [ 'leave', 'Name', 'x' ],
], $visited);
}
- /**
- * @it allows early exit from leaving different points
- */
public function testAllowsEarlyExitFromLeavingDifferentPoints()
{
$visited = [];
@@ -1102,30 +1066,30 @@ class VisitorTest extends ValidatorTestCase
$ast = Parser::parse('{ a { y }, b { x } }');
Visitor::visit($ast, Visitor::visitInParallel([
[
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['break-a', 'enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['break-a', 'enter', $node->kind, $node->value ?? null];
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['break-a', 'leave', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['break-a', 'leave', $node->kind, $node->value ?? null];
if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') {
return Visitor::stop();
}
- }
+ },
],
[
- 'enter' => function($node) use (&$visited, $ast) {
+ 'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['break-b', 'enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['break-b', 'enter', $node->kind, $node->value ?? null];
},
- 'leave' => function($node) use (&$visited, $ast) {
+ 'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['break-b', 'leave', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null];
if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
return Visitor::stop();
}
- }
+ },
],
]));
@@ -1165,18 +1129,15 @@ class VisitorTest extends ValidatorTestCase
[ 'break-b', 'leave', 'Name', 'x' ],
[ 'break-b', 'leave', 'Field', null ],
[ 'break-b', 'leave', 'SelectionSet', null ],
- [ 'break-b', 'leave', 'Field', null ]
+ [ 'break-b', 'leave', 'Field', null ],
], $visited);
}
- /**
- * @it allows for editing on enter
- */
public function testAllowsForEditingOnEnter2()
{
$visited = [];
- $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
+ $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
$editedAst = Visitor::visit($ast, Visitor::visitInParallel([
[
'enter' => function ($node) use (&$visited, $ast) {
@@ -1184,17 +1145,17 @@ class VisitorTest extends ValidatorTestCase
if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
return Visitor::removeNode();
}
- }
+ },
],
[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['enter', $node->kind, $node->value ?? null];
},
'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
- $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null];
- }
+ $visited[] = ['leave', $node->kind, $node->value ?? null];
+ },
],
]));
@@ -1232,18 +1193,15 @@ class VisitorTest extends ValidatorTestCase
['leave', 'Field', null],
['leave', 'SelectionSet', null],
['leave', 'OperationDefinition', null],
- ['leave', 'Document', null]
+ ['leave', 'Document', null],
], $visited);
}
- /**
- * @it allows for editing on leave
- */
public function testAllowsForEditingOnLeave2()
{
$visited = [];
- $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
+ $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
$editedAst = Visitor::visit($ast, Visitor::visitInParallel([
[
'leave' => function ($node) use (&$visited, $ast) {
@@ -1251,17 +1209,17 @@ class VisitorTest extends ValidatorTestCase
if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') {
return Visitor::removeNode();
}
- }
+ },
],
[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
- $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null];
+ $visited[] = ['enter', $node->kind, $node->value ?? null];
},
'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
- $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null];
- }
+ $visited[] = ['leave', $node->kind, $node->value ?? null];
+ },
],
]));
@@ -1305,14 +1263,13 @@ class VisitorTest extends ValidatorTestCase
['leave', 'Field', null],
['leave', 'SelectionSet', null],
['leave', 'OperationDefinition', null],
- ['leave', 'Document', null]
+ ['leave', 'Document', null],
], $visited);
}
- // Describe: visitWithTypeInfo
/**
- * @it maintains type info during visit
+ * Describe: visitWithTypeInfo
*/
public function testMaintainsTypeInfoDuringVisit()
{
@@ -1325,31 +1282,31 @@ class VisitorTest extends ValidatorTestCase
'enter' => function ($node) use ($typeInfo, &$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$parentType = $typeInfo->getParentType();
- $type = $typeInfo->getType();
- $inputType = $typeInfo->getInputType();
- $visited[] = [
+ $type = $typeInfo->getType();
+ $inputType = $typeInfo->getInputType();
+ $visited[] = [
'enter',
$node->kind,
$node->kind === 'Name' ? $node->value : null,
- $parentType ? (string)$parentType : null,
- $type ? (string)$type : null,
- $inputType ? (string)$inputType : null
+ $parentType ? (string) $parentType : null,
+ $type ? (string) $type : null,
+ $inputType ? (string) $inputType : null,
];
},
'leave' => function ($node) use ($typeInfo, &$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$parentType = $typeInfo->getParentType();
- $type = $typeInfo->getType();
- $inputType = $typeInfo->getInputType();
- $visited[] = [
+ $type = $typeInfo->getType();
+ $inputType = $typeInfo->getInputType();
+ $visited[] = [
'leave',
$node->kind,
$node->kind === 'Name' ? $node->value : null,
- $parentType ? (string)$parentType : null,
- $type ? (string)$type : null,
- $inputType ? (string)$inputType : null
+ $parentType ? (string) $parentType : null,
+ $type ? (string) $type : null,
+ $inputType ? (string) $inputType : null,
];
- }
+ },
]));
$this->assertEquals([
@@ -1392,40 +1349,36 @@ class VisitorTest extends ValidatorTestCase
['leave', 'Field', null, 'QueryRoot', 'Human', null],
['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null],
['leave', 'OperationDefinition', null, null, 'QueryRoot', null],
- ['leave', 'Document', null, null, null, null]
+ ['leave', 'Document', null, null, null, null],
], $visited);
}
- /**
- * @it maintains type info during edit
- */
public function testMaintainsTypeInfoDuringEdit()
{
- $visited = [];
+ $visited = [];
$typeInfo = new TypeInfo(ValidatorTestCase::getTestSchema());
- $ast = Parser::parse(
+ $ast = Parser::parse(
'{ human(id: 4) { name, pets }, alien }'
);
$editedAst = Visitor::visit($ast, Visitor::visitWithTypeInfo($typeInfo, [
'enter' => function ($node) use ($typeInfo, &$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
$parentType = $typeInfo->getParentType();
- $type = $typeInfo->getType();
- $inputType = $typeInfo->getInputType();
- $visited[] = [
+ $type = $typeInfo->getType();
+ $inputType = $typeInfo->getInputType();
+ $visited[] = [
'enter',
$node->kind,
$node->kind === 'Name' ? $node->value : null,
- $parentType ? (string)$parentType : null,
- $type ? (string)$type : null,
- $inputType ? (string)$inputType : null
+ $parentType ? (string) $parentType : null,
+ $type ? (string) $type : null,
+ $inputType ? (string) $inputType : null,
];
// Make a query valid by adding missing selection sets.
- if (
- $node->kind === 'Field' &&
- !$node->selectionSet &&
+ if ($node->kind === 'Field' &&
+ ! $node->selectionSet &&
Type::isCompositeType(Type::getNamedType($type))
) {
return new FieldNode([
@@ -1435,29 +1388,28 @@ class VisitorTest extends ValidatorTestCase
'directives' => $node->directives,
'selectionSet' => new SelectionSetNode([
'kind' => 'SelectionSet',
- 'selections' => [
- new FieldNode([
- 'name' => new NameNode(['value' => '__typename'])
- ])
- ]
- ])
+ 'selections' => [new FieldNode([
+ 'name' => new NameNode(['value' => '__typename']),
+ ]),
+ ],
+ ]),
]);
}
},
'leave' => function ($node) use ($typeInfo, &$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
$parentType = $typeInfo->getParentType();
- $type = $typeInfo->getType();
- $inputType = $typeInfo->getInputType();
- $visited[] = [
+ $type = $typeInfo->getType();
+ $inputType = $typeInfo->getInputType();
+ $visited[] = [
'leave',
$node->kind,
$node->kind === 'Name' ? $node->value : null,
- $parentType ? (string)$parentType : null,
- $type ? (string)$type : null,
- $inputType ? (string)$inputType : null
+ $parentType ? (string) $parentType : null,
+ $type ? (string) $type : null,
+ $inputType ? (string) $inputType : null,
];
- }
+ },
]));
$this->assertEquals(Printer::doPrint(Parser::parse(
@@ -1510,7 +1462,7 @@ class VisitorTest extends ValidatorTestCase
['leave', 'Field', null, 'QueryRoot', 'Alien', null],
['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null],
['leave', 'OperationDefinition', null, null, 'QueryRoot', null],
- ['leave', 'Document', null, null, null, null]
+ ['leave', 'Document', null, null, null, null],
], $visited);
}
}
diff --git a/tests/Validator/QuerySecurityTestCase.php b/tests/Validator/QuerySecurityTestCase.php
index cfdb29a..9bb07aa 100644
--- a/tests/Validator/QuerySecurityTestCase.php
+++ b/tests/Validator/QuerySecurityTestCase.php
@@ -5,7 +5,7 @@ use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser;
use GraphQL\Type\Introspection;
use GraphQL\Validator\DocumentValidator;
-use GraphQL\Validator\Rules\AbstractQuerySecurity;
+use GraphQL\Validator\Rules\QuerySecurityRule;
use PHPUnit\Framework\TestCase;
abstract class QuerySecurityTestCase extends TestCase
@@ -13,7 +13,7 @@ abstract class QuerySecurityTestCase extends TestCase
/**
* @param $max
*
- * @return AbstractQuerySecurity
+ * @return QuerySecurityRule
*/
abstract protected function getRule($max);
@@ -89,8 +89,9 @@ abstract class QuerySecurityTestCase extends TestCase
{
$this->assertDocumentValidator($query, $maxExpected);
$newMax = $maxExpected - 1;
- if ($newMax !== AbstractQuerySecurity::DISABLED) {
- $this->assertDocumentValidator($query, $newMax, [$this->createFormattedError($newMax, $maxExpected)]);
+ if ($newMax === QuerySecurityRule::DISABLED) {
+ return;
}
+ $this->assertDocumentValidator($query, $newMax, [$this->createFormattedError($newMax, $maxExpected)]);
}
}