mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 21:06:05 +03:00
AST: new NodeList
class for collections of nodes (vs array) to enable effective conversion of libgraphqlparser output to our AST tree
This commit is contained in:
parent
e04d3300a7
commit
1af902865b
@ -11,6 +11,7 @@ use GraphQL\Language\AST\FieldDefinitionNode;
|
|||||||
use GraphQL\Language\AST\FieldNode;
|
use GraphQL\Language\AST\FieldNode;
|
||||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||||
use GraphQL\Language\AST\InlineFragmentNode;
|
use GraphQL\Language\AST\InlineFragmentNode;
|
||||||
|
use GraphQL\Language\AST\NodeList;
|
||||||
use GraphQL\Language\AST\VariableNode;
|
use GraphQL\Language\AST\VariableNode;
|
||||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||||
use GraphQL\Language\Printer;
|
use GraphQL\Language\Printer;
|
||||||
@ -180,7 +181,7 @@ class Values
|
|||||||
*/
|
*/
|
||||||
public static function getDirectiveValues(Directive $directiveDef, $node, $variableValues = null)
|
public static function getDirectiveValues(Directive $directiveDef, $node, $variableValues = null)
|
||||||
{
|
{
|
||||||
if (isset($node->directives) && is_array($node->directives)) {
|
if (isset($node->directives) && $node->directives instanceof NodeList) {
|
||||||
$directiveNode = Utils::find($node->directives, function(DirectiveNode $directive) use ($directiveDef) {
|
$directiveNode = Utils::find($node->directives, function(DirectiveNode $directive) use ($directiveDef) {
|
||||||
return $directive->name->value === $directiveDef->name;
|
return $directive->name->value === $directiveDef->name;
|
||||||
});
|
});
|
||||||
|
@ -45,12 +45,28 @@ class Location
|
|||||||
*/
|
*/
|
||||||
public $source;
|
public $source;
|
||||||
|
|
||||||
public function __construct(Token $startToken, Token $endToken, Source $source = null)
|
/**
|
||||||
|
* @param $start
|
||||||
|
* @param $end
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function create($start, $end)
|
||||||
|
{
|
||||||
|
$tmp = new static();
|
||||||
|
$tmp->start = $start;
|
||||||
|
$tmp->end = $end;
|
||||||
|
return $tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(Token $startToken = null, Token $endToken = null, Source $source = null)
|
||||||
{
|
{
|
||||||
$this->startToken = $startToken;
|
$this->startToken = $startToken;
|
||||||
$this->endToken = $endToken;
|
$this->endToken = $endToken;
|
||||||
|
$this->source = $source;
|
||||||
|
|
||||||
|
if ($startToken && $endToken) {
|
||||||
$this->start = $startToken->start;
|
$this->start = $startToken->start;
|
||||||
$this->end = $endToken->end;
|
$this->end = $endToken->end;
|
||||||
$this->source = $source;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
|
use GraphQL\Error\InvariantViolation;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
|
|
||||||
abstract class Node
|
abstract class Node
|
||||||
@ -37,13 +38,70 @@ abstract class Node
|
|||||||
*/
|
*/
|
||||||
public $loc;
|
public $loc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts representation of AST as associative array to Node instance.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* ```php
|
||||||
|
* Node::fromArray([
|
||||||
|
* 'kind' => 'ListValue',
|
||||||
|
* 'values' => [
|
||||||
|
* ['kind' => 'StringValue', 'value' => 'my str'],
|
||||||
|
* ['kind' => 'StringValue', 'value' => 'my other str']
|
||||||
|
* ],
|
||||||
|
* 'loc' => ['start' => 21, 'end' => 25]
|
||||||
|
* ]);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Will produce instance of `ListValueNode` where `values` prop is a lazily-evaluated `NodeList`
|
||||||
|
* returning instances of `StringValueNode` on access.
|
||||||
|
*
|
||||||
|
* This is a reverse operation for $node->toArray(true)
|
||||||
|
*
|
||||||
|
* @param array $node
|
||||||
|
* @return EnumValueDefinitionNode
|
||||||
|
*/
|
||||||
|
public static function fromArray(array $node)
|
||||||
|
{
|
||||||
|
if (!isset($node['kind']) || !isset(NodeKind::$classMap[$node['kind']])) {
|
||||||
|
throw new InvariantViolation("Unexpected node structure: " . Utils::printSafeJson($node));
|
||||||
|
}
|
||||||
|
|
||||||
|
$kind = isset($node['kind']) ? $node['kind'] : null;
|
||||||
|
$class = NodeKind::$classMap[$kind];
|
||||||
|
$instance = new $class([]);
|
||||||
|
|
||||||
|
if (isset($node['loc'], $node['loc']['start'], $node['loc']['end'])) {
|
||||||
|
$instance->loc = Location::create($node['loc']['start'], $node['loc']['end']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($node as $key => $value) {
|
||||||
|
if ('loc' === $key || 'kind' === $key) {
|
||||||
|
continue ;
|
||||||
|
}
|
||||||
|
if (is_array($value)) {
|
||||||
|
if (isset($value[0]) || empty($value)) {
|
||||||
|
$value = new NodeList($value);
|
||||||
|
} else {
|
||||||
|
$value = self::fromArray($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$instance->{$key} = $value;
|
||||||
|
}
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $vars
|
* @param array $vars
|
||||||
*/
|
*/
|
||||||
public function __construct(array $vars)
|
public function __construct(array $vars)
|
||||||
{
|
{
|
||||||
|
if (!empty($vars)) {
|
||||||
Utils::assign($this, $vars);
|
Utils::assign($this, $vars);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return $this
|
* @return $this
|
||||||
@ -91,6 +149,9 @@ abstract class Node
|
|||||||
*/
|
*/
|
||||||
public function toArray($recursive = false)
|
public function toArray($recursive = false)
|
||||||
{
|
{
|
||||||
|
if ($recursive) {
|
||||||
|
return $this->recursiveToArray($this);
|
||||||
|
} else {
|
||||||
$tmp = (array) $this;
|
$tmp = (array) $this;
|
||||||
|
|
||||||
$tmp['loc'] = [
|
$tmp['loc'] = [
|
||||||
@ -98,27 +159,43 @@ abstract class Node
|
|||||||
'end' => $this->loc->end
|
'end' => $this->loc->end
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($recursive) {
|
|
||||||
$this->recursiveToArray($tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tmp;
|
return $tmp;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $object
|
* @param Node $node
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function recursiveToArray(&$object)
|
private function recursiveToArray(Node $node)
|
||||||
{
|
{
|
||||||
if ($object instanceof Node) {
|
$result = [
|
||||||
/** @var Node $object */
|
'kind' => $node->kind,
|
||||||
$object = $object->toArray(true);
|
'loc' => [
|
||||||
} elseif (is_object($object)) {
|
'start' => $node->loc->start,
|
||||||
$object = (array) $object;
|
'end' => $node->loc->end
|
||||||
} elseif (is_array($object)) {
|
]
|
||||||
foreach ($object as &$o) {
|
];
|
||||||
$this->recursiveToArray($o);
|
|
||||||
|
foreach (get_object_vars($node) as $prop => $propValue) {
|
||||||
|
if (isset($result[$prop]))
|
||||||
|
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) {
|
||||||
|
$tmp = $this->recursiveToArray($propValue);
|
||||||
|
} else if (is_scalar($propValue) || null === $propValue) {
|
||||||
|
$tmp = $propValue;
|
||||||
|
} else {
|
||||||
|
$tmp = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$result[$prop] = $tmp;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,4 +70,66 @@ class NodeKind
|
|||||||
// Directive Definitions
|
// Directive Definitions
|
||||||
|
|
||||||
const DIRECTIVE_DEFINITION = 'DirectiveDefinition';
|
const DIRECTIVE_DEFINITION = 'DirectiveDefinition';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo conver to const array when moving to PHP5.6
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $classMap = [
|
||||||
|
NodeKind::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,
|
||||||
|
|
||||||
|
// Fragments
|
||||||
|
NodeKind::FRAGMENT_SPREAD => FragmentSpreadNode::class,
|
||||||
|
NodeKind::INLINE_FRAGMENT => InlineFragmentNode::class,
|
||||||
|
NodeKind::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,
|
||||||
|
|
||||||
|
// Directives
|
||||||
|
NodeKind::DIRECTIVE => DirectiveNode::class,
|
||||||
|
|
||||||
|
// Types
|
||||||
|
NodeKind::NAMED_TYPE => NamedTypeNode::class,
|
||||||
|
NodeKind::LIST_TYPE => ListTypeNode::class,
|
||||||
|
NodeKind::NON_NULL_TYPE => NonNullTypeNode::class,
|
||||||
|
|
||||||
|
// Type System Definitions
|
||||||
|
NodeKind::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
|
||||||
|
NodeKind::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,
|
||||||
|
|
||||||
|
// Type Extensions
|
||||||
|
NodeKind::TYPE_EXTENSION_DEFINITION => TypeExtensionDefinitionNode::class,
|
||||||
|
|
||||||
|
// Directive Definitions
|
||||||
|
NodeKind::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
119
src/Language/AST/NodeList.php
Normal file
119
src/Language/AST/NodeList.php
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class NodeList
|
||||||
|
*
|
||||||
|
* @package GraphQL\Utils
|
||||||
|
*/
|
||||||
|
class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $nodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $nodes
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function create(array $nodes)
|
||||||
|
{
|
||||||
|
return new static($nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NodeList constructor.
|
||||||
|
* @param array $nodes
|
||||||
|
*/
|
||||||
|
public function __construct(array $nodes)
|
||||||
|
{
|
||||||
|
$this->nodes = $nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $offset
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function offsetExists($offset)
|
||||||
|
{
|
||||||
|
return isset($this->nodes[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $offset
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function offsetGet($offset)
|
||||||
|
{
|
||||||
|
$item = $this->nodes[$offset];
|
||||||
|
|
||||||
|
if (is_array($item) && isset($item['kind'])) {
|
||||||
|
$this->nodes[$offset] = $item = Node::fromArray($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $offset
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function offsetSet($offset, $value)
|
||||||
|
{
|
||||||
|
if (is_array($value) && isset($value['kind'])) {
|
||||||
|
$value = Node::fromArray($value);
|
||||||
|
}
|
||||||
|
$this->nodes[$offset] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $offset
|
||||||
|
*/
|
||||||
|
public function offsetUnset($offset)
|
||||||
|
{
|
||||||
|
unset($this->nodes[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $offset
|
||||||
|
* @param int $length
|
||||||
|
* @param mixed $replacement
|
||||||
|
* @return NodeList
|
||||||
|
*/
|
||||||
|
public function splice($offset, $length, $replacement = null)
|
||||||
|
{
|
||||||
|
return new NodeList(array_splice($this->nodes, $offset, $length, $replacement));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $list
|
||||||
|
* @return NodeList
|
||||||
|
*/
|
||||||
|
public function merge($list)
|
||||||
|
{
|
||||||
|
if ($list instanceof NodeList) {
|
||||||
|
$list = $list->nodes;
|
||||||
|
}
|
||||||
|
return new NodeList(array_merge($this->nodes, $list));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Generator
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
$count = count($this->nodes);
|
||||||
|
for ($i = 0; $i < $count; $i++) {
|
||||||
|
yield $this->offsetGet($i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return count($this->nodes);
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ use GraphQL\Language\AST\ListTypeNode;
|
|||||||
use GraphQL\Language\AST\Location;
|
use GraphQL\Language\AST\Location;
|
||||||
use GraphQL\Language\AST\NameNode;
|
use GraphQL\Language\AST\NameNode;
|
||||||
use GraphQL\Language\AST\NamedTypeNode;
|
use GraphQL\Language\AST\NamedTypeNode;
|
||||||
|
use GraphQL\Language\AST\NodeList;
|
||||||
use GraphQL\Language\AST\NonNullTypeNode;
|
use GraphQL\Language\AST\NonNullTypeNode;
|
||||||
use GraphQL\Language\AST\NullValueNode;
|
use GraphQL\Language\AST\NullValueNode;
|
||||||
use GraphQL\Language\AST\ObjectFieldNode;
|
use GraphQL\Language\AST\ObjectFieldNode;
|
||||||
@ -248,7 +249,7 @@ class Parser
|
|||||||
while (!$this->skip($closeKind)) {
|
while (!$this->skip($closeKind)) {
|
||||||
$nodes[] = $parseFn($this);
|
$nodes[] = $parseFn($this);
|
||||||
}
|
}
|
||||||
return $nodes;
|
return new NodeList($nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -260,7 +261,7 @@ class Parser
|
|||||||
* @param $openKind
|
* @param $openKind
|
||||||
* @param $parseFn
|
* @param $parseFn
|
||||||
* @param $closeKind
|
* @param $closeKind
|
||||||
* @return array
|
* @return NodeList
|
||||||
* @throws SyntaxError
|
* @throws SyntaxError
|
||||||
*/
|
*/
|
||||||
function many($openKind, $parseFn, $closeKind)
|
function many($openKind, $parseFn, $closeKind)
|
||||||
@ -271,7 +272,7 @@ class Parser
|
|||||||
while (!$this->skip($closeKind)) {
|
while (!$this->skip($closeKind)) {
|
||||||
$nodes[] = $parseFn($this);
|
$nodes[] = $parseFn($this);
|
||||||
}
|
}
|
||||||
return $nodes;
|
return new NodeList($nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -307,7 +308,7 @@ class Parser
|
|||||||
} while (!$this->skip(Token::EOF));
|
} while (!$this->skip(Token::EOF));
|
||||||
|
|
||||||
return new DocumentNode([
|
return new DocumentNode([
|
||||||
'definitions' => $definitions,
|
'definitions' => new NodeList($definitions),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -363,7 +364,7 @@ class Parser
|
|||||||
'operation' => 'query',
|
'operation' => 'query',
|
||||||
'name' => null,
|
'name' => null,
|
||||||
'variableDefinitions' => null,
|
'variableDefinitions' => null,
|
||||||
'directives' => [],
|
'directives' => new NodeList([]),
|
||||||
'selectionSet' => $this->parseSelectionSet(),
|
'selectionSet' => $this->parseSelectionSet(),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
]);
|
]);
|
||||||
@ -414,7 +415,7 @@ class Parser
|
|||||||
[$this, 'parseVariableDefinition'],
|
[$this, 'parseVariableDefinition'],
|
||||||
Token::PAREN_R
|
Token::PAREN_R
|
||||||
) :
|
) :
|
||||||
[];
|
new NodeList([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -513,7 +514,7 @@ class Parser
|
|||||||
{
|
{
|
||||||
return $this->peek(Token::PAREN_L) ?
|
return $this->peek(Token::PAREN_L) ?
|
||||||
$this->many(Token::PAREN_L, [$this, 'parseArgument'], Token::PAREN_R) :
|
$this->many(Token::PAREN_L, [$this, 'parseArgument'], Token::PAREN_R) :
|
||||||
[];
|
new NodeList([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -726,7 +727,7 @@ class Parser
|
|||||||
$fields[] = $this->parseObjectField($isConst);
|
$fields[] = $this->parseObjectField($isConst);
|
||||||
}
|
}
|
||||||
return new ObjectValueNode([
|
return new ObjectValueNode([
|
||||||
'fields' => $fields,
|
'fields' => new NodeList($fields),
|
||||||
'loc' => $this->loc($start)
|
'loc' => $this->loc($start)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -760,7 +761,7 @@ class Parser
|
|||||||
while ($this->peek(Token::AT)) {
|
while ($this->peek(Token::AT)) {
|
||||||
$directives[] = $this->parseDirective();
|
$directives[] = $this->parseDirective();
|
||||||
}
|
}
|
||||||
return $directives;
|
return new NodeList($directives);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,6 +38,7 @@ use GraphQL\Language\AST\StringValueNode;
|
|||||||
use GraphQL\Language\AST\TypeExtensionDefinitionNode;
|
use GraphQL\Language\AST\TypeExtensionDefinitionNode;
|
||||||
use GraphQL\Language\AST\UnionTypeDefinitionNode;
|
use GraphQL\Language\AST\UnionTypeDefinitionNode;
|
||||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||||
|
use GraphQL\Utils\Utils;
|
||||||
|
|
||||||
class Printer
|
class Printer
|
||||||
{
|
{
|
||||||
@ -280,7 +281,7 @@ class Printer
|
|||||||
return $maybeArray
|
return $maybeArray
|
||||||
? implode(
|
? implode(
|
||||||
$separator,
|
$separator,
|
||||||
array_filter(
|
Utils::filter(
|
||||||
$maybeArray,
|
$maybeArray,
|
||||||
function($x) { return !!$x;}
|
function($x) { return !!$x;}
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,7 @@ namespace GraphQL\Language;
|
|||||||
|
|
||||||
use GraphQL\Language\AST\Node;
|
use GraphQL\Language\AST\Node;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
|
use GraphQL\Language\AST\NodeList;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
|
|
||||||
class VisitorOperation
|
class VisitorOperation
|
||||||
@ -180,7 +181,7 @@ class Visitor
|
|||||||
$visitorKeys = $keyMap ?: self::$visitorKeys;
|
$visitorKeys = $keyMap ?: self::$visitorKeys;
|
||||||
|
|
||||||
$stack = null;
|
$stack = null;
|
||||||
$inArray = is_array($root);
|
$inArray = $root instanceof NodeList || is_array($root);
|
||||||
$keys = [$root];
|
$keys = [$root];
|
||||||
$index = -1;
|
$index = -1;
|
||||||
$edits = [];
|
$edits = [];
|
||||||
@ -206,6 +207,9 @@ class Visitor
|
|||||||
if ($isEdited) {
|
if ($isEdited) {
|
||||||
if ($inArray) {
|
if ($inArray) {
|
||||||
// $node = $node; // arrays are value types in PHP
|
// $node = $node; // arrays are value types in PHP
|
||||||
|
if ($node instanceof NodeList) {
|
||||||
|
$node = clone $node;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$node = clone $node;
|
$node = clone $node;
|
||||||
}
|
}
|
||||||
@ -218,10 +222,14 @@ class Visitor
|
|||||||
$editKey -= $editOffset;
|
$editKey -= $editOffset;
|
||||||
}
|
}
|
||||||
if ($inArray && $editValue === null) {
|
if ($inArray && $editValue === null) {
|
||||||
|
if ($node instanceof NodeList) {
|
||||||
|
$node->splice($editKey, 1);
|
||||||
|
} else {
|
||||||
array_splice($node, $editKey, 1);
|
array_splice($node, $editKey, 1);
|
||||||
|
}
|
||||||
$editOffset++;
|
$editOffset++;
|
||||||
} else {
|
} else {
|
||||||
if (is_array($node)) {
|
if ($node instanceof NodeList || is_array($node)) {
|
||||||
$node[$editKey] = $editValue;
|
$node[$editKey] = $editValue;
|
||||||
} else {
|
} else {
|
||||||
$node->{$editKey} = $editValue;
|
$node->{$editKey} = $editValue;
|
||||||
@ -236,7 +244,7 @@ class Visitor
|
|||||||
$stack = $stack['prev'];
|
$stack = $stack['prev'];
|
||||||
} else {
|
} else {
|
||||||
$key = $parent ? ($inArray ? $index : $keys[$index]) : $UNDEFINED;
|
$key = $parent ? ($inArray ? $index : $keys[$index]) : $UNDEFINED;
|
||||||
$node = $parent ? (is_array($parent) ? $parent[$key] : $parent->{$key}) : $newRoot;
|
$node = $parent ? (($parent instanceof NodeList || is_array($parent)) ? $parent[$key] : $parent->{$key}) : $newRoot;
|
||||||
if ($node === null || $node === $UNDEFINED) {
|
if ($node === null || $node === $UNDEFINED) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -246,7 +254,7 @@ class Visitor
|
|||||||
}
|
}
|
||||||
|
|
||||||
$result = null;
|
$result = null;
|
||||||
if (!is_array($node)) {
|
if (!$node instanceof NodeList && !is_array($node)) {
|
||||||
if (!($node instanceof Node)) {
|
if (!($node instanceof Node)) {
|
||||||
throw new \Exception('Invalid AST Node: ' . json_encode($node));
|
throw new \Exception('Invalid AST Node: ' . json_encode($node));
|
||||||
}
|
}
|
||||||
@ -297,7 +305,7 @@ class Visitor
|
|||||||
'edits' => $edits,
|
'edits' => $edits,
|
||||||
'prev' => $stack
|
'prev' => $stack
|
||||||
];
|
];
|
||||||
$inArray = is_array($node);
|
$inArray = $node instanceof NodeList || is_array($node);
|
||||||
|
|
||||||
$keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?: [];
|
$keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?: [];
|
||||||
$index = -1;
|
$index = -1;
|
||||||
|
@ -183,12 +183,19 @@ class Utils
|
|||||||
return $grouped;
|
return $grouped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array|Traversable $traversable
|
||||||
|
* @param callable $keyFn
|
||||||
|
* @param callable $valFn
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
public static function keyValMap($traversable, callable $keyFn, callable $valFn)
|
public static function keyValMap($traversable, callable $keyFn, callable $valFn)
|
||||||
{
|
{
|
||||||
return array_reduce($traversable, function ($map, $item) use ($keyFn, $valFn) {
|
$map = [];
|
||||||
|
foreach ($traversable as $item) {
|
||||||
$map[$keyFn($item)] = $valFn($item);
|
$map[$keyFn($item)] = $valFn($item);
|
||||||
|
}
|
||||||
return $map;
|
return $map;
|
||||||
}, []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -379,7 +379,7 @@ class OverlappingFieldsCanBeMerged
|
|||||||
*
|
*
|
||||||
* @return bool|string
|
* @return bool|string
|
||||||
*/
|
*/
|
||||||
private function sameArguments(array $arguments1, array $arguments2)
|
private function sameArguments($arguments1, $arguments2)
|
||||||
{
|
{
|
||||||
if (count($arguments1) !== count($arguments2)) {
|
if (count($arguments1) !== count($arguments2)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -131,13 +131,13 @@ class ValidationContext
|
|||||||
{
|
{
|
||||||
$fragments = $this->fragments;
|
$fragments = $this->fragments;
|
||||||
if (!$fragments) {
|
if (!$fragments) {
|
||||||
$this->fragments = $fragments =
|
$fragments = [];
|
||||||
array_reduce($this->getDocument()->definitions, function($frags, $statement) {
|
foreach ($this->getDocument()->definitions as $statement) {
|
||||||
if ($statement->kind === NodeKind::FRAGMENT_DEFINITION) {
|
if ($statement->kind === NodeKind::FRAGMENT_DEFINITION) {
|
||||||
$frags[$statement->name->value] = $statement;
|
$fragments[$statement->name->value] = $statement;
|
||||||
}
|
}
|
||||||
return $frags;
|
}
|
||||||
}, []);
|
$this->fragments = $fragments;
|
||||||
}
|
}
|
||||||
return isset($fragments[$name]) ? $fragments[$name] : null;
|
return isset($fragments[$name]) ? $fragments[$name] : null;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ use GraphQL\Language\AST\FieldNode;
|
|||||||
use GraphQL\Language\AST\NameNode;
|
use GraphQL\Language\AST\NameNode;
|
||||||
use GraphQL\Language\AST\Node;
|
use GraphQL\Language\AST\Node;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
|
use GraphQL\Language\AST\NodeList;
|
||||||
use GraphQL\Language\AST\SelectionSetNode;
|
use GraphQL\Language\AST\SelectionSetNode;
|
||||||
use GraphQL\Language\AST\StringValueNode;
|
use GraphQL\Language\AST\StringValueNode;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
@ -150,20 +151,20 @@ HEREDOC;
|
|||||||
$result = Parser::parse($query, ['noLocation' => true]);
|
$result = Parser::parse($query, ['noLocation' => true]);
|
||||||
|
|
||||||
$expected = new SelectionSetNode([
|
$expected = new SelectionSetNode([
|
||||||
'selections' => [
|
'selections' => new NodeList([
|
||||||
new FieldNode([
|
new FieldNode([
|
||||||
'name' => new NameNode(['value' => 'field']),
|
'name' => new NameNode(['value' => 'field']),
|
||||||
'arguments' => [
|
'arguments' => new NodeList([
|
||||||
new ArgumentNode([
|
new ArgumentNode([
|
||||||
'name' => new NameNode(['value' => 'arg']),
|
'name' => new NameNode(['value' => 'arg']),
|
||||||
'value' => new StringValueNode([
|
'value' => new StringValueNode([
|
||||||
'value' => "Has a $char multi-byte character."
|
'value' => "Has a $char multi-byte character."
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
],
|
]),
|
||||||
'directives' => []
|
'directives' => new NodeList([])
|
||||||
|
])
|
||||||
])
|
])
|
||||||
]
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertEquals($expected, $result->definitions[0]->selectionSet);
|
$this->assertEquals($expected, $result->definitions[0]->selectionSet);
|
||||||
|
73
tests/Language/SerializationTest.php
Normal file
73
tests/Language/SerializationTest.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests;
|
||||||
|
|
||||||
|
use GraphQL\Language\AST\Location;
|
||||||
|
use GraphQL\Language\AST\Node;
|
||||||
|
use GraphQL\Language\AST\NodeList;
|
||||||
|
use GraphQL\Language\Parser;
|
||||||
|
|
||||||
|
class SerializationTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testSerializesAst()
|
||||||
|
{
|
||||||
|
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||||
|
$ast = Parser::parse($kitchenSink);
|
||||||
|
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
|
||||||
|
$this->assertEquals($expectedAst, $ast->toArray(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnserializesAst()
|
||||||
|
{
|
||||||
|
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
|
||||||
|
$serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink.ast'), true);
|
||||||
|
$actualAst = Node::fromArray($serializedAst);
|
||||||
|
$parsedAst = Parser::parse($kitchenSink);
|
||||||
|
$this->assertNodesAreEqual($parsedAst, $actualAst);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc
|
||||||
|
*
|
||||||
|
* @param $expected
|
||||||
|
* @param $actual
|
||||||
|
* @param array $path
|
||||||
|
*/
|
||||||
|
private function assertNodesAreEqual($expected, $actual, $path = [])
|
||||||
|
{
|
||||||
|
$err = "Mismatch at AST path: " . implode(', ', $path);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Node::class, $actual, $err);
|
||||||
|
$this->assertEquals(get_class($expected), get_class($actual), $err);
|
||||||
|
|
||||||
|
$expectedVars = get_object_vars($expected);
|
||||||
|
$actualVars = get_object_vars($actual);
|
||||||
|
$this->assertSame(count($expectedVars), count($actualVars), $err);
|
||||||
|
$this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err);
|
||||||
|
|
||||||
|
foreach ($expectedVars as $name => $expectedValue) {
|
||||||
|
$actualValue = $actualVars[$name];
|
||||||
|
$tmpPath = $path;
|
||||||
|
$tmpPath[] = $name;
|
||||||
|
$err = "Mismatch at AST path: " . implode(', ', $tmpPath);
|
||||||
|
|
||||||
|
if ($expectedValue instanceof Node) {
|
||||||
|
$this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath);
|
||||||
|
} else if ($expectedValue instanceof NodeList) {
|
||||||
|
$this->assertEquals(count($expectedValue), count($actualValue), $err);
|
||||||
|
$this->assertInstanceOf(NodeList::class, $actualValue, $err);
|
||||||
|
|
||||||
|
foreach ($expectedValue as $index => $listNode) {
|
||||||
|
$tmpPath2 = $tmpPath;
|
||||||
|
$tmpPath2 [] = $index;
|
||||||
|
$this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2);
|
||||||
|
}
|
||||||
|
} else if ($expectedValue instanceof Location) {
|
||||||
|
$this->assertInstanceOf(Location::class, $actualValue, $err);
|
||||||
|
$this->assertSame($expectedValue->start, $actualValue->start, $err);
|
||||||
|
$this->assertSame($expectedValue->end, $actualValue->end, $err);
|
||||||
|
} else {
|
||||||
|
$this->assertEquals($expectedValue, $actualValue, $err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ namespace GraphQL\Tests\Language;
|
|||||||
|
|
||||||
use GraphQL\Language\AST\Location;
|
use GraphQL\Language\AST\Location;
|
||||||
use GraphQL\Language\AST\Node;
|
use GraphQL\Language\AST\Node;
|
||||||
|
use GraphQL\Language\AST\NodeList;
|
||||||
|
|
||||||
class TestUtils
|
class TestUtils
|
||||||
{
|
{
|
||||||
@ -22,7 +23,7 @@ class TestUtils
|
|||||||
if (isset($result[$prop]))
|
if (isset($result[$prop]))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (is_array($propValue)) {
|
if (is_array($propValue) || $propValue instanceof NodeList) {
|
||||||
$tmp = [];
|
$tmp = [];
|
||||||
foreach ($propValue as $tmp1) {
|
foreach ($propValue as $tmp1) {
|
||||||
$tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1;
|
$tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1;
|
||||||
|
@ -6,6 +6,7 @@ use GraphQL\Language\AST\FieldNode;
|
|||||||
use GraphQL\Language\AST\NameNode;
|
use GraphQL\Language\AST\NameNode;
|
||||||
use GraphQL\Language\AST\Node;
|
use GraphQL\Language\AST\Node;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
|
use GraphQL\Language\AST\NodeList;
|
||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Language\AST\SelectionSetNode;
|
use GraphQL\Language\AST\SelectionSetNode;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
@ -157,7 +158,7 @@ class VisitorTest extends \PHPUnit_Framework_TestCase
|
|||||||
if ($node instanceof FieldNode && $node->name->value === 'a') {
|
if ($node instanceof FieldNode && $node->name->value === 'a') {
|
||||||
return new FieldNode([
|
return new FieldNode([
|
||||||
'selectionSet' => new SelectionSetNode(array(
|
'selectionSet' => new SelectionSetNode(array(
|
||||||
'selections' => array_merge([$addedField], $node->selectionSet->selections)
|
'selections' => NodeList::create([$addedField])->merge($node->selectionSet->selections)
|
||||||
))
|
))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
1280
tests/Language/kitchen-sink.ast
Normal file
1280
tests/Language/kitchen-sink.ast
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user