Skip to content

Commit 1af9028

Browse files
committed
AST: new NodeList class for collections of nodes (vs array) to enable effective conversion of libgraphqlparser output to our AST tree
1 parent e04d330 commit 1af9028

File tree

16 files changed

+1712
-64
lines changed

16 files changed

+1712
-64
lines changed

src/Executor/Values.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use GraphQL\Language\AST\FieldNode;
1212
use GraphQL\Language\AST\FragmentSpreadNode;
1313
use GraphQL\Language\AST\InlineFragmentNode;
14+
use GraphQL\Language\AST\NodeList;
1415
use GraphQL\Language\AST\VariableNode;
1516
use GraphQL\Language\AST\VariableDefinitionNode;
1617
use GraphQL\Language\Printer;
@@ -180,7 +181,7 @@ public static function getArgumentValues($def, $node, $variableValues = null)
180181
*/
181182
public static function getDirectiveValues(Directive $directiveDef, $node, $variableValues = null)
182183
{
183-
if (isset($node->directives) && is_array($node->directives)) {
184+
if (isset($node->directives) && $node->directives instanceof NodeList) {
184185
$directiveNode = Utils::find($node->directives, function(DirectiveNode $directive) use ($directiveDef) {
185186
return $directive->name->value === $directiveDef->name;
186187
});

src/Language/AST/Location.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,28 @@ class Location
4545
*/
4646
public $source;
4747

48-
public function __construct(Token $startToken, Token $endToken, Source $source = null)
48+
/**
49+
* @param $start
50+
* @param $end
51+
* @return static
52+
*/
53+
public static function create($start, $end)
54+
{
55+
$tmp = new static();
56+
$tmp->start = $start;
57+
$tmp->end = $end;
58+
return $tmp;
59+
}
60+
61+
public function __construct(Token $startToken = null, Token $endToken = null, Source $source = null)
4962
{
5063
$this->startToken = $startToken;
5164
$this->endToken = $endToken;
52-
$this->start = $startToken->start;
53-
$this->end = $endToken->end;
5465
$this->source = $source;
66+
67+
if ($startToken && $endToken) {
68+
$this->start = $startToken->start;
69+
$this->end = $endToken->end;
70+
}
5571
}
5672
}

src/Language/AST/Node.php

Lines changed: 97 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22
namespace GraphQL\Language\AST;
33

4+
use GraphQL\Error\InvariantViolation;
45
use GraphQL\Utils\Utils;
56

67
abstract class Node
@@ -37,12 +38,69 @@ abstract class Node
3738
*/
3839
public $loc;
3940

41+
/**
42+
* Converts representation of AST as associative array to Node instance.
43+
*
44+
* For example:
45+
*
46+
* ```php
47+
* Node::fromArray([
48+
* 'kind' => 'ListValue',
49+
* 'values' => [
50+
* ['kind' => 'StringValue', 'value' => 'my str'],
51+
* ['kind' => 'StringValue', 'value' => 'my other str']
52+
* ],
53+
* 'loc' => ['start' => 21, 'end' => 25]
54+
* ]);
55+
* ```
56+
*
57+
* Will produce instance of `ListValueNode` where `values` prop is a lazily-evaluated `NodeList`
58+
* returning instances of `StringValueNode` on access.
59+
*
60+
* This is a reverse operation for $node->toArray(true)
61+
*
62+
* @param array $node
63+
* @return EnumValueDefinitionNode
64+
*/
65+
public static function fromArray(array $node)
66+
{
67+
if (!isset($node['kind']) || !isset(NodeKind::$classMap[$node['kind']])) {
68+
throw new InvariantViolation("Unexpected node structure: " . Utils::printSafeJson($node));
69+
}
70+
71+
$kind = isset($node['kind']) ? $node['kind'] : null;
72+
$class = NodeKind::$classMap[$kind];
73+
$instance = new $class([]);
74+
75+
if (isset($node['loc'], $node['loc']['start'], $node['loc']['end'])) {
76+
$instance->loc = Location::create($node['loc']['start'], $node['loc']['end']);
77+
}
78+
79+
80+
foreach ($node as $key => $value) {
81+
if ('loc' === $key || 'kind' === $key) {
82+
continue ;
83+
}
84+
if (is_array($value)) {
85+
if (isset($value[0]) || empty($value)) {
86+
$value = new NodeList($value);
87+
} else {
88+
$value = self::fromArray($value);
89+
}
90+
}
91+
$instance->{$key} = $value;
92+
}
93+
return $instance;
94+
}
95+
4096
/**
4197
* @param array $vars
4298
*/
4399
public function __construct(array $vars)
44100
{
45-
Utils::assign($this, $vars);
101+
if (!empty($vars)) {
102+
Utils::assign($this, $vars);
103+
}
46104
}
47105

48106
/**
@@ -91,34 +149,53 @@ public function __toString()
91149
*/
92150
public function toArray($recursive = false)
93151
{
94-
$tmp = (array) $this;
152+
if ($recursive) {
153+
return $this->recursiveToArray($this);
154+
} else {
155+
$tmp = (array) $this;
95156

96-
$tmp['loc'] = [
97-
'start' => $this->loc->start,
98-
'end' => $this->loc->end
99-
];
157+
$tmp['loc'] = [
158+
'start' => $this->loc->start,
159+
'end' => $this->loc->end
160+
];
100161

101-
if ($recursive) {
102-
$this->recursiveToArray($tmp);
162+
return $tmp;
103163
}
104-
105-
return $tmp;
106164
}
107165

108166
/**
109-
* @param $object
167+
* @param Node $node
168+
* @return array
110169
*/
111-
public function recursiveToArray(&$object)
170+
private function recursiveToArray(Node $node)
112171
{
113-
if ($object instanceof Node) {
114-
/** @var Node $object */
115-
$object = $object->toArray(true);
116-
} elseif (is_object($object)) {
117-
$object = (array) $object;
118-
} elseif (is_array($object)) {
119-
foreach ($object as &$o) {
120-
$this->recursiveToArray($o);
172+
$result = [
173+
'kind' => $node->kind,
174+
'loc' => [
175+
'start' => $node->loc->start,
176+
'end' => $node->loc->end
177+
]
178+
];
179+
180+
foreach (get_object_vars($node) as $prop => $propValue) {
181+
if (isset($result[$prop]))
182+
continue;
183+
184+
if (is_array($propValue) || $propValue instanceof NodeList) {
185+
$tmp = [];
186+
foreach ($propValue as $tmp1) {
187+
$tmp[] = $tmp1 instanceof Node ? $this->recursiveToArray($tmp1) : (array) $tmp1;
188+
}
189+
} else if ($propValue instanceof Node) {
190+
$tmp = $this->recursiveToArray($propValue);
191+
} else if (is_scalar($propValue) || null === $propValue) {
192+
$tmp = $propValue;
193+
} else {
194+
$tmp = null;
121195
}
196+
197+
$result[$prop] = $tmp;
122198
}
199+
return $result;
123200
}
124201
}

src/Language/AST/NodeKind.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,66 @@ class NodeKind
7070
// Directive Definitions
7171

7272
const DIRECTIVE_DEFINITION = 'DirectiveDefinition';
73+
74+
/**
75+
* @todo conver to const array when moving to PHP5.6
76+
* @var array
77+
*/
78+
public static $classMap = [
79+
NodeKind::NAME => NameNode::class,
80+
81+
// Document
82+
NodeKind::DOCUMENT => DocumentNode::class,
83+
NodeKind::OPERATION_DEFINITION => OperationDefinitionNode::class,
84+
NodeKind::VARIABLE_DEFINITION => VariableDefinitionNode::class,
85+
NodeKind::VARIABLE => VariableNode::class,
86+
NodeKind::SELECTION_SET => SelectionSetNode::class,
87+
NodeKind::FIELD => FieldNode::class,
88+
NodeKind::ARGUMENT => ArgumentNode::class,
89+
90+
// Fragments
91+
NodeKind::FRAGMENT_SPREAD => FragmentSpreadNode::class,
92+
NodeKind::INLINE_FRAGMENT => InlineFragmentNode::class,
93+
NodeKind::FRAGMENT_DEFINITION => FragmentDefinitionNode::class,
94+
95+
// Values
96+
NodeKind::INT => IntValueNode::class,
97+
NodeKind::FLOAT => FloatValueNode::class,
98+
NodeKind::STRING => StringValueNode::class,
99+
NodeKind::BOOLEAN => BooleanValueNode::class,
100+
NodeKind::ENUM => EnumValueNode::class,
101+
NodeKind::NULL => NullValueNode::class,
102+
NodeKind::LST => ListValueNode::class,
103+
NodeKind::OBJECT => ObjectValueNode::class,
104+
NodeKind::OBJECT_FIELD => ObjectFieldNode::class,
105+
106+
// Directives
107+
NodeKind::DIRECTIVE => DirectiveNode::class,
108+
109+
// Types
110+
NodeKind::NAMED_TYPE => NamedTypeNode::class,
111+
NodeKind::LIST_TYPE => ListTypeNode::class,
112+
NodeKind::NON_NULL_TYPE => NonNullTypeNode::class,
113+
114+
// Type System Definitions
115+
NodeKind::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
116+
NodeKind::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class,
117+
118+
// Type Definitions
119+
NodeKind::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class,
120+
NodeKind::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class,
121+
NodeKind::FIELD_DEFINITION => FieldDefinitionNode::class,
122+
NodeKind::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class,
123+
NodeKind::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class,
124+
NodeKind::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class,
125+
NodeKind::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class,
126+
NodeKind::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class,
127+
NodeKind::INPUT_OBJECT_TYPE_DEFINITION =>InputObjectTypeDefinitionNode::class,
128+
129+
// Type Extensions
130+
NodeKind::TYPE_EXTENSION_DEFINITION => TypeExtensionDefinitionNode::class,
131+
132+
// Directive Definitions
133+
NodeKind::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class
134+
];
73135
}

src/Language/AST/NodeList.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
namespace GraphQL\Language\AST;
3+
4+
/**
5+
* Class NodeList
6+
*
7+
* @package GraphQL\Utils
8+
*/
9+
class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
10+
{
11+
/**
12+
* @var array
13+
*/
14+
private $nodes;
15+
16+
/**
17+
* @param array $nodes
18+
* @return static
19+
*/
20+
public static function create(array $nodes)
21+
{
22+
return new static($nodes);
23+
}
24+
25+
/**
26+
* NodeList constructor.
27+
* @param array $nodes
28+
*/
29+
public function __construct(array $nodes)
30+
{
31+
$this->nodes = $nodes;
32+
}
33+
34+
/**
35+
* @param mixed $offset
36+
* @return bool
37+
*/
38+
public function offsetExists($offset)
39+
{
40+
return isset($this->nodes[$offset]);
41+
}
42+
43+
/**
44+
* @param mixed $offset
45+
* @return mixed
46+
*/
47+
public function offsetGet($offset)
48+
{
49+
$item = $this->nodes[$offset];
50+
51+
if (is_array($item) && isset($item['kind'])) {
52+
$this->nodes[$offset] = $item = Node::fromArray($item);
53+
}
54+
55+
return $item;
56+
}
57+
58+
/**
59+
* @param mixed $offset
60+
* @param mixed $value
61+
*/
62+
public function offsetSet($offset, $value)
63+
{
64+
if (is_array($value) && isset($value['kind'])) {
65+
$value = Node::fromArray($value);
66+
}
67+
$this->nodes[$offset] = $value;
68+
}
69+
70+
/**
71+
* @param mixed $offset
72+
*/
73+
public function offsetUnset($offset)
74+
{
75+
unset($this->nodes[$offset]);
76+
}
77+
78+
/**
79+
* @param int $offset
80+
* @param int $length
81+
* @param mixed $replacement
82+
* @return NodeList
83+
*/
84+
public function splice($offset, $length, $replacement = null)
85+
{
86+
return new NodeList(array_splice($this->nodes, $offset, $length, $replacement));
87+
}
88+
89+
/**
90+
* @param $list
91+
* @return NodeList
92+
*/
93+
public function merge($list)
94+
{
95+
if ($list instanceof NodeList) {
96+
$list = $list->nodes;
97+
}
98+
return new NodeList(array_merge($this->nodes, $list));
99+
}
100+
101+
/**
102+
* @return \Generator
103+
*/
104+
public function getIterator()
105+
{
106+
$count = count($this->nodes);
107+
for ($i = 0; $i < $count; $i++) {
108+
yield $this->offsetGet($i);
109+
}
110+
}
111+
112+
/**
113+
* @return int
114+
*/
115+
public function count()
116+
{
117+
return count($this->nodes);
118+
}
119+
}

0 commit comments

Comments
 (0)