Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
Merge 170d681 into 7ce0c8f
Browse files Browse the repository at this point in the history
  • Loading branch information
crisu83 committed Mar 2, 2018
2 parents 7ce0c8f + 170d681 commit bd874ae
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 3 deletions.
6 changes: 5 additions & 1 deletion src/Language/AST/Node/AbstractNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
namespace Digia\GraphQL\Language\AST\Node;

use Digia\GraphQL\Config\ConfigObject;
use Digia\GraphQL\Language\AST\Visitor\AcceptVisitorInterface;
use Digia\GraphQL\Language\AST\Visitor\AcceptVisitorTrait;
use Digia\GraphQL\Language\Location;
use Digia\GraphQL\Util\SerializationInterface;
use function Digia\GraphQL\Util\jsonEncode;

abstract class AbstractNode extends ConfigObject implements SerializationInterface
abstract class AbstractNode extends ConfigObject implements SerializationInterface, AcceptVisitorInterface
{

use AcceptVisitorTrait;

/**
* @var string
*/
Expand Down
4 changes: 2 additions & 2 deletions src/Language/AST/Node/DocumentNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ public function getDefinitionsAsArray(): array
public function toArray(): array
{
return [
'kind' => $this->kind,
'kind' => $this->kind,
'definitions' => $this->getDefinitionsAsArray(),
'loc' => $this->getLocationAsArray(),
'loc' => $this->getLocationAsArray(),
];
}
}
8 changes: 8 additions & 0 deletions src/Language/AST/Visitor/AbstractVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Digia\GraphQL\Language\AST\Visitor;

abstract class AbstractVisitor implements VisitorInterface
{

}
15 changes: 15 additions & 0 deletions src/Language/AST/Visitor/AcceptVisitorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Digia\GraphQL\Language\AST\Visitor;

interface AcceptVisitorInterface
{

/**
* @param VisitorInterface $visitor
* @param string|null $key
* @param array $path
* @return bool
*/
public function accept(VisitorInterface $visitor, ?string $key = null, array $path = []);
}
126 changes: 126 additions & 0 deletions src/Language/AST/Visitor/AcceptVisitorTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

namespace Digia\GraphQL\Language\AST\Visitor;

trait AcceptVisitorTrait
{

protected static $visitorKindToChildNodesMap = [
'Name' => [],

'Document' => ['definitions'],
'OperationDefinition' => [
'name',
'variableDefinitions',
'directives',
'selectionSet',
],
'VariableDefinition' => ['variable', 'type', 'defaultValue'],
'Variable' => ['name'],
'SelectionSet' => ['selections'],
'Field' => ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
'Argument' => ['name', 'value'],

'FragmentSpread' => ['name', 'directives'],
'InlineFragment' => ['typeCondition', 'directives', 'selectionSet'],
'FragmentDefinition' => [
'name',
// Note: fragment variable definitions are experimental and may be changed
// or removed in the future.
'variableDefinitions',
'typeCondition',
'directives',
'selectionSet',
],

'IntValue' => [],
'FloatValue' => [],
'StringValue' => [],
'BooleanValue' => [],
'NullValue' => [],
'EnumValue' => [],
'ListValue' => ['values'],
'ObjectValue' => ['fields'],
'ObjectField' => ['name', 'value'],

'Directive' => ['name', 'arguments'],

'NamedType' => ['name'],
'ListType' => ['type'],
'NonNullType' => ['type'],

'SchemaDefinition' => ['directives', 'operationTypes'],
'OperationTypeDefinition' => ['type'],

'ScalarTypeDefinition' => ['description', 'name', 'directives'],
'ObjectTypeDefinition' => [
'description',
'name',
'interfaces',
'directives',
'fields',
],
'FieldDefinition' => ['description', 'name', 'arguments', 'type', 'directives'],
'InputValueDefinition' => [
'description',
'name',
'type',
'defaultValue',
'directives',
],
'InterfaceTypeDefinition' => ['description', 'name', 'directives', 'fields'],
'UnionTypeDefinition' => ['description', 'name', 'directives', 'types'],
'EnumTypeDefinition' => ['description', 'name', 'directives', 'values'],
'EnumValueDefinition' => ['description', 'name', 'directives'],
'InputObjectTypeDefinition' => ['description', 'name', 'directives', 'fields'],

'ScalarTypeExtension' => ['name', 'directives'],
'ObjectTypeExtension' => ['name', 'interfaces', 'directives', 'fields'],
'InterfaceTypeExtension' => ['name', 'directives', 'fields'],
'UnionTypeExtension' => ['name', 'directives', 'types'],
'EnumTypeExtension' => ['name', 'directives', 'values'],
'InputObjectTypeExtension' => ['name', 'directives', 'fields'],

'DirectiveDefinition' => ['description', 'name', 'arguments', 'locations'],
];

/**
* @return string
*/
abstract public function getKind(): string;

/**
* @param VisitorInterface $visitor
* @param string|null $key
* @return bool
*/
public function accept(VisitorInterface $visitor, ?string $key = null, array $path = [])
{
/** @noinspection PhpParamsInspection */
if ($visitor->enterNode($this, $key, $path)) {
foreach (self::$visitorKindToChildNodesMap[$this->getKind()] as $name) {
$value = $this->{$name};
if (is_array($value) && !empty($value)) {
$path[] = $name;
foreach ($value as $k => $v) {
if ($v instanceof AcceptVisitorInterface) {
$path[] = $k;
$v->accept($visitor, $k, $path);
$path = array_slice($path, 0, count($path) - 1);
}
}
$path = array_slice($path, 0, count($path) - 1);
} else {
if ($value instanceof AcceptVisitorInterface) {
$path[] = $name;
$value->accept($visitor, $name, $path);
$path = array_slice($path, 0, count($path) - 1);
}
}
}
}

/** @noinspection PhpParamsInspection */
return $visitor->leaveNode($this, $key, $path);
}
}
25 changes: 25 additions & 0 deletions src/Language/AST/Visitor/VisitorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Digia\GraphQL\Language\AST\Visitor;

