graphql-php/src/Validator/ValidationContext.php

296 lines
7.8 KiB
PHP
Raw Normal View History

2015-07-15 20:05:46 +03:00
<?php
namespace GraphQL\Validator;
use GraphQL\Language\AST\FragmentSpread;
use GraphQL\Language\AST\HasSelectionSet;
2016-11-11 01:20:19 +03:00
use GraphQL\Language\AST\NodeType;
use GraphQL\Language\AST\OperationDefinition;
use GraphQL\Language\AST\Variable;
use GraphQL\Language\Visitor;
use \SplObjectStorage;
use GraphQL\Error\Error;
2015-07-15 20:05:46 +03:00
use GraphQL\Schema;
use GraphQL\Language\AST\Document;
use GraphQL\Language\AST\FragmentDefinition;
use GraphQL\Language\AST\Node;
use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\OutputType;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\TypeInfo;
/**
* 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
{
/**
* @var Schema
*/
private $schema;
2015-07-15 20:05:46 +03:00
/**
* @var Document
*/
private $ast;
2015-07-15 20:05:46 +03:00
/**
* @var TypeInfo
*/
private $typeInfo;
2015-07-15 20:05:46 +03:00
/**
* @var Error[]
*/
private $errors;
2015-07-15 20:05:46 +03:00
/**
* @var array<string, FragmentDefinition>
*/
private $fragments;
2015-07-15 20:05:46 +03:00
/**
* @var SplObjectStorage
*/
private $fragmentSpreads;
/**
* @var SplObjectStorage
*/
private $recursivelyReferencedFragments;
/**
* @var SplObjectStorage
*/
private $variableUsages;
/**
* @var SplObjectStorage
*/
private $recursiveVariableUsages;
/**
* ValidationContext constructor.
*
* @param Schema $schema
* @param Document $ast
* @param TypeInfo $typeInfo
*/
2015-07-15 20:05:46 +03:00
function __construct(Schema $schema, Document $ast, TypeInfo $typeInfo)
{
$this->schema = $schema;
$this->ast = $ast;
$this->typeInfo = $typeInfo;
$this->errors = [];
$this->fragmentSpreads = new SplObjectStorage();
$this->recursivelyReferencedFragments = new SplObjectStorage();
$this->variableUsages = new SplObjectStorage();
$this->recursiveVariableUsages = new SplObjectStorage();
}
/**
* @param Error $error
*/
function reportError(Error $error)
{
$this->errors[] = $error;
}
/**
* @return Error[]
*/
function getErrors()
{
return $this->errors;
2015-07-15 20:05:46 +03:00
}
/**
* @return Schema
*/
function getSchema()
{
return $this->schema;
2015-07-15 20:05:46 +03:00
}
/**
* @return Document
*/
function getDocument()
{
return $this->ast;
2015-07-15 20:05:46 +03:00
}
/**
* @param $name
* @return FragmentDefinition|null
*/
function getFragment($name)
{
$fragments = $this->fragments;
2015-07-15 20:05:46 +03:00
if (!$fragments) {
$this->fragments = $fragments =
2015-07-15 20:05:46 +03:00
array_reduce($this->getDocument()->definitions, function($frags, $statement) {
2016-11-11 01:20:19 +03:00
if ($statement->kind === NodeType::FRAGMENT_DEFINITION) {
2015-07-15 20:05:46 +03:00
$frags[$statement->name->value] = $statement;
}
return $frags;
}, []);
}
return isset($fragments[$name]) ? $fragments[$name] : null;
}
/**
* @param HasSelectionSet $node
* @return FragmentSpread[]
*/
function getFragmentSpreads(HasSelectionSet $node)
{
$spreads = isset($this->fragmentSpreads[$node]) ? $this->fragmentSpreads[$node] : null;
if (!$spreads) {
$spreads = [];
$setsToVisit = [$node->selectionSet];
while (!empty($setsToVisit)) {
$set = array_pop($setsToVisit);
for ($i = 0; $i < count($set->selections); $i++) {
$selection = $set->selections[$i];
2016-11-11 01:20:19 +03:00
if ($selection->kind === NodeType::FRAGMENT_SPREAD) {
$spreads[] = $selection;
} else if ($selection->selectionSet) {
$setsToVisit[] = $selection->selectionSet;
}
}
}
$this->fragmentSpreads[$node] = $spreads;
}
return $spreads;
}
/**
* @param OperationDefinition $operation
* @return FragmentDefinition[]
*/
function getRecursivelyReferencedFragments(OperationDefinition $operation)
{
$fragments = isset($this->recursivelyReferencedFragments[$operation]) ? $this->recursivelyReferencedFragments[$operation] : null;
if (!$fragments) {
$fragments = [];
$collectedNames = [];
$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;
if (empty($collectedNames[$fragName])) {
$collectedNames[$fragName] = true;
$fragment = $this->getFragment($fragName);
if ($fragment) {
$fragments[] = $fragment;
$nodesToVisit[] = $fragment;
}
}
}
}
$this->recursivelyReferencedFragments[$operation] = $fragments;
}
return $fragments;
}
/**
* @param HasSelectionSet $node
* @return array List of ['node' => Variable, 'type' => ?InputObjectType]
*/
function getVariableUsages(HasSelectionSet $node)
{
$usages = isset($this->variableUsages[$node]) ? $this->variableUsages[$node] : null;
if (!$usages) {
$newUsages = [];
$typeInfo = new TypeInfo($this->schema);
Visitor::visit($node, Visitor::visitWithTypeInfo($typeInfo, [
2016-11-11 01:20:19 +03:00
NodeType::VARIABLE_DEFINITION => function () {
return false;
},
2016-11-11 01:20:19 +03:00
NodeType::VARIABLE => function (Variable $variable) use (&$newUsages, $typeInfo) {
$newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
}
]));
$usages = $newUsages;
$this->variableUsages[$node] = $usages;
}
return $usages;
}
/**
* @param OperationDefinition $operation
* @return array List of ['node' => Variable, 'type' => ?InputObjectType]
*/
function getRecursiveVariableUsages(OperationDefinition $operation)
{
$usages = isset($this->recursiveVariableUsages[$operation]) ? $this->recursiveVariableUsages[$operation] : null;
if (!$usages) {
$usages = $this->getVariableUsages($operation);
$fragments = $this->getRecursivelyReferencedFragments($operation);
$tmp = [$usages];
for ($i = 0; $i < count($fragments); $i++) {
$tmp[] = $this->getVariableUsages($fragments[$i]);
}
$usages = call_user_func_array('array_merge', $tmp);
$this->recursiveVariableUsages[$operation] = $usages;
}
return $usages;
}
2015-07-15 20:05:46 +03:00
/**
* Returns OutputType
*
* @return Type
*/
function getType()
{
return $this->typeInfo->getType();
2015-07-15 20:05:46 +03:00
}
/**
* @return CompositeType
*/
function getParentType()
{
return $this->typeInfo->getParentType();
2015-07-15 20:05:46 +03:00
}
/**
* @return InputType
*/
function getInputType()
{
return $this->typeInfo->getInputType();
2015-07-15 20:05:46 +03:00
}
/**
* @return FieldDefinition
*/
function getFieldDef()
{
return $this->typeInfo->getFieldDef();
2015-07-15 20:05:46 +03:00
}
function getDirective()
{
return $this->typeInfo->getDirective();
}
function getArgument()
{
return $this->typeInfo->getArgument();
}
2015-07-15 20:05:46 +03:00
}