graphql-php/src/Validator/ValidationContext.php

297 lines
8.0 KiB
PHP
Raw Normal View History

2015-07-15 20:05:46 +03:00
<?php
2018-08-07 01:35:37 +03:00
declare(strict_types=1);
2015-07-15 20:05:46 +03:00
namespace GraphQL\Validator;
2018-08-07 01:35:37 +03:00
use GraphQL\Error\Error;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\HasSelectionSet;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
2018-08-13 11:36:03 +03:00
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\Visitor;
2015-07-15 20:05:46 +03:00
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\Type;
2018-08-07 01:35:37 +03:00
use GraphQL\Type\Schema;
2015-07-15 20:05:46 +03:00
use GraphQL\Utils\TypeInfo;
2018-08-07 01:35:37 +03:00
use SplObjectStorage;
use function array_pop;
use function call_user_func_array;
use function count;
2015-07-15 20:05:46 +03:00
/**
* An instance of this class is passed as the "this" context to all validators,
* allowing access to commonly useful contextual information from within a
* validation rule.
*/
class ValidationContext
{
2018-08-07 01:35:37 +03:00
/** @var Schema */
private $schema;
2015-07-15 20:05:46 +03:00
2018-08-07 01:35:37 +03:00
/** @var DocumentNode */
private $ast;
2015-07-15 20:05:46 +03:00
2018-08-07 01:35:37 +03:00
/** @var TypeInfo */
private $typeInfo;
2015-07-15 20:05:46 +03:00
2018-08-07 01:35:37 +03:00
/** @var Error[] */
private $errors;
2018-08-07 01:35:37 +03:00
/** @var FragmentDefinitionNode[] */
private $fragments;
2015-07-15 20:05:46 +03:00
2018-08-07 01:35:37 +03:00
/** @var SplObjectStorage */
private $fragmentSpreads;
2018-08-07 01:35:37 +03:00
/** @var SplObjectStorage */
private $recursivelyReferencedFragments;
2018-08-07 01:35:37 +03:00
/** @var SplObjectStorage */
private $variableUsages;
2018-08-07 01:35:37 +03:00
/** @var SplObjectStorage */
private $recursiveVariableUsages;
2018-08-07 01:35:37 +03:00
public function __construct(Schema $schema, DocumentNode $ast, TypeInfo $typeInfo)
2015-07-15 20:05:46 +03:00
{
2018-08-07 01:35:37 +03:00
$this->schema = $schema;
$this->ast = $ast;
$this->typeInfo = $typeInfo;
$this->errors = [];
$this->fragmentSpreads = new SplObjectStorage();
$this->recursivelyReferencedFragments = new SplObjectStorage();
2018-08-07 01:35:37 +03:00
$this->variableUsages = new SplObjectStorage();
$this->recursiveVariableUsages = new SplObjectStorage();
}
2018-08-07 01:35:37 +03:00
public function reportError(Error $error)
{
$this->errors[] = $error;
}
/**
* @return Error[]
*/
2018-08-07 01:35:37 +03:00
public function getErrors()
{
return $this->errors;
2015-07-15 20:05:46 +03:00
}
/**
* @return Schema
*/
2018-08-07 01:35:37 +03:00
public function getSchema()
2015-07-15 20:05:46 +03:00
{
return $this->schema;
2015-07-15 20:05:46 +03:00
}
/**
2018-08-07 01:35:37 +03:00
* @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
2015-07-15 20:05:46 +03:00
*/
2018-08-07 01:35:37 +03:00
public function getRecursiveVariableUsages(OperationDefinitionNode $operation)
2015-07-15 20:05:46 +03:00
{
2018-08-07 01:35:37 +03:00
$usages = $this->recursiveVariableUsages[$operation] ?? null;
2015-07-15 20:05:46 +03:00
2018-08-07 01:35:37 +03:00
if (! $usages) {
$usages = $this->getVariableUsages($operation);
$fragments = $this->getRecursivelyReferencedFragments($operation);
$tmp = [$usages];
for ($i = 0; $i < count($fragments); $i++) {
$tmp[] = $this->getVariableUsages($fragments[$i]);
}
2018-08-07 01:35:37 +03:00
$usages = call_user_func_array('array_merge', $tmp);
$this->recursiveVariableUsages[$operation] = $usages;
2015-07-15 20:05:46 +03:00
}
2018-08-07 01:35:37 +03:00
return $usages;
2015-07-15 20:05:46 +03:00
}
/**
2018-08-07 01:35:37 +03:00
* @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
*/
2018-08-07 01:35:37 +03:00
private function getVariableUsages(HasSelectionSet $node)
{
2018-08-07 01:35:37 +03:00
$usages = $this->variableUsages[$node] ?? null;
2018-08-07 01:35:37 +03:00
if (! $usages) {
$newUsages = [];
$typeInfo = new TypeInfo($this->schema);
Visitor::visit(
$node,
Visitor::visitWithTypeInfo(
$typeInfo,
[
NodeKind::VARIABLE_DEFINITION => function () {
return false;
},
NodeKind::VARIABLE => function (VariableNode $variable) use (
&$newUsages,
$typeInfo
) {
$newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
},
]
)
);
$usages = $newUsages;
$this->variableUsages[$node] = $usages;
}
2018-08-07 01:35:37 +03:00
return $usages;
}
/**
* @return FragmentDefinitionNode[]
*/
2018-08-07 01:35:37 +03:00
public function getRecursivelyReferencedFragments(OperationDefinitionNode $operation)
{
2018-08-07 01:35:37 +03:00
$fragments = $this->recursivelyReferencedFragments[$operation] ?? null;
2018-08-07 01:35:37 +03:00
if (! $fragments) {
$fragments = [];
$collectedNames = [];
2018-08-07 01:35:37 +03:00
$nodesToVisit = [$operation];
while (! empty($nodesToVisit)) {
$node = array_pop($nodesToVisit);
$spreads = $this->getFragmentSpreads($node);
for ($i = 0; $i < count($spreads); $i++) {
$fragName = $spreads[$i]->name->value;
2018-08-07 01:35:37 +03:00
if (! empty($collectedNames[$fragName])) {
continue;
}
2018-08-07 01:35:37 +03:00
$collectedNames[$fragName] = true;
$fragment = $this->getFragment($fragName);
if (! $fragment) {
continue;
}
$fragments[] = $fragment;
$nodesToVisit[] = $fragment;
}
}
$this->recursivelyReferencedFragments[$operation] = $fragments;
}
2018-08-07 01:35:37 +03:00
return $fragments;
}
/**
2018-08-07 01:35:37 +03:00
* @return FragmentSpreadNode[]
*/
2018-08-07 01:35:37 +03:00
public function getFragmentSpreads(HasSelectionSet $node)
{
2018-08-07 01:35:37 +03:00
$spreads = $this->fragmentSpreads[$node] ?? null;
if (! $spreads) {
$spreads = [];
2018-08-13 11:36:03 +03:00
/** @var SelectionSetNode[] $setsToVisit */
2018-08-07 01:35:37 +03:00
$setsToVisit = [$node->selectionSet];
while (! empty($setsToVisit)) {
$set = array_pop($setsToVisit);
2018-08-07 01:35:37 +03:00
for ($i = 0; $i < count($set->selections); $i++) {
$selection = $set->selections[$i];
if ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
$spreads[] = $selection;
} elseif ($selection->selectionSet) {
$setsToVisit[] = $selection->selectionSet;
}
}
2018-08-07 01:35:37 +03:00
}
$this->fragmentSpreads[$node] = $spreads;
}
2018-08-07 01:35:37 +03:00
return $spreads;
}
/**
2018-08-07 01:35:37 +03:00
* @param string $name
* @return FragmentDefinitionNode|null
*/
2018-08-07 01:35:37 +03:00
public function getFragment($name)
{
2018-08-07 01:35:37 +03:00
$fragments = $this->fragments;
if (! $fragments) {
$fragments = [];
foreach ($this->getDocument()->definitions as $statement) {
if ($statement->kind !== NodeKind::FRAGMENT_DEFINITION) {
continue;
}
2018-08-07 01:35:37 +03:00
$fragments[$statement->name->value] = $statement;
}
2018-08-07 01:35:37 +03:00
$this->fragments = $fragments;
}
2018-08-07 01:35:37 +03:00
return $fragments[$name] ?? null;
}
/**
* @return DocumentNode
*/
public function getDocument()
{
return $this->ast;
}
2015-07-15 20:05:46 +03:00
/**
* Returns OutputType
*
* @return Type
*/
2018-08-07 01:35:37 +03:00
public function getType()
2015-07-15 20:05:46 +03:00
{
return $this->typeInfo->getType();
2015-07-15 20:05:46 +03:00
}
/**
2018-08-07 01:35:37 +03:00
* @return Type
2015-07-15 20:05:46 +03:00
*/
2018-08-07 01:35:37 +03:00
public function getParentType()
2015-07-15 20:05:46 +03:00
{
return $this->typeInfo->getParentType();
2015-07-15 20:05:46 +03:00
}
/**
* @return InputType
*/
2018-08-07 01:35:37 +03:00
public function getInputType()
2015-07-15 20:05:46 +03:00
{
return $this->typeInfo->getInputType();
2015-07-15 20:05:46 +03:00
}
/**
* @return InputType
*/
2018-08-07 01:35:37 +03:00
public function getParentInputType()
{
return $this->typeInfo->getParentInputType();
}
2015-07-15 20:05:46 +03:00
/**
* @return FieldDefinition
*/
2018-08-07 01:35:37 +03:00
public function getFieldDef()
2015-07-15 20:05:46 +03:00
{
return $this->typeInfo->getFieldDef();
2015-07-15 20:05:46 +03:00
}
2018-08-07 01:35:37 +03:00
public function getDirective()
{
return $this->typeInfo->getDirective();
}
2018-08-07 01:35:37 +03:00
public function getArgument()
{
return $this->typeInfo->getArgument();
}
2015-07-15 20:05:46 +03:00
}