use Digia\GraphQL\Language\AST\Node\NodeInterface;

interface VisitorInterface
{

/**
* @param NodeInterface $node
* @param string|null $key
* @param array $path
* @return bool
*/
public function enterNode(NodeInterface $node, ?string $key = null, array $path = []): bool;

/**
* @param NodeInterface $node
* @param string|null $key
* @param array $path
* @return bool
*/
public function leaveNode(NodeInterface $node, ?string $key = null, array $path = []): bool;
}
92 changes: 92 additions & 0 deletions tests/Functional/Language/VisitorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace Digia\GraphQL\Test\Functional\Language;

use Digia\GraphQL\Language\AST\Node\DocumentNode;
use Digia\GraphQL\Language\AST\Node\NodeInterface;
use Digia\GraphQL\Language\AST\Visitor\AbstractVisitor;
use Digia\GraphQL\Test\TestCase;
use function Digia\GraphQL\parse;

class VisitorTest extends TestCase
{

/**
* @throws \Digia\GraphQL\Error\GraphQLError
* @throws \Exception
*/
public function testValidatesPathArgument()
{
$visited = [];

/** @var DocumentNode $ast */
$ast = parse('{ a }');

$visitor = new Visitor(
function (NodeInterface $node, ?string $key = null, array $path = []) use (&$visited) {
$visited[] = ['enter', array_slice($path, 0)];
return true;
},
function (NodeInterface $node, ?string $key = null, array $path = []) use (&$visited) {
$visited[] = ['leave', array_slice($path, 0)];
return true;
}
);

$ast->accept($visitor);

$this->assertEquals([
['enter', []],
['enter', ['definitions', 0]],
['enter', ['definitions', 0, 'selectionSet']],
['enter', ['definitions', 0, 'selectionSet', 'selections', 0]],
['enter', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']],
['leave', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']],
['leave', ['definitions', 0, 'selectionSet', 'selections', 0]],
['leave', ['definitions', 0, 'selectionSet']],
['leave', ['definitions', 0]],
['leave', []],
], $visited);
}
}

class Visitor extends AbstractVisitor
{

/**
* @var callable
*/
protected $enterFunction;

/**
* @var callable
*/
protected $leaveFunction;

/**
* TestableVisitor constructor.
* @param callable $enterFunction
* @param callable $leaveFunction
*/
public function __construct(callable $enterFunction, callable $leaveFunction)
{
$this->enterFunction = $enterFunction;
$this->leaveFunction = $leaveFunction;
}

/**
* @inheritdoc
*/
public function enterNode(NodeInterface $node, ?string $key = null, array $path = []): bool
{
return call_user_func($this->enterFunction, $node, $key, $path);
}

/**
* @inheritdoc
*/
public function leaveNode(NodeInterface $node, ?string $key = null, array $path = []): bool
{
return call_user_func($this->leaveFunction, $node, $key, $path);
}
}

0 comments on commit bd874ae

Please sign in to comment.