# Conflicts:
#	src/Utils/AST.php
This commit is contained in:
Vladimir Razuvaev 2018-08-21 22:10:50 +07:00
commit d44ec9e809
116 changed files with 4348 additions and 3644 deletions

View File

@ -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

View File

@ -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
*/

View File

@ -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

View File

@ -1,11 +1,11 @@
<?php
namespace GraphQL\Benchmarks\Utils;
use GraphQL\Schema;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
class SchemaGenerator
{

View File

@ -10,6 +10,7 @@
],
"require": {
"php": "^7.1",
"ext-json": "*",
"ext-mbstring": "*"
},
"require-dev": {

View File

@ -119,7 +119,7 @@ static function getStandardTypes()
* Returns standard validation rules implementing GraphQL spec
*
* @api
* @return AbstractValidationRule[]
* @return ValidationRule[]
*/
static function getStandardValidationRules()
```
@ -1241,7 +1241,7 @@ an empty array if no errors were encountered and the document is valid.
A list of specific validation rules may be provided. If not provided, the
default list of rules defined by the GraphQL specification will be used.
Each validation rule is an instance of GraphQL\Validator\Rules\AbstractValidationRule
Each validation rule is an instance of GraphQL\Validator\Rules\ValidationRule
which returns a visitor (see the [GraphQL\Language\Visitor API](reference.md#graphqllanguagevisitor)).
Visitor methods are expected to return an instance of [GraphQL\Error\Error](reference.md#graphqlerrorerror),
@ -1258,7 +1258,7 @@ will be created from the provided schema.
* @api
* @param Schema $schema
* @param DocumentNode $ast
* @param AbstractValidationRule[]|null $rules
* @param ValidationRule[]|null $rules
* @param TypeInfo|null $typeInfo
* @return Error[]
*/
@ -1275,7 +1275,7 @@ static function validate(
* Returns all global validation rules.
*
* @api
* @return AbstractValidationRule[]
* @return ValidationRule[]
*/
static function allRules()
```
@ -1289,7 +1289,7 @@ static function allRules()
*
* @api
* @param string $name
* @return AbstractValidationRule
* @return ValidationRule
*/
static function getRule($name)
```
@ -1299,9 +1299,9 @@ static function getRule($name)
* Add rule to list of global validation rules
*
* @api
* @param AbstractValidationRule $rule
* @param ValidationRule $rule
*/
static function addRule(GraphQL\Validator\Rules\AbstractValidationRule $rule)
static function addRule(GraphQL\Validator\Rules\ValidationRule $rule)
```
# GraphQL\Error\Error
Describes an Error found during the parse, validate, or

View File

@ -96,10 +96,11 @@ class EmailType extends ScalarType
* }
*
* @param \GraphQL\Language\AST\Node $valueNode
* @param array|null $variables
* @return string
* @throws Error
*/
public function parseLiteral($valueNode)
public function parseLiteral($valueNode, array $variables = null)
{
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
// error location in query:
@ -124,6 +125,6 @@ $emailType = new CustomScalarType([
'name' => '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 */},
]);
```

View File

@ -90,4 +90,11 @@
/>
</properties>
</rule>
<!-- IDEs sort by PSR12, Slevomat coding standard uses old sorting for BC -->
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
<properties>
<property name="psr12Compatible" type="bool" value="true" />
</properties>
</rule>
</ruleset>

View File

@ -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;
@ -82,7 +82,7 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
/**
* @param string $message
* @param Node[]|null $nodes
* @param Node|Node[]|Traversable|null $nodes
* @param mixed[]|null $positions
* @param mixed[]|null $path
* @param \Throwable $previous
@ -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);
}
},

View File

@ -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()
{

View File

@ -1,17 +1,19 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ArgumentNode extends Node
{
/** @var string */
public $kind = NodeKind::ARGUMENT;
/**
* @var ValueNode
*/
/** @var ValueNode */
public $value;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
}

View File

@ -1,13 +1,15 @@
<?php
namespace GraphQL\Language\AST;
declare(strict_types=1);
namespace GraphQL\Language\AST;
class BooleanValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::BOOLEAN;
/**
* @var string
*/
/** @var string */
public $value;
}

View File

@ -1,11 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
interface DefinitionNode
{
/**
* export type DefinitionNode =
* | ExecutableDefinitionNode
* | TypeSystemDefinitionNode; // experimental non-spec addition.
*/
interface DefinitionNode
{
}

View File

@ -1,30 +1,24 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class DirectiveDefinitionNode extends Node implements TypeSystemDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::DIRECTIVE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var ArgumentNode[]
*/
/** @var ArgumentNode[] */
public $arguments;
/**
* @var NameNode[]
*/
/** @var NameNode[] */
public $locations;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View File

@ -1,17 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class DirectiveNode extends Node
{
/** @var string */
public $kind = NodeKind::DIRECTIVE;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var ArgumentNode[]
*/
/** @var ArgumentNode[] */
public $arguments;
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class DocumentNode extends Node
{
/** @var string */
public $kind = NodeKind::DOCUMENT;
/**
* @var DefinitionNode[]
*/
/** @var DefinitionNode[] */
public $definitions;
}

View File

@ -1,30 +1,25 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class EnumTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::ENUM_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var EnumValueDefinitionNode[]|null|NodeList
*/
/** @var EnumValueDefinitionNode[]|null|NodeList */
public $values;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View File

@ -1,25 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class EnumTypeExtensionNode extends Node implements TypeExtensionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::ENUM_TYPE_EXTENSION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var EnumValueDefinitionNode[]|null
*/
/** @var EnumValueDefinitionNode[]|null */
public $values;
}

View File

@ -1,25 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class EnumValueDefinitionNode extends Node
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::ENUM_VALUE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class EnumValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::ENUM;
/**
* @var string
*/
/** @var string */
public $value;
}

View File

@ -1,11 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
interface ExecutableDefinitionNode extends DefinitionNode
{
/**
* export type ExecutableDefinitionNode =
* | OperationDefinitionNode
* | FragmentDefinitionNode;
*/
interface ExecutableDefinitionNode extends DefinitionNode
{
}

View File

@ -1,35 +1,27 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FieldDefinitionNode extends Node
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::FIELD_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var InputValueDefinitionNode[]|NodeList
*/
/** @var InputValueDefinitionNode[]|NodeList */
public $arguments;
/**
* @var TypeNode
*/
/** @var TypeNode */
public $type;
/**
* @var DirectiveNode[]|NodeList
*/
/** @var DirectiveNode[]|NodeList */
public $directives;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View File

@ -1,32 +1,27 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FieldNode extends Node implements SelectionNode
{
/** @var string */
public $kind = NodeKind::FIELD;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var NameNode|null
*/
/** @var NameNode|null */
public $alias;
/**
* @var ArgumentNode[]|null
*/
/** @var ArgumentNode[]|null */
public $arguments;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var SelectionSetNode|null
*/
/** @var SelectionSetNode|null */
public $selectionSet;
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FloatValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::FLOAT;
/**
* @var string
*/
/** @var string */
public $value;
}

View File

@ -1,13 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, HasSelectionSet
{
/** @var string */
public $kind = NodeKind::FRAGMENT_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
@ -18,18 +20,14 @@ class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, H
*/
public $variableDefinitions;
/**
* @var NamedTypeNode
*/
/** @var NamedTypeNode */
public $typeCondition;
/**
* @var DirectiveNode[]|NodeList
*/
/** @var DirectiveNode[]|NodeList */
public $directives;
/**
* @var SelectionSetNode
*/
/** @var SelectionSetNode */
public $selectionSet;
}

View File

@ -1,17 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FragmentSpreadNode extends Node implements SelectionNode
{
/** @var string */
public $kind = NodeKind::FRAGMENT_SPREAD;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
interface HasSelectionSet

View File

@ -1,22 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InlineFragmentNode extends Node implements SelectionNode
{
/** @var string */
public $kind = NodeKind::INLINE_FRAGMENT;
/**
* @var NamedTypeNode
*/
/** @var NamedTypeNode */
public $typeCondition;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var SelectionSetNode
*/
/** @var SelectionSetNode */
public $selectionSet;
}

View File

@ -1,30 +1,24 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InputObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INPUT_OBJECT_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var InputValueDefinitionNode[]|null
*/
/** @var InputValueDefinitionNode[]|null */
public $fields;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View File

@ -1,25 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InputObjectTypeExtensionNode extends Node implements TypeExtensionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INPUT_OBJECT_TYPE_EXTENSION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var InputValueDefinitionNode[]|null
*/
/** @var InputValueDefinitionNode[]|null */
public $fields;
}

View File

@ -1,35 +1,27 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InputValueDefinitionNode extends Node
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INPUT_VALUE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var TypeNode
*/
/** @var TypeNode */
public $type;
/**
* @var ValueNode
*/
/** @var ValueNode */
public $defaultValue;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class IntValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::INT;
/**
* @var string
*/
/** @var string */
public $value;
}

View File

@ -1,30 +1,24 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InterfaceTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INTERFACE_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var FieldDefinitionNode[]|null
*/
/** @var FieldDefinitionNode[]|null */
public $fields;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View File

@ -1,25 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InterfaceTypeExtensionNode extends Node implements TypeExtensionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INTERFACE_TYPE_EXTENSION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var FieldDefinitionNode[]|null
*/
/** @var FieldDefinitionNode[]|null */
public $fields;
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ListTypeNode extends Node implements TypeNode
{
/** @var string */
public $kind = NodeKind::LIST_TYPE;
/**
* @var Node
*/
/** @var Node */
public $type;
}

View File

@ -1,13 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ListValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::LST;
/**
* @var ValueNode[]|NodeList
*/
/** @var ValueNode[]|NodeList */
public $values;
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
use GraphQL\Language\Source;
@ -46,8 +49,8 @@ class Location
public $source;
/**
* @param $start
* @param $end
* @param int $start
* @param int $end
* @return static
*/
public static function create($start, $end)
@ -58,15 +61,17 @@ class Location
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;
if ($startToken && $endToken) {
if (! $startToken || ! $endToken) {
return;
}
$this->start = $startToken->start;
$this->end = $endToken->end;
}
}
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class NameNode extends Node implements TypeNode
{
/** @var string */
public $kind = NodeKind::NAME;
/**
* @var string
*/
/** @var string */
public $value;
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class NamedTypeNode extends Node implements TypeNode
{
/** @var string */
public $kind = NodeKind::NAMED_TYPE;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
}

View File

@ -1,50 +1,54 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
use GraphQL\Utils\Utils;
use function get_object_vars;
use function is_array;
use function is_scalar;
use function json_encode;
/**
* type Node = NameNode
* | DocumentNode
* | OperationDefinitionNode
* | VariableDefinitionNode
* | VariableNode
* | SelectionSetNode
* | FieldNode
* | ArgumentNode
* | FragmentSpreadNode
* | InlineFragmentNode
* | FragmentDefinitionNode
* | IntValueNode
* | FloatValueNode
* | StringValueNode
* | BooleanValueNode
* | EnumValueNode
* | ListValueNode
* | ObjectValueNode
* | ObjectFieldNode
* | DirectiveNode
* | ListTypeNode
* | NonNullTypeNode
*/
abstract class Node
{
/**
type Node = NameNode
| DocumentNode
| OperationDefinitionNode
| VariableDefinitionNode
| VariableNode
| SelectionSetNode
| FieldNode
| ArgumentNode
| FragmentSpreadNode
| InlineFragmentNode
| FragmentDefinitionNode
| IntValueNode
| FloatValueNode
| StringValueNode
| BooleanValueNode
| EnumValueNode
| ListValueNode
| ObjectValueNode
| ObjectFieldNode
| DirectiveNode
| ListTypeNode
| NonNullTypeNode
*/
public $kind;
/**
* @var Location
*/
/** @var Location */
public $loc;
/**
* @param array $vars
* @param (string|NameNode|NodeList|SelectionSetNode|Location|null)[] $vars
*/
public function __construct(array $vars)
{
if (!empty($vars)) {
Utils::assign($this, $vars);
if (empty($vars)) {
return;
}
Utils::assign($this, $vars);
}
/**
@ -56,8 +60,8 @@ abstract class Node
}
/**
* @param $value
* @return array|Node
* @param string|NodeList|Location|Node|(Node|NodeList|Location)[] $value
* @return string|NodeList|Location|Node
*/
private function cloneValue($value)
{
@ -66,7 +70,7 @@ abstract class Node
foreach ($value as $key => $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
'end' => $this->loc->end,
];
}
return $tmp;
}
}
/**
* @param Node $node
* @return array
* @return mixed[]
*/
private function recursiveToArray(Node $node)
{
@ -122,16 +126,18 @@ 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 = [];
@ -140,7 +146,7 @@ abstract class 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;
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class NodeKind
@ -7,7 +9,6 @@ class NodeKind
// constants from language/kinds.js:
const NAME = 'Name';
// Document
const DOCUMENT = 'Document';
@ -17,13 +18,11 @@ class NodeKind
const SELECTION_SET = 'SelectionSet';
const FIELD = 'Field';
const ARGUMENT = 'Argument';
// Fragments
const FRAGMENT_SPREAD = 'FragmentSpread';
const INLINE_FRAGMENT = 'InlineFragment';
const FRAGMENT_DEFINITION = 'FragmentDefinition';
// Values
const INT = 'IntValue';
@ -35,22 +34,18 @@ class NodeKind
const LST = 'ListValue';
const OBJECT = 'ObjectValue';
const OBJECT_FIELD = 'ObjectField';
// Directives
const DIRECTIVE = 'Directive';
// Types
const NAMED_TYPE = 'NamedType';
const LIST_TYPE = 'ListType';
const NON_NULL_TYPE = 'NonNullType';
// Type System Definitions
const SCHEMA_DEFINITION = 'SchemaDefinition';
const OPERATION_TYPE_DEFINITION = 'OperationTypeDefinition';
// Type Definitions
const SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition';
@ -62,7 +57,6 @@ class NodeKind
const ENUM_TYPE_DEFINITION = 'EnumTypeDefinition';
const ENUM_VALUE_DEFINITION = 'EnumValueDefinition';
const INPUT_OBJECT_TYPE_DEFINITION = 'InputObjectTypeDefinition';
// Type Extensions
const SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension';
@ -71,75 +65,71 @@ class NodeKind
const UNION_TYPE_EXTENSION = 'UnionTypeExtension';
const ENUM_TYPE_EXTENSION = 'EnumTypeExtension';
const INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension';
// Directive Definitions
const DIRECTIVE_DEFINITION = 'DirectiveDefinition';
/**
* @todo conver to const array when moving to PHP5.6
* @var array
*/
/** @var string[] */
public static $classMap = [
NodeKind::NAME => 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,
];
}

View File

@ -1,22 +1,22 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
use GraphQL\Utils\AST;
use function array_merge;
use function array_splice;
use function count;
use function is_array;
/**
* Class NodeList
*
* @package GraphQL\Utils
*/
class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
{
/**
* @var array
*/
/** @var Node[]|mixed[] */
private $nodes;
/**
* @param array $nodes
* @param Node[]|mixed[] $nodes
* @return static
*/
public static function create(array $nodes)
@ -25,8 +25,8 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
}
/**
* NodeList constructor.
* @param array $nodes
*
* @param Node[]|mixed[] $nodes
*/
public function __construct(array $nodes)
{
@ -89,12 +89,12 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
}
/**
* @param $list
* @param NodeList|Node[] $list
* @return NodeList
*/
public function merge($list)
{
if ($list instanceof NodeList) {
if ($list instanceof self) {
$list = $list->nodes;
}
return new NodeList(array_merge($this->nodes, $list));

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class NonNullTypeNode extends Node implements TypeNode
{
/** @var string */
public $kind = NodeKind::NON_NULL_TYPE;
/**
* @var NameNode | ListTypeNode
*/
/** @var NameNode | ListTypeNode */
public $type;
}

View File

@ -1,7 +1,12 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class NullValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::NULL;
}

View File

@ -1,18 +1,18 @@
<?php
namespace GraphQL\Language\AST;
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ObjectFieldNode extends Node
{
/** @var string */
public $kind = NodeKind::OBJECT_FIELD;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var ValueNode
*/
/** @var ValueNode */
public $value;
}

View File

@ -1,35 +1,28 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::OBJECT_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var NamedTypeNode[]
*/
/** @var NamedTypeNode[] */
public $interfaces = [];
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var FieldDefinitionNode[]|null
*/
/** @var FieldDefinitionNode[]|null */
public $fields;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View File

@ -1,30 +1,24 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ObjectTypeExtensionNode extends Node implements TypeExtensionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::OBJECT_TYPE_EXTENSION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var NamedTypeNode[]
*/
/** @var NamedTypeNode[] */
public $interfaces = [];
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var FieldDefinitionNode[]
*/
/** @var FieldDefinitionNode[] */
public $fields;
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ObjectValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::OBJECT;
/**
* @var ObjectFieldNode[]|NodeList
*/
/** @var ObjectFieldNode[]|NodeList */
public $fields;
}

View File

@ -1,35 +1,27 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class OperationDefinitionNode extends Node implements ExecutableDefinitionNode, HasSelectionSet
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::OPERATION_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var string (oneOf 'query', 'mutation'))
*/
/** @var string (oneOf 'query', 'mutation')) */
public $operation;
/**
* @var VariableDefinitionNode[]
*/
/** @var VariableDefinitionNode[] */
public $variableDefinitions;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var SelectionSetNode
*/
/** @var SelectionSetNode */
public $selectionSet;
}

View File

@ -1,11 +1,12 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class OperationTypeDefinitionNode extends Node
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::OPERATION_TYPE_DEFINITION;
/**
@ -15,8 +16,7 @@ class OperationTypeDefinitionNode extends Node
*/
public $operation;
/**
* @var NamedTypeNode
*/
/** @var NamedTypeNode */
public $type;
}

View File

@ -1,25 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ScalarTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::SCALAR_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View File

@ -1,20 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ScalarTypeExtensionNode extends Node implements TypeExtensionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::SCALAR_TYPE_EXTENSION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
}

View File

@ -1,20 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class SchemaDefinitionNode extends Node implements TypeSystemDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::SCHEMA_DEFINITION;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var OperationTypeDefinitionNode[]
*/
/** @var OperationTypeDefinitionNode[] */
public $operationTypes;
}

View File

@ -1,9 +1,12 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
interface SelectionNode
{
/**
* export type SelectionNode = FieldNode | FragmentSpreadNode | InlineFragmentNode
*/
interface SelectionNode
{
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class SelectionSetNode extends Node
{
/** @var string */
public $kind = NodeKind::SELECTION_SET;
/**
* @var SelectionNode[]
*/
/** @var SelectionNode[] */
public $selections;
}

View File

@ -1,17 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class StringValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::STRING;
/**
* @var string
*/
/** @var string */
public $value;
/**
* @var boolean|null
*/
/** @var bool|null */
public $block;
}

View File

@ -1,14 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
/**
* export type TypeDefinitionNode = ScalarTypeDefinitionNode
* | ObjectTypeDefinitionNode
* | InterfaceTypeDefinitionNode
* | UnionTypeDefinitionNode
* | EnumTypeDefinitionNode
* | InputObjectTypeDefinitionNode
*/
interface TypeDefinitionNode extends TypeSystemDefinitionNode
{
/**
export type TypeDefinitionNode = ScalarTypeDefinitionNode
| ObjectTypeDefinitionNode
| InterfaceTypeDefinitionNode
| UnionTypeDefinitionNode
| EnumTypeDefinitionNode
| InputObjectTypeDefinitionNode
*/
}

View File

@ -1,15 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
/**
* export type TypeExtensionNode =
* | ScalarTypeExtensionNode
* | ObjectTypeExtensionNode
* | InterfaceTypeExtensionNode
* | UnionTypeExtensionNode
* | EnumTypeExtensionNode
* | InputObjectTypeExtensionNode;
*/
interface TypeExtensionNode extends TypeSystemDefinitionNode
{
/**
export type TypeExtensionNode =
| ScalarTypeExtensionNode
| ObjectTypeExtensionNode
| InterfaceTypeExtensionNode
| UnionTypeExtensionNode
| EnumTypeExtensionNode
| InputObjectTypeExtensionNode;
*/
}

View File

@ -1,13 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
/**
* export type TypeNode = NamedTypeNode
* | ListTypeNode
* | NonNullTypeNode
*/
interface TypeNode
{
/**
export type TypeNode = NamedTypeNode
| ListTypeNode
| NonNullTypeNode
*/
}

View File

@ -1,13 +1,16 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
/**
* export type TypeSystemDefinitionNode =
* | SchemaDefinitionNode
* | TypeDefinitionNode
* | TypeExtensionNode
* | DirectiveDefinitionNode
*/
interface TypeSystemDefinitionNode extends DefinitionNode
{
/**
export type TypeSystemDefinitionNode =
| SchemaDefinitionNode
| TypeDefinitionNode
| TypeExtensionNode
| DirectiveDefinitionNode
*/
}

View File

@ -1,30 +1,24 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class UnionTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::UNION_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var NamedTypeNode[]|null
*/
/** @var NamedTypeNode[]|null */
public $types;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View File

@ -1,25 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class UnionTypeExtensionNode extends Node implements TypeExtensionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::UNION_TYPE_EXTENSION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var NamedTypeNode[]|null
*/
/** @var NamedTypeNode[]|null */
public $types;
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
/**
@ -14,5 +17,4 @@ export type ValueNode = VariableNode
*/
interface ValueNode
{
}

View File

@ -1,22 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class VariableDefinitionNode extends Node implements DefinitionNode
{
/** @var string */
public $kind = NodeKind::VARIABLE_DEFINITION;
/**
* @var VariableNode
*/
/** @var VariableNode */
public $variable;
/**
* @var TypeNode
*/
/** @var TypeNode */
public $type;
/**
* @var ValueNode|null
*/
/** @var ValueNode|null */
public $defaultValue;
}

View File

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class VariableNode extends Node
{
/** @var string */
public $kind = NodeKind::VARIABLE;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language;
/**
@ -28,6 +31,7 @@ class DirectiveLocation
const INPUT_OBJECT = 'INPUT_OBJECT';
const INPUT_FIELD_DEFINITION = 'INPUT_FIELD_DEFINITION';
/** @var string[] */
private static $locations = [
self::QUERY => self::QUERY,
self::MUTATION => self::MUTATION,

View File

@ -1,9 +1,16 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language;
use GraphQL\Error\SyntaxError;
use GraphQL\Utils\Utils;
use GraphQL\Utils\BlockString;
use GraphQL\Utils\Utils;
use function chr;
use function hexdec;
use function ord;
use function preg_match;
/**
* A Lexer is a stateful stream generator in that every time
@ -16,14 +23,10 @@ use GraphQL\Utils\BlockString;
*/
class Lexer
{
/**
* @var Source
*/
/** @var Source */
public $source;
/**
* @var array
*/
/** @var bool[] */
public $options;
/**
@ -69,10 +72,7 @@ class Lexer
private $byteStreamPosition;
/**
* Lexer constructor.
*
* @param Source $source
* @param array $options
* @param bool[] $options
*/
public function __construct(Source $source, array $options = [])
{
@ -94,6 +94,7 @@ class Lexer
{
$this->lastToken = $this->token;
$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
*/
@ -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,24 +180,76 @@ 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:
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:
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);
// "
@ -230,7 +284,6 @@ class Lexer
*
* @param int $line
* @param int $col
* @param Token $prev
* @return Token
*/
private function readName($line, $col, Token $prev)
@ -248,6 +301,7 @@ class Lexer
$value .= $char;
list ($char, $code) = $this->moveStringCursor(1, 1)->readChar();
}
return new Token(
Token::NAME,
$start,
@ -268,7 +322,6 @@ class Lexer
*
* @param int $line
* @param int $col
* @param Token $prev
* @return Token
* @throws SyntaxError
*/
@ -291,7 +344,11 @@ class Lexer
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();
@ -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
) {
@ -407,14 +462,30 @@ class Lexer
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;
list ($hex) = $this->readChars(4, true);
@ -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)
@ -598,8 +668,7 @@ class Lexer
do {
list ($char, $code, $bytes) = $this->moveStringCursor(1, $bytes)->readChar();
$value .= $char;
} while (
$code &&
} while ($code &&
// SourceCharacter but not LineTerminator
($code > 0x001F || $code === 0x0009)
);
@ -620,7 +689,7 @@ class Lexer
*
* @param bool $advance
* @param int $byteStreamPosition
* @return array
* @return (string|int)[]
*/
private function readChar($advance = false, $byteStreamPosition = null)
{
@ -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 = '';
$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;
}
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->byteStreamPosition += $byteStreamOffset;
return $this;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +1,32 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumTypeExtensionNode;
use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\ListTypeNode;
use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
@ -31,6 +34,7 @@ use GraphQL\Language\AST\NonNullTypeNode;
use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\ObjectFieldNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Language\AST\ObjectValueNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\OperationTypeDefinitionNode;
@ -39,11 +43,17 @@ use GraphQL\Language\AST\ScalarTypeExtensionNode;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\UnionTypeExtensionNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Utils\Utils;
use function count;
use function implode;
use function json_encode;
use function preg_replace;
use function sprintf;
use function str_replace;
use function strpos;
/**
* Prints AST to string. Capable of printing GraphQL queries and Type definition language.
@ -70,25 +80,32 @@ class Printer
{
static $instance;
$instance = $instance ?: new static();
return $instance->printAST($ast);
}
protected function __construct()
{}
{
}
public function printAST($ast)
{
return Visitor::visit($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;
@ -101,101 +118,126 @@ class Printer
? $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, ', '), ')'),
return $this->join(
[
$this->wrap('', $node->alias, ': ') . $node->name . $this->wrap(
'(',
$this->join($node->arguments, ', '),
')'
),
$this->join($node->directives, ' '),
$node->selectionSet
], ' ');
$node->selectionSet,
],
' '
);
},
NodeKind::ARGUMENT => function (ArgumentNode $node) {
return $node->name . ': ' . $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([
"...",
return $this->join(
[
'...',
$this->wrap('on ', $node->typeCondition),
$this->join($node->directives, ' '),
$node->selectionSet
], ' ');
$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}"
// 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, ', '), ')')
. " on {$node->typeCondition} "
. sprintf(' on %s ', $node->typeCondition)
. $this->wrap('', $this->join($node->directives, ' '), ' ')
. $node->selectionSet;
},
// 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;
},
// DirectiveNode
NodeKind::DIRECTIVE => function (DirectiveNode $node) {
return '@' . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')');
},
// 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 . '!';
},
// Type System Definitions
NodeKind::SCHEMA_DEFINITION => function (SchemaDefinitionNode $def) {
return $this->join([
return $this->join(
[
'schema',
$this->join($def->directives, ' '),
$this->block($def->operationTypes)
], ' ');
$this->block($def->operationTypes),
],
' '
);
},
NodeKind::OPERATION_TYPE_DEFINITION => function (OperationTypeDefinitionNode $def) {
return $def->operation . ': ' . $def->type;
},
@ -203,123 +245,179 @@ class Printer
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([
return $this->join(
[
'type',
$def->name,
$this->wrap('implements ', $this->join($def->interfaces, ' & ')),
$this->join($def->directives, ' '),
$this->block($def->fields)
], ' ');
$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([
return $this->join(
[
$def->name . ': ' . $def->type,
$this->wrap('= ', $def->defaultValue),
$this->join($def->directives, ' ')
], ' ');
$this->join($def->directives, ' '),
],
' '
);
}),
NodeKind::INTERFACE_TYPE_DEFINITION => $this->addDescription(function(InterfaceTypeDefinitionNode $def) {
return $this->join([
NodeKind::INTERFACE_TYPE_DEFINITION => $this->addDescription(
function (InterfaceTypeDefinitionNode $def) {
return $this->join(
[
'interface',
$def->name,
$this->join($def->directives, ' '),
$this->block($def->fields)
], ' ');
}),
$this->block($def->fields),
],
' '
);
}
),
NodeKind::UNION_TYPE_DEFINITION => $this->addDescription(function (UnionTypeDefinitionNode $def) {
return $this->join([
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([
return $this->join(
[
'enum',
$def->name,
$this->join($def->directives, ' '),
$this->block($def->values)
], ' ');
$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([
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $this->addDescription(function (
InputObjectTypeDefinitionNode $def
) {
return $this->join(
[
'input',
$def->name,
$this->join($def->directives, ' '),
$this->block($def->fields)
], ' ');
$this->block($def->fields),
],
' '
);
}),
NodeKind::SCALAR_TYPE_EXTENSION => function (ScalarTypeExtensionNode $def) {
return $this->join([
return $this->join(
[
'extend scalar',
$def->name,
$this->join($def->directives, ' '),
], ' ');
],
' '
);
},
NodeKind::OBJECT_TYPE_EXTENSION => function (ObjectTypeExtensionNode $def) {
return $this->join([
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([
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([
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([
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([
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\"\"\"");

View File

@ -1,36 +1,33 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language;
use GraphQL\Utils\Utils;
use function is_string;
use function json_decode;
use function mb_strlen;
use function mb_substr;
use function preg_match_all;
use const PREG_OFFSET_CAPTURE;
/**
* Class Source
* @package GraphQL\Language
*/
class Source
{
/**
* @var string
*/
/** @var string */
public $body;
/**
* @var int
*/
/** @var int */
public $length;
/**
* @var string
*/
/** @var string */
public $name;
/**
* @var SourceLocation
*/
/** @var SourceLocation */
public $locationOffset;
/**
* Source constructor.
*
*
* A representation of source input to GraphQL.
* `name` and `locationOffset` are optional. They are useful for clients who
@ -41,9 +38,8 @@ class Source
*
* @param string $body
* @param string|null $name
* @param SourceLocation|null $location
*/
public function __construct($body, $name = null, SourceLocation $location = null)
public function __construct($body, $name = null, ?SourceLocation $location = null)
{
Utils::invariant(
is_string($body),
@ -66,7 +62,7 @@ class Source
}
/**
* @param $position
* @param int $position
* @return SourceLocation
*/
public function getLocation($position)
@ -81,6 +77,7 @@ class Source
foreach ($matches[0] as $index => $match) {
$line += 1;
$column = $position + 1 - ($match[1] + mb_strlen($match[0], 'UTF-8'));
}

View File

@ -1,11 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language;
class SourceLocation implements \JsonSerializable
{
/** @var int */
public $line;
/** @var int */
public $column;
/**
* @param int $line
* @param int $col
*/
public function __construct($line, $col)
{
$this->line = $line;
@ -13,18 +23,18 @@ class SourceLocation implements \JsonSerializable
}
/**
* @return array
* @return int[]
*/
public function toArray()
{
return [
'line' => $this->line,
'column' => $this->column
'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 <b>json_encode</b>,
* 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();
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language;
/**
@ -66,9 +69,7 @@ class Token
*/
public $column;
/**
* @var string|null
*/
/** @var string|null */
public $value;
/**
@ -80,28 +81,25 @@ 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->start = $start;
$this->end = $end;
$this->line = $line;
$this->column = $column;
$this->prev = $previous;
$this->next = null;
$this->value = $value;
@ -116,7 +114,7 @@ class Token
}
/**
* @return array
* @return (string|int|null)[]
*/
public function toArray()
{
@ -124,7 +122,7 @@ class Token
'kind' => $this->kind,
'value' => $this->value,
'line' => $this->line,
'column' => $this->column
'column' => $this->column,
];
}
}

View File

@ -1,19 +1,24 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language;
use ArrayObject;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Utils\TypeInfo;
class VisitorOperation
{
public $doBreak;
public $doContinue;
public $removeNode;
}
use stdClass;
use function array_pop;
use function array_splice;
use function call_user_func;
use function call_user_func_array;
use function count;
use function func_get_args;
use function is_array;
use function is_callable;
use function json_encode;
/**
* Utility for efficient AST traversal and modification.
@ -104,6 +109,7 @@ class VisitorOperation
*/
class Visitor
{
/** @var string[][] */
public static $visitorKeys = [
NodeKind::NAME => [],
NodeKind::DOCUMENT => ['definitions'],
@ -122,7 +128,7 @@ class Visitor
'variableDefinitions',
'typeCondition',
'directives',
'selectionSet'
'selectionSet',
],
NodeKind::INT => [],
@ -158,16 +164,16 @@ class Visitor
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
*/
@ -277,12 +283,12 @@ class Visitor
$edits[] = [$key, $editValue];
if (! $isLeaving) {
if ($editValue instanceof Node) {
$node = $editValue;
} else {
if (! ($editValue instanceof Node)) {
array_pop($path);
continue;
}
$node = $editValue;
}
}
}
@ -300,7 +306,7 @@ class Visitor
'index' => $index,
'keys' => $keys,
'edits' => $edits,
'prev' => $stack
'prev' => $stack,
];
$inArray = $node instanceof NodeList || is_array($node);
@ -312,7 +318,6 @@ class Visitor
}
$parent = $node;
}
} while ($stack);
if (count($edits) !== 0) {
@ -332,6 +337,7 @@ class Visitor
{
$r = new VisitorOperation();
$r->doBreak = true;
return $r;
}
@ -345,6 +351,7 @@ class Visitor
{
$r = new VisitorOperation();
$r->doContinue = true;
return $r;
}
@ -358,25 +365,36 @@ class Visitor
{
$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);
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;
}
$fn = self::getVisitFn(
$visitors[$i],
$node->kind, /* isLeaving */
false
);
if (! $fn) {
continue;
}
if ($fn) {
$result = call_user_func_array($fn, func_get_args());
if ($result instanceof VisitorOperation) {
@ -391,13 +409,15 @@ class Visitor
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());
@ -415,7 +435,7 @@ class Visitor
$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,31 +458,35 @@ class Visitor
$typeInfo->enter($result);
}
}
return $result;
}
return null;
},
'leave' => function ($node) use ($typeInfo, $visitor) {
'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;
$kindVisitor = $visitor[$kind] ?? null;
if (! $isLeaving && is_callable($kindVisitor)) {
// { Kind() {} }
@ -471,19 +495,21 @@ class Visitor
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;
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language;
class VisitorOperation
{
/** @var bool */
public $doBreak;
/** @var bool */
public $doContinue;
/** @var bool */
public $removeNode;
}

View File

@ -8,7 +8,7 @@ use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use GraphQL\Validator\Rules\AbstractValidationRule;
use GraphQL\Validator\Rules\ValidationRule;
use function is_array;
use function is_callable;
use function method_exists;
@ -73,7 +73,7 @@ class ServerConfig
/** @var bool */
private $queryBatching = false;
/** @var AbstractValidationRule[]|callable */
/** @var ValidationRule[]|callable */
private $validationRules;
/** @var callable */
@ -150,7 +150,7 @@ class ServerConfig
* Set validation rules for this server.
*
* @api
* @param AbstractValidationRule[]|callable $validationRules
* @param ValidationRule[]|callable $validationRules
* @return self
*/
public function setValidationRules($validationRules)
@ -281,7 +281,7 @@ class ServerConfig
}
/**
* @return AbstractValidationRule[]|callable
* @return ValidationRule[]|callable
*/
public function getValidationRules()
{

View File

@ -204,7 +204,7 @@ class Schema
*
* @api
* @param string $name
* @return Type
* @return Type|null
*/
public function getType($name)
{

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Utils;
use GraphQL\Error\Error;
@ -33,14 +36,28 @@ use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use function array_combine;
use function array_key_exists;
use function array_map;
use function count;
use function floatval;
use function intval;
use function is_array;
use function is_bool;
use function is_float;
use function is_int;
use function is_object;
use function is_string;
use function iterator_to_array;
use function json_encode;
use function property_exists;
use function substr;
/**
* Various utilities dealing with AST
*/
class AST
{
public static $integerStringRegExp = '/^-?(0|[1-9][0-9]*)$/';
/**
* Convert representation of AST as an associative array to instance of GraphQL\Language\AST\Node.
*
@ -63,16 +80,16 @@ class AST
* This is a reverse operation for AST::toArray($node)
*
* @api
* @param array $node
* @param mixed[] $node
* @return Node
*/
public static function fromArray(array $node)
{
if (! isset($node['kind']) || ! isset(NodeKind::$classMap[$node['kind']])) {
throw new InvariantViolation("Unexpected node structure: " . Utils::printSafeJson($node));
throw new InvariantViolation('Unexpected node structure: ' . Utils::printSafeJson($node));
}
$kind = isset($node['kind']) ? $node['kind'] : null;
$kind = $node['kind'] ?? null;
$class = NodeKind::$classMap[$kind];
$instance = new $class([]);
@ -80,9 +97,8 @@ class AST
$instance->loc = Location::create($node['loc']['start'], $node['loc']['end']);
}
foreach ($node as $key => $value) {
if ('loc' === $key || 'kind' === $key) {
if ($key === 'loc' || $key === 'kind') {
continue;
}
if (is_array($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,12 +170,16 @@ class AST
$valuesNodes = [];
foreach ($value as $item) {
$itemNode = self::astFromValue($item, $itemType);
if ($itemNode) {
if (! $itemNode) {
continue;
}
$valuesNodes[] = $itemNode;
}
}
return new ListValueNode(['values' => $valuesNodes]);
}
return self::astFromValue($value, $itemType);
}
@ -175,14 +195,14 @@ class AST
$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;
} elseif ($isArray) {
$fieldExists = array_key_exists($fieldName, $value);
@ -193,17 +213,22 @@ class AST
$fieldExists = property_exists($value, $fieldName);
}
if ($fieldExists) {
if (! $fieldExists) {
continue;
}
$fieldNode = self::astFromValue($fieldValue, $field->getType());
if ($fieldNode) {
if (! $fieldNode) {
continue;
}
$fieldNodes[] = new ObjectFieldNode([
'name' => new NameNode(['value' => $fieldName]),
'value' => $fieldNode
'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 ValueNode|null $valueNode
* @param mixed[]|null $variables
* @return array|null|\stdClass
* @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);
}
@ -348,6 +376,7 @@ class AST
$coercedValues[] = $itemValue;
}
}
return $coercedValues;
}
$coercedValue = self::valueFromAST($valueNode, $itemType, $variables);
@ -355,6 +384,7 @@ class AST
// Invalid: intentionally return no value.
return $undefined;
}
return [$coercedValue];
}
@ -366,11 +396,16 @@ class AST
$coercedObj = [];
$fields = $type->getFields();
$fieldNodes = Utils::keyMap($valueNode->fields, function($field) {return $field->name->value;});
$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 ($field->defaultValueExists()) {
@ -390,6 +425,7 @@ class AST
}
$coercedObj[$fieldName] = $fieldValue;
}
return $coercedObj;
}
@ -439,11 +475,12 @@ class AST
*
* @api
* @param Node $valueNode
* @param array|null $variables
* @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;
@ -465,16 +502,21 @@ class AST
case $valueNode instanceof ObjectValueNode:
return array_combine(
array_map(
function($field) { return $field->name->value; },
function ($field) {
return $field->name->value;
},
iterator_to_array($valueNode->fields)
),
array_map(
function($field) use ($variables) { return self::valueFromASTUntyped($field->value, $variables); },
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 (! ($def instanceof OperationDefinitionNode)) {
continue;
}
if (! $operationName || (isset($def->name->value) && $def->name->value === $operationName)) {
return $def->operation;
}
}
}
}
return false;
}
}

View File

@ -1,16 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Utils;
use GraphQL\Error\Error;
use GraphQL\Executor\Values;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumTypeExtensionNode;
use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\ListTypeNode;
use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NonNullTypeNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
@ -19,47 +24,49 @@ use GraphQL\Language\AST\TypeNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\Token;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\FieldArgument;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\FieldArgument;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use function array_reverse;
use function implode;
use function is_array;
use function is_string;
use function sprintf;
class ASTDefinitionBuilder
{
/**
* @var array
*/
/** @var Node[] */
private $typeDefintionsMap;
/**
* @var callable
*/
/** @var callable */
private $typeConfigDecorator;
/**
* @var array
*/
/** @var bool[] */
private $options;
/**
* @var callable
*/
/** @var callable */
private $resolveType;
/**
* @var array
*/
/** @var Type[] */
private $cache;
public function __construct(array $typeDefintionsMap, $options, callable $resolveType, callable $typeConfigDecorator = null)
{
/**
* @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;
@ -69,19 +76,20 @@ class ASTDefinitionBuilder
}
/**
* @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,6 +103,7 @@ class ASTDefinitionBuilder
while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) {
$namedType = $namedType->type;
}
return $namedType;
}
@ -104,7 +113,8 @@ class ASTDefinitionBuilder
* @return Type
* @throws Error
*/
private function internalBuildType($typeName, $typeNode = null) {
private function internalBuildType($typeName, $typeNode = null)
{
if (! isset($this->cache[$typeName])) {
if (isset($this->typeDefintionsMap[$typeName])) {
$type = $this->makeSchemaDef($this->typeDefintionsMap[$typeName]);
@ -112,20 +122,10 @@ class ASTDefinitionBuilder
$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,
@ -135,8 +135,11 @@ class ASTDefinitionBuilder
}
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 " .
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);
@ -166,13 +169,13 @@ 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);
}
@ -181,9 +184,12 @@ class ASTDefinitionBuilder
return new Directive([
'name' => $directiveNode->name->value,
'description' => $this->getDescription($directiveNode),
'locations' => Utils::map($directiveNode->locations, function ($node) {
'locations' => Utils::map(
$directiveNode->locations,
function ($node) {
return $node->value;
}),
}
),
'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
'astNode' => $directiveNode,
]);
@ -203,6 +209,11 @@ class ASTDefinitionBuilder
];
}
/**
* @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeDefinitionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode|UnionTypeDefinitionNode $def
* @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType
* @throws Error
*/
private function makeSchemaDef($def)
{
if (! $def) {
@ -222,10 +233,16 @@ 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) {
@ -245,13 +262,14 @@ 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,
'description' => $this->getDescription($def),
@ -261,7 +279,7 @@ class ASTDefinitionBuilder
'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 Utils::map(
$def->interfaces,
function ($iface) {
return $this->buildType($iface);
});
}
);
}
return null;
}
@ -309,11 +331,12 @@ class ASTDefinitionBuilder
'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,13 +345,14 @@ class ASTDefinitionBuilder
private function makeInterfaceDef(InterfaceTypeDefinitionNode $def)
{
$typeName = $def->name->value;
return new InterfaceType([
'name' => $typeName,
'description' => $this->getDescription($def),
'fields' => function () use ($def) {
return $this->makeFieldDefMap($def);
},
'astNode' => $def
'astNode' => $def,
]);
}
@ -347,7 +371,7 @@ class ASTDefinitionBuilder
return [
'description' => $this->getDescription($enumValue),
'deprecationReason' => $this->getDeprecationReason($enumValue),
'astNode' => $enumValue
'astNode' => $enumValue,
];
}
)
@ -365,9 +389,12 @@ class ASTDefinitionBuilder
// 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) {
? Utils::map(
$def->types,
function ($typeNode) {
return $this->buildType($typeNode);
}):
}
) :
[],
'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;
}
/**
@ -438,8 +466,7 @@ class ASTDefinitionBuilder
}
$comments = [];
$token = $loc->startToken->prev;
while (
$token &&
while ($token &&
$token->kind === Token::COMMENT &&
$token->next && $token->prev &&
$token->line + 1 === $token->next->line &&

View File

@ -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()
{

View File

@ -1,14 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Visitor;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\TypeInfo;
use GraphQL\Validator\Rules\AbstractValidationRule;
use GraphQL\Validator\Rules\ValuesOfCorrectType;
use GraphQL\Validator\Rules\DisableIntrospection;
use GraphQL\Validator\Rules\ExecutableDefinitions;
use GraphQL\Validator\Rules\FieldsOnCorrectType;
@ -27,6 +28,7 @@ use GraphQL\Validator\Rules\PossibleFragmentSpreads;
use GraphQL\Validator\Rules\ProvidedNonNullArguments;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\QueryDepth;
use GraphQL\Validator\Rules\QuerySecurityRule;
use GraphQL\Validator\Rules\ScalarLeafs;
use GraphQL\Validator\Rules\UniqueArgumentNames;
use GraphQL\Validator\Rules\UniqueDirectivesPerLocation;
@ -34,9 +36,16 @@ use GraphQL\Validator\Rules\UniqueFragmentNames;
use GraphQL\Validator\Rules\UniqueInputFieldNames;
use GraphQL\Validator\Rules\UniqueOperationNames;
use GraphQL\Validator\Rules\UniqueVariableNames;
use GraphQL\Validator\Rules\ValidationRule;
use GraphQL\Validator\Rules\ValuesOfCorrectType;
use GraphQL\Validator\Rules\VariablesAreInputTypes;
use GraphQL\Validator\Rules\VariablesDefaultValueAllowed;
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
use function array_filter;
use function array_merge;
use function count;
use function is_array;
use function sprintf;
/**
* Implements the "Validation" section of the spec.
@ -47,7 +56,7 @@ use GraphQL\Validator\Rules\VariablesInAllowedPosition;
* A list of specific validation rules may be provided. If not provided, the
* default list of rules defined by the GraphQL specification will be used.
*
* Each validation rule is an instance of GraphQL\Validator\Rules\AbstractValidationRule
* Each validation rule is an instance of GraphQL\Validator\Rules\ValidationRule
* which returns a visitor (see the [GraphQL\Language\Visitor API](reference.md#graphqllanguagevisitor)).
*
* Visitor methods are expected to return an instance of [GraphQL\Error\Error](reference.md#graphqlerrorerror),
@ -58,51 +67,50 @@ use GraphQL\Validator\Rules\VariablesInAllowedPosition;
*/
class DocumentValidator
{
/** @var ValidationRule[] */
private static $rules = [];
/** @var ValidationRule[]|null */
private static $defaultRules;
/** @var QuerySecurityRule[]|null */
private static $securityRules;
/** @var bool */
private static $initRules = false;
/**
* Primary method for query validation. See class description for details.
*
* @api
* @param Schema $schema
* @param DocumentNode $ast
* @param AbstractValidationRule[]|null $rules
* @param TypeInfo|null $typeInfo
* @param ValidationRule[]|null $rules
* @return Error[]
*/
public static function validate(
Schema $schema,
DocumentNode $ast,
array $rules = null,
TypeInfo $typeInfo = null
)
{
if (null === $rules) {
?array $rules = null,
?TypeInfo $typeInfo = null
) {
if ($rules === null) {
$rules = static::allRules();
}
if (true === is_array($rules) && 0 === count($rules)) {
if (is_array($rules) === true && count($rules) === 0) {
// Skip validation if there are no rules
return [];
}
$typeInfo = $typeInfo ?: new TypeInfo($schema);
$errors = static::visitUsingRules($schema, $typeInfo, $ast, $rules);
return $errors;
}
return static::visitUsingRules($schema, $typeInfo, $ast, $rules);
}
/**
* Returns all global validation rules.
*
* @api
* @return AbstractValidationRule[]
* @return ValidationRule[]
*/
public static function allRules()
{
@ -116,7 +124,7 @@ class DocumentValidator
public static function defaultRules()
{
if (null === self::$defaultRules) {
if (self::$defaultRules === null) {
self::$defaultRules = [
ExecutableDefinitions::class => new ExecutableDefinitions(),
UniqueOperationNames::class => new UniqueOperationNames(),
@ -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
];
}
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;
}
@ -236,27 +270,7 @@ class DocumentValidator
$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();
}
}

View File

@ -1,11 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Validator\ValidationContext;
class CustomValidationRule extends AbstractValidationRule
class CustomValidationRule extends ValidationRule
{
/** @var callable */
private $visitorFn;
public function __construct($name, callable $visitorFn)
@ -15,12 +19,12 @@ class CustomValidationRule extends AbstractValidationRule
}
/**
* @param ValidationContext $context
* @return Error[]
*/
public function getVisitor(ValidationContext $context)
{
$fn = $this->visitorFn;
return $fn($context);
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
@ -6,9 +9,11 @@ use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Validator\ValidationContext;
class DisableIntrospection extends AbstractQuerySecurity
class DisableIntrospection extends QuerySecurityRule
{
const ENABLED = 1;
public const ENABLED = 1;
/** @var bool */
private $isEnabled;
public function __construct($enabled = self::ENABLED)
@ -21,7 +26,26 @@ class DisableIntrospection extends AbstractQuerySecurity
$this->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]
));
}
}
]
);
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
@ -9,6 +12,7 @@ use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\Visitor;
use GraphQL\Validator\ValidationContext;
use function sprintf;
/**
* Executable definitions
@ -16,32 +20,33 @@ use GraphQL\Validator\ValidationContext;
* A GraphQL document is only valid for execution if all definitions are either
* operation or fragment definitions.
*/
class ExecutableDefinitions extends AbstractValidationRule
class ExecutableDefinitions extends ValidationRule
{
static function nonExecutableDefinitionMessage($defName)
{
return "The \"$defName\" definition is not executable.";
}
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::DOCUMENT => 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
) {
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);
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
@ -10,31 +13,27 @@ use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
use function array_keys;
use function array_merge;
use function arsort;
use function sprintf;
class FieldsOnCorrectType extends AbstractValidationRule
class FieldsOnCorrectType extends ValidationRule
{
static function undefinedFieldMessage($fieldName, $type, array $suggestedTypeNames, array $suggestedFieldNames)
{
$message = 'Cannot query field "' . $fieldName . '" on type "' . $type.'".';
if ($suggestedTypeNames) {
$suggestions = Utils::quotedOrList($suggestedTypeNames);
$message .= " Did you mean to use an inline fragment on $suggestions?";
} else if ($suggestedFieldNames) {
$suggestions = Utils::quotedOrList($suggestedFieldNames);
$message .= " Did you mean {$suggestions}?";
}
return $message;
}
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::FIELD => function (FieldNode $node) use ($context) {
$type = $context->getParentType();
if ($type) {
if (! $type) {
return;
}
$fieldDef = $context->getFieldDef();
if (!$fieldDef) {
if ($fieldDef) {
return;
}
// This isn't valid. Let's find suggestions, if any.
$schema = $context->getSchema();
$fieldName = $node->name->value;
@ -63,9 +62,7 @@ class FieldsOnCorrectType extends AbstractValidationRule
),
[$node]
));
}
}
}
},
];
}
@ -75,10 +72,9 @@ class FieldsOnCorrectType extends AbstractValidationRule
* suggest them, sorted by how often the type is referenced, starting
* with Interfaces.
*
* @param Schema $schema
* @param $type
* @param ObjectType|InterfaceType $type
* @param string $fieldName
* @return array
* @return string[]
*/
private function getSuggestedTypeNames(Schema $schema, $type, $fieldName)
{
@ -122,8 +118,7 @@ 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 ObjectType|InterfaceType $type
* @param string $fieldName
* @return array|string[]
*/
@ -131,9 +126,39 @@ class FieldsOnCorrectType extends AbstractValidationRule
{
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;
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
@ -9,43 +12,53 @@ use GraphQL\Language\Printer;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\TypeInfo;
use GraphQL\Validator\ValidationContext;
use function sprintf;
class FragmentsOnCompositeTypes extends AbstractValidationRule
class FragmentsOnCompositeTypes extends ValidationRule
{
static function inlineFragmentOnNonCompositeErrorMessage($type)
{
return "Fragment cannot condition on non composite type \"$type\".";
}
static function fragmentOnNonCompositeErrorMessage($fragName, $type)
{
return "Fragment \"$fragName\" cannot condition on non composite type \"$type\".";
}
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) use ($context) {
if ($node->typeCondition) {
if (! $node->typeCondition) {
return;
}
$type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition);
if ($type && !Type::isCompositeType($type)) {
if (! $type || Type::isCompositeType($type)) {
return;
}
$context->reportError(new Error(
static::inlineFragmentOnNonCompositeErrorMessage($type),
[$node->typeCondition]
));
}
}
},
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
$type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition);
if ($type && !Type::isCompositeType($type)) {
if (! $type || Type::isCompositeType($type)) {
return;
}
$context->reportError(new Error(
static::fragmentOnNonCompositeErrorMessage($node->name->value, Printer::doPrint($node->typeCondition)),
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);
}
}

View File

@ -1,11 +1,19 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
use function array_map;
use function count;
use function sprintf;
/**
* Known argument names
@ -13,32 +21,18 @@ use GraphQL\Validator\ValidationContext;
* A GraphQL field is only valid if all supplied arguments are defined by
* that field.
*/
class KnownArgumentNames extends AbstractValidationRule
class KnownArgumentNames extends ValidationRule
{
public static function unknownArgMessage($argName, $fieldName, $typeName, array $suggestedArgs)
{
$message = "Unknown argument \"$argName\" on field \"$fieldName\" of type \"$typeName\".";
if ($suggestedArgs) {
$message .= ' Did you mean ' . Utils::quotedOrList($suggestedArgs) . '?';
}
return $message;
}
public static function unknownDirectiveArgMessage($argName, $directiveName, array $suggestedArgs)
{
$message = "Unknown argument \"$argName\" on directive \"@$directiveName\".";
if ($suggestedArgs) {
$message .= ' Did you mean ' . Utils::quotedOrList($suggestedArgs) . '?';
}
return $message;
}
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::ARGUMENT => function (ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) {
/** @var NodeList|Node[] $ancestors */
$argDef = $context->getArgument();
if (!$argDef) {
if ($argDef !== null) {
return;
}
$argumentOf = $ancestors[count($ancestors) - 1];
if ($argumentOf->kind === NodeKind::FIELD) {
$fieldDef = $context->getFieldDef();
@ -51,7 +45,12 @@ class KnownArgumentNames extends AbstractValidationRule
$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]
@ -66,15 +65,45 @@ class KnownArgumentNames extends AbstractValidationRule
$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]
));
}
}
}
}
},
];
}
/**
* @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;
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
@ -7,19 +10,12 @@ use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Validator\ValidationContext;
use function count;
use function in_array;
use function sprintf;
class KnownDirectives extends AbstractValidationRule
class KnownDirectives extends ValidationRule
{
static function unknownDirectiveMessage($directiveName)
{
return "Unknown directive \"$directiveName\".";
}
static function misplacedDirectiveMessage($directiveName, $location)
{
return "Directive \"$directiveName\" may not be used on \"$location\".";
}
public function getVisitor(ValidationContext $context)
{
return [
@ -37,6 +33,7 @@ class KnownDirectives extends AbstractValidationRule
self::unknownDirectiveMessage($node->name->value),
[$node]
));
return;
}
$candidateLocation = $this->getDirectiveLocationForASTPath($ancestors);
@ -52,19 +49,30 @@ class KnownDirectives extends AbstractValidationRule
[$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);
}
}

View File

@ -1,31 +1,40 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Validator\ValidationContext;
use function sprintf;
class KnownFragmentNames extends AbstractValidationRule
class KnownFragmentNames extends ValidationRule
{
static function unknownFragmentMessage($fragName)
{
return "Unknown fragment \"$fragName\".";
}
public function getVisitor(ValidationContext $context)
{
return [
NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) use ($context) {
$fragmentName = $node->name->value;
$fragment = $context->getFragment($fragmentName);
if (!$fragment) {
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);
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
@ -7,6 +10,8 @@ use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Visitor;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
use function array_keys;
use function sprintf;
/**
* Known type names
@ -14,21 +19,13 @@ use GraphQL\Validator\ValidationContext;
* A GraphQL document is only valid if referenced types (specifically
* variable definitions and fragment conditions) are defined by the type schema.
*/
class KnownTypeNames extends AbstractValidationRule
class KnownTypeNames extends ValidationRule
{
static function unknownTypeMessage($type, array $suggestedTypes)
{
$message = "Unknown type \"$type\".";
if ($suggestedTypes) {
$suggestions = Utils::quotedOrList($suggestedTypes);
$message .= " Did you mean $suggestions?";
}
return $message;
}
public function getVisitor(ValidationContext $context)
{
$skip = function() { return Visitor::skipNode(); };
$skip = function () {
return Visitor::skipNode();
};
return [
// TODO: when validating IDL, re-enable these. Experimental version does not
@ -42,15 +39,34 @@ class KnownTypeNames extends AbstractValidationRule
$schema = $context->getSchema();
$typeName = $node->name->value;
$type = $schema->getType($typeName);
if (!$type) {
if ($type !== null) {
return;
}
$context->reportError(new Error(
self::unknownTypeMessage(
$typeName,
Utils::suggestionList($typeName, array_keys($schema->getTypeMap()))
), [$node])
);
}
}
),
[$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;
}
}

View File

@ -1,12 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
use function count;
/**
* Lone anonymous operation
@ -14,33 +19,40 @@ use GraphQL\Validator\ValidationContext;
* A GraphQL document is only valid if when it contains an anonymous operation
* (the query short-hand) that it contains only that one operation definition.
*/
class LoneAnonymousOperation extends AbstractValidationRule
class LoneAnonymousOperation extends ValidationRule
{
static function anonOperationNotAloneMessage()
{
return 'This anonymous operation must be the only defined operation.';
}
public function getVisitor(ValidationContext $context)
{
$operationCount = 0;
return [
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) {
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.';
}
}

View File

@ -1,25 +1,33 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Visitor;
use GraphQL\Utils\Utils;
use GraphQL\Validator\ValidationContext;
use function array_merge;
use function array_pop;
use function array_slice;
use function count;
use function implode;
use function is_array;
use function sprintf;
class NoFragmentCycles extends AbstractValidationRule
class NoFragmentCycles extends ValidationRule
{
static function cycleErrorMessage($fragName, array $spreadNames = [])
{
$via = !empty($spreadNames) ? ' via ' . implode(', ', $spreadNames) : '';
return "Cannot spread fragment \"$fragName\" within itself$via.";
}
/** @var bool[] */
public $visitedFrags;
/** @var FragmentSpreadNode[] */
public $spreadPath;
/** @var (int|null)[] */
public $spreadPathIndexByName;
public function getVisitor(ValidationContext $context)
@ -42,8 +50,9 @@ class NoFragmentCycles extends AbstractValidationRule
if (! isset($this->visitedFrags[$node->name->value])) {
$this->detectCycleRecursive($node, $context);
}
return Visitor::skipNode();
}
},
];
}
@ -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;
@ -87,9 +96,12 @@ class NoFragmentCycles extends AbstractValidationRule
$context->reportError(new Error(
self::cycleErrorMessage(
$spreadName,
Utils::map($cyclePath, function ($s) {
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) : ''
);
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
@ -6,24 +9,16 @@ use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Validator\ValidationContext;
use function sprintf;
/**
* Class NoUndefinedVariables
*
* A GraphQL operation is only valid if all variables encountered, both directly
* and via fragment spreads, are defined by that operation.
*
* @package GraphQL\Validator\Rules
*/
class NoUndefinedVariables extends AbstractValidationRule
class NoUndefinedVariables extends ValidationRule
{
static function undefinedVarMessage($varName, $opName = null)
{
return $opName
? "Variable \"$$varName\" is not defined by operation \"$opName\"."
: "Variable \"$$varName\" is not defined.";
}
public function getVisitor(ValidationContext $context)
{
$variableNameDefined = [];
@ -40,7 +35,10 @@ class NoUndefinedVariables extends AbstractValidationRule
$node = $usage['node'];
$varName = $node->name->value;
if (empty($variableNameDefined[$varName])) {
if (! empty($variableNameDefined[$varName])) {
continue;
}
$context->reportError(new Error(
self::undefinedVarMessage(
$varName,
@ -49,12 +47,18 @@ class NoUndefinedVariables extends AbstractValidationRule
[$node, $operation]
));
}
}
}
},
],
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);
}
}

View File

@ -1,21 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\Visitor;
use GraphQL\Validator\ValidationContext;
use function sprintf;
class NoUnusedFragments extends AbstractValidationRule
class NoUnusedFragments extends ValidationRule
{
static function unusedFragMessage($fragName)
{
return "Fragment \"$fragName\" is never used.";
}
/** @var OperationDefinitionNode[] */
public $operationDefs;
/** @var FragmentDefinitionNode[] */
public $fragmentDefs;
public function getVisitor(ValidationContext $context)
@ -26,10 +28,12 @@ class NoUnusedFragments extends AbstractValidationRule
return [
NodeKind::OPERATION_DEFINITION => function ($node) {
$this->operationDefs[] = $node;
return Visitor::skipNode();
},
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $def) {
$this->fragmentDefs[] = $def;
return Visitor::skipNode();
},
NodeKind::DOCUMENT => [
@ -44,15 +48,22 @@ class NoUnusedFragments extends AbstractValidationRule
foreach ($this->fragmentDefs as $fragmentDef) {
$fragName = $fragmentDef->name->value;
if (empty($fragmentNameUsed[$fragName])) {
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);
}
}

View File

@ -1,20 +1,19 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Validator\ValidationContext;
use function sprintf;
class NoUnusedVariables extends AbstractValidationRule
class NoUnusedVariables extends ValidationRule
{
static function unusedVariableMessage($varName, $opName = null)
{
return $opName
? "Variable \"$$varName\" is never used in operation \"$opName\"."
: "Variable \"$$varName\" is never used.";
}
/** @var VariableDefinitionNode[] */
public $variableDefs;
public function getVisitor(ValidationContext $context)
@ -39,18 +38,27 @@ class NoUnusedVariables extends AbstractValidationRule
foreach ($this->variableDefs as $variableDef) {
$variableName = $variableDef->variable->name->value;
if (empty($variableNameUsed[$variableName])) {
if (! empty($variableNameUsed[$variableName])) {
continue;
}
$context->reportError(new Error(
self::unusedVariableMessage($variableName, $opName),
[$variableDef]
));
}
}
}
},
],
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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,25 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Validator\ValidationContext;
use GraphQL\Type\Schema;
use GraphQL\Utils\TypeInfo;
use GraphQL\Validator\ValidationContext;
use function sprintf;
class PossibleFragmentSpreads extends AbstractValidationRule
class PossibleFragmentSpreads extends ValidationRule
{
static function typeIncompatibleSpreadMessage($fragName, $parentType, $fragType)
{
return "Fragment \"$fragName\" cannot be spread here as objects of type \"$parentType\" can never be of type \"$fragType\".";
}
static function typeIncompatibleAnonSpreadMessage($parentType, $fragType)
{
return "Fragment cannot be spread here as objects of type \"$parentType\" can never be of type \"$fragType\".";
}
public function getVisitor(ValidationContext $context)
{
return [
@ -33,42 +27,37 @@ class PossibleFragmentSpreads extends AbstractValidationRule
$fragType = $context->getType();
$parentType = $context->getParentType();
if ($fragType instanceof CompositeType &&
$parentType instanceof CompositeType &&
!$this->doTypesOverlap($context->getSchema(), $fragType, $parentType)) {
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);
$parentType = $context->getParentType();
if ($fragType && $parentType && !$this->doTypesOverlap($context->getSchema(), $fragType, $parentType)) {
if (! $fragType ||
! $parentType ||
$this->doTypesOverlap($context->getSchema(), $fragType, $parentType)
) {
return;
}
$context->reportError(new Error(
self::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
[$node]
));
}
}
},
];
}
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;
}
private function doTypesOverlap(Schema $schema, CompositeType $fragType, CompositeType $parentType)
{
// Checking in the order of the most frequently used scenarios:
@ -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
);
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
@ -8,19 +11,10 @@ use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Visitor;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Validator\ValidationContext;
use function sprintf;
class ProvidedNonNullArguments extends AbstractValidationRule
class ProvidedNonNullArguments extends ValidationRule
{
static function missingFieldArgMessage($fieldName, $argName, $type)
{
return "Field \"$fieldName\" argument \"$argName\" of type \"$type\" is required but not provided.";
}
static function missingDirectiveArgMessage($directiveName, $argName, $type)
{
return "Directive \"@$directiveName\" argument \"$argName\" of type \"$type\" is required but not provided.";
}
public function getVisitor(ValidationContext $context)
{
return [
@ -38,15 +32,17 @@ 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) {
$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) {
@ -61,16 +57,42 @@ class ProvidedNonNullArguments extends AbstractValidationRule
}
foreach ($directiveDef->args as $argDef) {
$argNode = isset($argNodeMap[$argDef->name]) ? $argNodeMap[$argDef->name] : null;
if (!$argNode && $argDef->getType() instanceof NonNull) {
$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()),
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
);
}
}

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
@ -14,20 +17,27 @@ use GraphQL\Language\Visitor;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Validator\ValidationContext;
use function array_map;
use function call_user_func_array;
use function implode;
use function method_exists;
use function sprintf;
class QueryComplexity extends AbstractQuerySecurity
class QueryComplexity extends QuerySecurityRule
{
/** @var int */
private $maxQueryComplexity;
/** @var mixed[]|null */
private $rawVariableValues = [];
/** @var \ArrayObject */
private $variableDefs;
/** @var \ArrayObject */
private $fieldNodeAndDefs;
/**
* @var ValidationContext
*/
/** @var ValidationContext */
private $context;
public function __construct($maxQueryComplexity)
@ -35,38 +45,6 @@ class QueryComplexity extends AbstractQuerySecurity
$this->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;
@ -89,21 +67,29 @@ class QueryComplexity extends AbstractQuerySecurity
},
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)) {
if (! empty($errors)) {
return;
}
$complexity = $this->fieldComplexity($operationDefinition, $complexity);
if ($complexity > $this->getMaxQueryComplexity()) {
if ($complexity <= $this->getMaxQueryComplexity()) {
return;
}
$context->reportError(
new Error($this->maxQueryComplexityErrorMessage($this->getMaxQueryComplexity(), $complexity))
new Error($this->maxQueryComplexityErrorMessage(
$this->getMaxQueryComplexity(),
$complexity
))
);
}
}
},
],
]
@ -125,7 +111,7 @@ class QueryComplexity extends AbstractQuerySecurity
{
switch ($node->kind) {
case NodeKind::FIELD:
/* @var FieldNode $node */
/** @var FieldNode $node */
// default values
$args = [];
$complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN;
@ -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;
@ -183,7 +169,7 @@ class QueryComplexity extends AbstractQuerySecurity
$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,6 +179,59 @@ class QueryComplexity extends AbstractQuerySecurity
return $astFieldInfo;
}
private function directiveExcludesField(FieldNode $node)
{
foreach ($node->directives as $directiveNode) {
if ($directiveNode->name->value === 'deprecated') {
return false;
}
$variableValuesResult = Values::getVariableValues(
$this->context->getSchema(),
$this->variableDefs,
$this->getRawVariableValues()
);
if ($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();
$directiveArgs = Values::getArgumentValues($directive, $directiveNode, $variableValues);
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();
@ -209,11 +248,15 @@ class QueryComplexity extends AbstractQuerySecurity
);
if ($variableValuesResult['errors']) {
throw new Error(implode("\n\n", array_map(
throw new Error(implode(
"\n\n",
array_map(
function ($error) {
return $error->getMessage();
}
, $variableValuesResult['errors'])));
},
$variableValuesResult['errors']
)
));
}
$variableValues = $variableValuesResult['coerced'];
@ -223,39 +266,24 @@ class QueryComplexity extends AbstractQuerySecurity
return $args;
}
private function directiveExcludesField(FieldNode $node) {
foreach ($node->directives as $directiveNode) {
if ($directiveNode->name->value === 'deprecated') {
return false;
public function getMaxQueryComplexity()
{
return $this->maxQueryComplexity;
}
$variableValuesResult = Values::getVariableValues(
$this->context->getSchema(),
$this->variableDefs,
$this->getRawVariableValues()
);
/**
* 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);
if ($variableValuesResult['errors']) {
throw new Error(implode("\n\n", array_map(
function ($error) {
return $error->getMessage();
$this->maxQueryComplexity = (int) $maxQueryComplexity;
}
, $variableValuesResult['errors'])));
}
$variableValues = $variableValuesResult['coerced'];
if ($directiveNode->name->value === 'include') {
$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'];
}
}
public static function maxQueryComplexityErrorMessage($max, $count)
{
return sprintf('Max query complexity should be %d but got %d.', $max, $count);
}
protected function isEnabled()

View File

@ -1,21 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Validator\ValidationContext;
use function sprintf;
class QueryDepth extends AbstractQuerySecurity
class QueryDepth extends QuerySecurityRule
{
/**
* @var int
*/
/** @var int */
private $maxQueryDepth;
public function __construct($maxQueryDepth)
@ -23,28 +22,6 @@ class QueryDepth extends AbstractQuerySecurity
$this->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()) {
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;
}
}

View File

@ -1,40 +1,35 @@
<?php
declare(strict_types=1);
namespace GraphQL\Validator\Rules;
use Closure;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Introspection;
use GraphQL\Utils\TypeInfo;
use GraphQL\Validator\ValidationContext;
use function class_alias;
use function method_exists;
use function sprintf;
abstract class AbstractQuerySecurity extends AbstractValidationRule
abstract class QuerySecurityRule extends ValidationRule
{
const DISABLED = 0;
public const DISABLED = 0;
/**
* @var FragmentDefinitionNode[]
*/
/** @var FragmentDefinitionNode[] */
private $fragments = [];
/**
* @return \GraphQL\Language\AST\FragmentDefinitionNode[]
*/
protected function getFragments()
{
return $this->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,26 +38,26 @@ 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();
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?
@ -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,23 +96,24 @@ 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
*
* @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();
foreach ($selectionSet->selections as $selection) {
switch ($selection->kind) {
case NodeKind::FIELD:
/* @var FieldNode $selection */
/** @var FieldNode $selection */
$fieldName = $selection->name->value;
$fieldDef = null;
if ($parentType && method_exists($parentType, 'getFields')) {
@ -128,7 +140,7 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
$_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,7 +150,7 @@ abstract class AbstractQuerySecurity extends AbstractValidationRule
);
break;
case NodeKind::FRAGMENT_SPREAD:
/* @var FragmentSpreadNode $selection */
/** @var FragmentSpreadNode $selection */
$fragName = $selection->name->value;
if (empty($_visitedFragmentNames[$fragName])) {
@ -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');

Some files were not shown because too many files have changed in this diff Show More