<?php

declare(strict_types=1);

namespace GraphQL\Type;

use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils;
use function is_callable;

/**
 * Schema configuration class.
 * Could be passed directly to schema constructor. List of options accepted by **create** method is
 * [described in docs](type-system/schema.md#configuration-options).
 *
 * Usage example:
 *
 *     $config = SchemaConfig::create()
 *         ->setQuery($myQueryType)
 *         ->setTypeLoader($myTypeLoader);
 *
 *     $schema = new Schema($config);
 */
class SchemaConfig
{
    /** @var ObjectType */
    public $query;

    /** @var ObjectType */
    public $mutation;

    /** @var ObjectType */
    public $subscription;

    /** @var Type[]|callable */
    public $types;

    /** @var Directive[] */
    public $directives;

    /** @var callable */
    public $typeLoader;

    /** @var SchemaDefinitionNode */
    public $astNode;

    /** @var bool */
    public $assumeValid;

    /** @var SchemaTypeExtensionNode[] */
    public $extensionASTNodes;

    /**
     * Converts an array of options to instance of SchemaConfig
     * (or just returns empty config when array is not passed).
     *
     * @param mixed[] $options
     *
     * @return SchemaConfig
     *
     * @api
     */
    public static function create(array $options = [])
    {
        $config = new static();

        if (! empty($options)) {
            if (isset($options['query'])) {
                $config->setQuery($options['query']);
            }

            if (isset($options['mutation'])) {
                $config->setMutation($options['mutation']);
            }

            if (isset($options['subscription'])) {
                $config->setSubscription($options['subscription']);
            }

            if (isset($options['types'])) {
                $config->setTypes($options['types']);
            }

            if (isset($options['directives'])) {
                $config->setDirectives($options['directives']);
            }

            if (isset($options['typeLoader'])) {
                Utils::invariant(
                    is_callable($options['typeLoader']),
                    'Schema type loader must be callable if provided but got: %s',
                    Utils::printSafe($options['typeLoader'])
                );
                $config->setTypeLoader($options['typeLoader']);
            }

            if (isset($options['astNode'])) {
                $config->setAstNode($options['astNode']);
            }

            if (isset($options['assumeValid'])) {
                $config->setAssumeValid((bool) $options['assumeValid']);
            }

            if (isset($options['extensionASTNodes'])) {
                $config->setExtensionASTNodes($options['extensionASTNodes']);
            }
        }

        return $config;
    }

    /**
     * @return SchemaDefinitionNode
     */
    public function getAstNode()
    {
        return $this->astNode;
    }

    /**
     * @return SchemaConfig
     */
    public function setAstNode(SchemaDefinitionNode $astNode)
    {
        $this->astNode = $astNode;

        return $this;
    }

    /**
     * @return ObjectType
     *
     * @api
     */
    public function getQuery()
    {
        return $this->query;
    }

    /**
     * @param ObjectType $query
     *
     * @return SchemaConfig
     *
     * @api
     */
    public function setQuery($query)
    {
        $this->query = $query;

        return $this;
    }

    /**
     * @return ObjectType
     *
     * @api
     */
    public function getMutation()
    {
        return $this->mutation;
    }

    /**
     * @param ObjectType $mutation
     *
     * @return SchemaConfig
     *
     * @api
     */
    public function setMutation($mutation)
    {
        $this->mutation = $mutation;

        return $this;
    }

    /**
     * @return ObjectType
     *
     * @api
     */
    public function getSubscription()
    {
        return $this->subscription;
    }

    /**
     * @param ObjectType $subscription
     *
     * @return SchemaConfig
     *
     * @api
     */
    public function setSubscription($subscription)
    {
        $this->subscription = $subscription;

        return $this;
    }

    /**
     * @return Type[]
     *
     * @api
     */
    public function getTypes()
    {
        return $this->types ?: [];
    }

    /**
     * @param Type[]|callable $types
     *
     * @return SchemaConfig
     *
     * @api
     */
    public function setTypes($types)
    {
        $this->types = $types;

        return $this;
    }

    /**
     * @return Directive[]
     *
     * @api
     */
    public function getDirectives()
    {
        return $this->directives ?: [];
    }

    /**
     * @param Directive[] $directives
     *
     * @return SchemaConfig
     *
     * @api
     */
    public function setDirectives(array $directives)
    {
        $this->directives = $directives;

        return $this;
    }

    /**
     * @return callable
     *
     * @api
     */
    public function getTypeLoader()
    {
        return $this->typeLoader;
    }

    /**
     * @return SchemaConfig
     *
     * @api
     */
    public function setTypeLoader(callable $typeLoader)
    {
        $this->typeLoader = $typeLoader;

        return $this;
    }

    /**
     * @return bool
     */
    public function getAssumeValid()
    {
        return $this->assumeValid;
    }

    /**
     * @param bool $assumeValid
     *
     * @return SchemaConfig
     */
    public function setAssumeValid($assumeValid)
    {
        $this->assumeValid = $assumeValid;

        return $this;
    }

    /**
     * @return SchemaTypeExtensionNode[]
     */
    public function getExtensionASTNodes()
    {
        return $this->extensionASTNodes;
    }

    /**
     * @param SchemaTypeExtensionNode[] $extensionASTNodes
     */
    public function setExtensionASTNodes(array $extensionASTNodes)
    {
        $this->extensionASTNodes = $extensionASTNodes;
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Validator\Rules;

use GraphQL\Error\Error;
use GraphQL\Language\AST\ArgumentNode;
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\Language\Printer;
use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\OutputType;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\PairSet;
use GraphQL\Utils\TypeInfo;
use GraphQL\Validator\ValidationContext;
use SplObjectStorage;
use function array_keys;
use function array_map;
use function array_merge;
use function array_reduce;
use function count;
use function implode;
use function is_array;
use function sprintf;

class OverlappingFieldsCanBeMerged extends ValidationRule
{
    /**
     * A memoization for when two fragments are compared "between" each other for
     * conflicts. Two fragments may be compared many times, so memoizing this can
     * dramatically improve the performance of this validator.
     *
     * @var PairSet
     */
    private $comparedFragmentPairs;

    /**
     * A cache for the "field map" and list of fragment names found in any given
     * selection set. Selection sets may be asked for this information multiple
     * times, so this improves the performance of this validator.
     *
     * @var SplObjectStorage
     */
    private $cachedFieldsAndFragmentNames;

    public function getVisitor(ValidationContext $context)
    {
        $this->comparedFragmentPairs        = new PairSet();
        $this->cachedFieldsAndFragmentNames = new SplObjectStorage();

        return [
            NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) {
                $conflicts = $this->findConflictsWithinSelectionSet(
                    $context,
                    $context->getParentType(),
                    $selectionSet
                );

                foreach ($conflicts as $conflict) {
                    [[$responseName, $reason], $fields1, $fields2] = $conflict;

                    $context->reportError(new Error(
                        self::fieldsConflictMessage($responseName, $reason),
                        array_merge($fields1, $fields2)
                    ));
                }
            },
        ];
    }

    /**
     * Find all conflicts found "within" a selection set, including those found
     * via spreading in fragments. Called when visiting each SelectionSet in the
     * GraphQL Document.
     *
     * @param CompositeType $parentType
     *
     * @return mixed[]
     */
    private function findConflictsWithinSelectionSet(
        ValidationContext $context,
        $parentType,
        SelectionSetNode $selectionSet
    ) {
        [$fieldMap, $fragmentNames] = $this->getFieldsAndFragmentNames(
            $context,
            $parentType,
            $selectionSet
        );

        $conflicts = [];

        // (A) Find find all conflicts "within" the fields of this selection set.
        // Note: this is the *only place* `collectConflictsWithin` is called.
        $this->collectConflictsWithin(
            $context,
            $conflicts,
            $fieldMap
        );

        $fragmentNamesLength = count($fragmentNames);
        if ($fragmentNamesLength !== 0) {
            // (B) Then collect conflicts between these fields and those represented by
            // each spread fragment name found.
            $comparedFragments = [];
            for ($i = 0; $i < $fragmentNamesLength; $i++) {
                $this->collectConflictsBetweenFieldsAndFragment(
                    $context,
                    $conflicts,
                    $comparedFragments,
                    false,
                    $fieldMap,
                    $fragmentNames[$i]
                );
                // (C) Then compare this fragment with all other fragments found in this
                // selection set to collect conflicts between fragments spread together.
                // This compares each item in the list of fragment names to every other item
                // in that same list (except for itself).
                for ($j = $i + 1; $j < $fragmentNamesLength; $j++) {
                    $this->collectConflictsBetweenFragments(
                        $context,
                        $conflicts,
                        false,
                        $fragmentNames[$i],
                        $fragmentNames[$j]
                    );
                }
            }
        }

        return $conflicts;
    }

    /**
     * Given a selection set, return the collection of fields (a mapping of response
     * name to field ASTs and definitions) as well as a list of fragment names
     * referenced via fragment spreads.
     *
     * @param CompositeType $parentType
     *
     * @return mixed[]|SplObjectStorage
     */
    private function getFieldsAndFragmentNames(
        ValidationContext $context,
        $parentType,
        SelectionSetNode $selectionSet
    ) {
        if (isset($this->cachedFieldsAndFragmentNames[$selectionSet])) {
            $cached = $this->cachedFieldsAndFragmentNames[$selectionSet];
        } else {
            $astAndDefs    = [];
            $fragmentNames = [];

            $this->internalCollectFieldsAndFragmentNames(
                $context,
                $parentType,
                $selectionSet,
                $astAndDefs,
                $fragmentNames
            );
            $cached                                            = [$astAndDefs, array_keys($fragmentNames)];
            $this->cachedFieldsAndFragmentNames[$selectionSet] = $cached;
        }

        return $cached;
    }

    /**
     * Algorithm:
     *
     * Conflicts occur when two fields exist in a query which will produce the same
     * response name, but represent differing values, thus creating a conflict.
     * The algorithm below finds all conflicts via making a series of comparisons
     * between fields. In order to compare as few fields as possible, this makes
     * a series of comparisons "within" sets of fields and "between" sets of fields.
     *
     * Given any selection set, a collection produces both a set of fields by
     * also including all inline fragments, as well as a list of fragments
     * referenced by fragment spreads.
     *
     * A) Each selection set represented in the document first compares "within" its
     * collected set of fields, finding any conflicts between every pair of
     * overlapping fields.
     * Note: This is the *only time* that a the fields "within" a set are compared
     * to each other. After this only fields "between" sets are compared.
     *
     * B) Also, if any fragment is referenced in a selection set, then a
     * comparison is made "between" the original set of fields and the
     * referenced fragment.
     *
     * C) Also, if multiple fragments are referenced, then comparisons
     * are made "between" each referenced fragment.
     *
     * D) When comparing "between" a set of fields and a referenced fragment, first
     * a comparison is made between each field in the original set of fields and
     * each field in the the referenced set of fields.
     *
     * E) Also, if any fragment is referenced in the referenced selection set,
     * then a comparison is made "between" the original set of fields and the
     * referenced fragment (recursively referring to step D).
     *
     * F) When comparing "between" two fragments, first a comparison is made between
     * each field in the first referenced set of fields and each field in the the
     * second referenced set of fields.
     *
     * G) Also, any fragments referenced by the first must be compared to the
     * second, and any fragments referenced by the second must be compared to the
     * first (recursively referring to step F).
     *
     * H) When comparing two fields, if both have selection sets, then a comparison
     * is made "between" both selection sets, first comparing the set of fields in
     * the first selection set with the set of fields in the second.
     *
     * I) Also, if any fragment is referenced in either selection set, then a
     * comparison is made "between" the other set of fields and the
     * referenced fragment.
     *
     * J) Also, if two fragments are referenced in both selection sets, then a
     * comparison is made "between" the two fragments.
     */

    /**
     * Given a reference to a fragment, return the represented collection of fields
     * as well as a list of nested fragment names referenced via fragment spreads.
     *
     * @param CompositeType $parentType
     * @param mixed[][][]   $astAndDefs
     * @param bool[]        $fragmentNames
     */
    private function internalCollectFieldsAndFragmentNames(
        ValidationContext $context,
        $parentType,
        SelectionSetNode $selectionSet,
        array &$astAndDefs,
        array &$fragmentNames
    ) {
        foreach ($selectionSet->selections as $selection) {
            switch (true) {
                case $selection instanceof FieldNode:
                    $fieldName = $selection->name->value;
                    $fieldDef  = null;
                    if ($parentType instanceof ObjectType ||
                        $parentType instanceof InterfaceType) {
                        $tmp = $parentType->getFields();
                        if (isset($tmp[$fieldName])) {
                            $fieldDef = $tmp[$fieldName];
                        }
                    }
                    $responseName = $selection->alias ? $selection->alias->value : $fieldName;

                    if (! isset($astAndDefs[$responseName])) {
                        $astAndDefs[$responseName] = [];
                    }
                    $astAndDefs[$responseName][] = [$parentType, $selection, $fieldDef];
                    break;
                case $selection instanceof FragmentSpreadNode:
                    $fragmentNames[$selection->name->value] = true;
                    break;
                case $selection instanceof InlineFragmentNode:
                    $typeCondition      = $selection->typeCondition;
                    $inlineFragmentType = $typeCondition
                        ? TypeInfo::typeFromAST($context->getSchema(), $typeCondition)
                        : $parentType;

                    $this->internalCollectFieldsAndFragmentNames(
                        $context,
                        $inlineFragmentType,
                        $selection->selectionSet,
                        $astAndDefs,
                        $fragmentNames
                    );
                    break;
            }
        }
    }

    /**
     * Collect all Conflicts "within" one collection of fields.
     *
     * @param mixed[][] $conflicts
     * @param mixed[][] $fieldMap
     */
    private function collectConflictsWithin(
        ValidationContext $context,
        array &$conflicts,
        array $fieldMap
    ) {
        // A field map is a keyed collection, where each key represents a response
        // name and the value at that key is a list of all fields which provide that
        // response name. For every response name, if there are multiple fields, they
        // must be compared to find a potential conflict.
        foreach ($fieldMap as $responseName => $fields) {
            // This compares every field in the list to every other field in this list
            // (except to itself). If the list only has one item, nothing needs to
            // be compared.
            $fieldsLength = count($fields);
            if ($fieldsLength <= 1) {
                continue;
            }

            for ($i = 0; $i < $fieldsLength; $i++) {
                for ($j = $i + 1; $j < $fieldsLength; $j++) {
                    $conflict = $this->findConflict(
                        $context,
                        false, // within one collection is never mutually exclusive
                        $responseName,
                        $fields[$i],
                        $fields[$j]
                    );
                    if (! $conflict) {
                        continue;
                    }

                    $conflicts[] = $conflict;
                }
            }
        }
    }

    /**
     * Determines if there is a conflict between two particular fields, including
     * comparing their sub-fields.
     *
     * @param bool    $parentFieldsAreMutuallyExclusive
     * @param string  $responseName
     * @param mixed[] $field1
     * @param mixed[] $field2
     *
     * @return mixed[]|null
     */
    private function findConflict(
        ValidationContext $context,
        $parentFieldsAreMutuallyExclusive,
        $responseName,
        array $field1,
        array $field2
    ) {
        [$parentType1, $ast1, $def1] = $field1;
        [$parentType2, $ast2, $def2] = $field2;

        // If it is known that two fields could not possibly apply at the same
        // time, due to the parent types, then it is safe to permit them to diverge
        // in aliased field or arguments used as they will not present any ambiguity
        // by differing.
        // It is known that two parent types could never overlap if they are
        // different Object types. Interface or Union types might overlap - if not
        // in the current state of the schema, then perhaps in some future version,
        // thus may not safely diverge.
        $areMutuallyExclusive =
            $parentFieldsAreMutuallyExclusive ||
            (
                $parentType1 !== $parentType2 &&
                $parentType1 instanceof ObjectType &&
                $parentType2 instanceof ObjectType
            );

        // The return type for each field.
        $type1 = $def1 === null ? null : $def1->getType();
        $type2 = $def2 === null ? null : $def2->getType();

        if (! $areMutuallyExclusive) {
            // Two aliases must refer to the same field.
            $name1 = $ast1->name->value;
            $name2 = $ast2->name->value;
            if ($name1 !== $name2) {
                return [
                    [$responseName, sprintf('%s and %s are different fields', $name1, $name2)],
                    [$ast1],
                    [$ast2],
                ];
            }

            if (! $this->sameArguments($ast1->arguments ?: [], $ast2->arguments ?: [])) {
                return [
                    [$responseName, 'they have differing arguments'],
                    [$ast1],
                    [$ast2],
                ];
            }
        }

        if ($type1 && $type2 && $this->doTypesConflict($type1, $type2)) {
            return [
                [$responseName, sprintf('they return conflicting types %s and %s', $type1, $type2)],
                [$ast1],
                [$ast2],
            ];
        }

        // Collect and compare sub-fields. Use the same "visited fragment names" list
        // for both collections so fields in a fragment reference are never
        // compared to themselves.
        $selectionSet1 = $ast1->selectionSet;
        $selectionSet2 = $ast2->selectionSet;
        if ($selectionSet1 && $selectionSet2) {
            $conflicts = $this->findConflictsBetweenSubSelectionSets(
                $context,
                $areMutuallyExclusive,
                Type::getNamedType($type1),
                $selectionSet1,
                Type::getNamedType($type2),
                $selectionSet2
            );

            return $this->subfieldConflicts(
                $conflicts,
                $responseName,
                $ast1,
                $ast2
            );
        }

        return null;
    }

    /**
     * @param ArgumentNode[] $arguments1
     * @param ArgumentNode[] $arguments2
     *
     * @return bool
     */
    private function sameArguments($arguments1, $arguments2)
    {
        if (count($arguments1) !== count($arguments2)) {
            return false;
        }
        foreach ($arguments1 as $argument1) {
            $argument2 = null;
            foreach ($arguments2 as $argument) {
                if ($argument->name->value === $argument1->name->value) {
                    $argument2 = $argument;
                    break;
                }
            }
            if (! $argument2) {
                return false;
            }

            if (! $this->sameValue($argument1->value, $argument2->value)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @return bool
     */
    private function sameValue(Node $value1, Node $value2)
    {
        return (! $value1 && ! $value2) || (Printer::doPrint($value1) === Printer::doPrint($value2));
    }

    /**
     * Two types conflict if both types could not apply to a value simultaneously.
     * Composite types are ignored as their individual field types will be compared
     * later recursively. However List and Non-Null types must match.
     *
     * @return bool
     */
    private function doTypesConflict(OutputType $type1, OutputType $type2)
    {
        if ($type1 instanceof ListOfType) {
            return $type2 instanceof ListOfType ?
                $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
                true;
        }
        if ($type2 instanceof ListOfType) {
            return $type1 instanceof ListOfType ?
                $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
                true;
        }
        if ($type1 instanceof NonNull) {
            return $type2 instanceof NonNull ?
                $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
                true;
        }
        if ($type2 instanceof NonNull) {
            return $type1 instanceof NonNull ?
                $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
                true;
        }
        if (Type::isLeafType($type1) || Type::isLeafType($type2)) {
            return $type1 !== $type2;
        }

        return false;
    }

    /**
     * Find all conflicts found between two selection sets, including those found
     * via spreading in fragments. Called when determining if conflicts exist
     * between the sub-fields of two overlapping fields.
     *
     * @param bool          $areMutuallyExclusive
     * @param CompositeType $parentType1
     * @param CompositeType $parentType2
     *
     * @return mixed[][]
     */
    private function findConflictsBetweenSubSelectionSets(
        ValidationContext $context,
        $areMutuallyExclusive,
        $parentType1,
        SelectionSetNode $selectionSet1,
        $parentType2,
        SelectionSetNode $selectionSet2
    ) {
        $conflicts = [];

        [$fieldMap1, $fragmentNames1] = $this->getFieldsAndFragmentNames(
            $context,
            $parentType1,
            $selectionSet1
        );
        [$fieldMap2, $fragmentNames2] = $this->getFieldsAndFragmentNames(
            $context,
            $parentType2,
            $selectionSet2
        );

        // (H) First, collect all conflicts between these two collections of field.
        $this->collectConflictsBetween(
            $context,
            $conflicts,
            $areMutuallyExclusive,
            $fieldMap1,
            $fieldMap2
        );

        // (I) Then collect conflicts between the first collection of fields and
        // those referenced by each fragment name associated with the second.
        $fragmentNames2Length = count($fragmentNames2);
        if ($fragmentNames2Length !== 0) {
            $comparedFragments = [];
            for ($j = 0; $j < $fragmentNames2Length; $j++) {
                $this->collectConflictsBetweenFieldsAndFragment(
                    $context,
                    $conflicts,
                    $comparedFragments,
                    $areMutuallyExclusive,
                    $fieldMap1,
                    $fragmentNames2[$j]
                );
            }
        }

        // (I) Then collect conflicts between the second collection of fields and
        // those referenced by each fragment name associated with the first.
        $fragmentNames1Length = count($fragmentNames1);
        if ($fragmentNames1Length !== 0) {
            $comparedFragments = [];
            for ($i = 0; $i < $fragmentNames1Length; $i++) {
                $this->collectConflictsBetweenFieldsAndFragment(
                    $context,
                    $conflicts,
                    $comparedFragments,
                    $areMutuallyExclusive,
                    $fieldMap2,
                    $fragmentNames1[$i]
                );
            }
        }

        // (J) Also collect conflicts between any fragment names by the first and
        // fragment names by the second. This compares each item in the first set of
        // names to each item in the second set of names.
        for ($i = 0; $i < $fragmentNames1Length; $i++) {
            for ($j = 0; $j < $fragmentNames2Length; $j++) {
                $this->collectConflictsBetweenFragments(
                    $context,
                    $conflicts,
                    $areMutuallyExclusive,
                    $fragmentNames1[$i],
                    $fragmentNames2[$j]
                );
            }
        }

        return $conflicts;
    }

    /**
     * Collect all Conflicts between two collections of fields. This is similar to,
     * but different from the `collectConflictsWithin` function above. This check
     * assumes that `collectConflictsWithin` has already been called on each
     * provided collection of fields. This is true because this validator traverses
     * each individual selection set.
     *
     * @param mixed[][] $conflicts
     * @param bool      $parentFieldsAreMutuallyExclusive
     * @param mixed[]   $fieldMap1
     * @param mixed[]   $fieldMap2
     */
    private function collectConflictsBetween(
        ValidationContext $context,
        array &$conflicts,
        $parentFieldsAreMutuallyExclusive,
        array $fieldMap1,
        array $fieldMap2
    ) {
        // A field map is a keyed collection, where each key represents a response
        // name and the value at that key is a list of all fields which provide that
        // response name. For any response name which appears in both provided field
        // maps, each field from the first field map must be compared to every field
        // in the second field map to find potential conflicts.
        foreach ($fieldMap1 as $responseName => $fields1) {
            if (! isset($fieldMap2[$responseName])) {
                continue;
            }

            $fields2       = $fieldMap2[$responseName];
            $fields1Length = count($fields1);
            $fields2Length = count($fields2);
            for ($i = 0; $i < $fields1Length; $i++) {
                for ($j = 0; $j < $fields2Length; $j++) {
                    $conflict = $this->findConflict(
                        $context,
                        $parentFieldsAreMutuallyExclusive,
                        $responseName,
                        $fields1[$i],
                        $fields2[$j]
                    );
                    if (! $conflict) {
                        continue;
                    }

                    $conflicts[] = $conflict;
                }
            }
        }
    }

    /**
     * Collect all conflicts found between a set of fields and a fragment reference
     * including via spreading in any nested fragments.
     *
     * @param mixed[][] $conflicts
     * @param bool[]    $comparedFragments
     * @param bool      $areMutuallyExclusive
     * @param mixed[][] $fieldMap
     * @param string    $fragmentName
     */
    private function collectConflictsBetweenFieldsAndFragment(
        ValidationContext $context,
        array &$conflicts,
        array &$comparedFragments,
        $areMutuallyExclusive,
        array $fieldMap,
        $fragmentName
    ) {
        if (isset($comparedFragments[$fragmentName])) {
            return;
        }
        $comparedFragments[$fragmentName] = true;

        $fragment = $context->getFragment($fragmentName);
        if (! $fragment) {
            return;
        }

        [$fieldMap2, $fragmentNames2] = $this->getReferencedFieldsAndFragmentNames(
            $context,
            $fragment
        );

        if ($fieldMap === $fieldMap2) {
            return;
        }

        // (D) First collect any conflicts between the provided collection of fields
        // and the collection of fields represented by the given fragment.
        $this->collectConflictsBetween(
            $context,
            $conflicts,
            $areMutuallyExclusive,
            $fieldMap,
            $fieldMap2
        );

        // (E) Then collect any conflicts between the provided collection of fields
        // and any fragment names found in the given fragment.
        $fragmentNames2Length = count($fragmentNames2);
        for ($i = 0; $i < $fragmentNames2Length; $i++) {
            $this->collectConflictsBetweenFieldsAndFragment(
                $context,
                $conflicts,
                $comparedFragments,
                $areMutuallyExclusive,
                $fieldMap,
                $fragmentNames2[$i]
            );
        }
    }

    /**
     * Given a reference to a fragment, return the represented collection of fields
     * as well as a list of nested fragment names referenced via fragment spreads.
     *
     * @return mixed[]|SplObjectStorage
     */
    private function getReferencedFieldsAndFragmentNames(
        ValidationContext $context,
        FragmentDefinitionNode $fragment
    ) {
        // Short-circuit building a type from the AST if possible.
        if (isset($this->cachedFieldsAndFragmentNames[$fragment->selectionSet])) {
            return $this->cachedFieldsAndFragmentNames[$fragment->selectionSet];
        }

        $fragmentType = TypeInfo::typeFromAST($context->getSchema(), $fragment->typeCondition);

        return $this->getFieldsAndFragmentNames(
            $context,
            $fragmentType,
            $fragment->selectionSet
        );
    }

    /**
     * Collect all conflicts found between two fragments, including via spreading in
     * any nested fragments.
     *
     * @param mixed[][] $conflicts
     * @param bool      $areMutuallyExclusive
     * @param string    $fragmentName1
     * @param string    $fragmentName2
     */
    private function collectConflictsBetweenFragments(
        ValidationContext $context,
        array &$conflicts,
        $areMutuallyExclusive,
        $fragmentName1,
        $fragmentName2
    ) {
        // No need to compare a fragment to itself.
        if ($fragmentName1 === $fragmentName2) {
            return;
        }

        // Memoize so two fragments are not compared for conflicts more than once.
        if ($this->comparedFragmentPairs->has(
            $fragmentName1,
            $fragmentName2,
            $areMutuallyExclusive
        )
        ) {
            return;
        }
        $this->comparedFragmentPairs->add(
            $fragmentName1,
            $fragmentName2,
            $areMutuallyExclusive
        );

        $fragment1 = $context->getFragment($fragmentName1);
        $fragment2 = $context->getFragment($fragmentName2);
        if (! $fragment1 || ! $fragment2) {
            return;
        }

        [$fieldMap1, $fragmentNames1] = $this->getReferencedFieldsAndFragmentNames(
            $context,
            $fragment1
        );
        [$fieldMap2, $fragmentNames2] = $this->getReferencedFieldsAndFragmentNames(
            $context,
            $fragment2
        );

        // (F) First, collect all conflicts between these two collections of fields
        // (not including any nested fragments).
        $this->collectConflictsBetween(
            $context,
            $conflicts,
            $areMutuallyExclusive,
            $fieldMap1,
            $fieldMap2
        );

        // (G) Then collect conflicts between the first fragment and any nested
        // fragments spread in the second fragment.
        $fragmentNames2Length = count($fragmentNames2);
        for ($j = 0; $j < $fragmentNames2Length; $j++) {
            $this->collectConflictsBetweenFragments(
                $context,
                $conflicts,
                $areMutuallyExclusive,
                $fragmentName1,
                $fragmentNames2[$j]
            );
        }

        // (G) Then collect conflicts between the second fragment and any nested
        // fragments spread in the first fragment.
        $fragmentNames1Length = count($fragmentNames1);
        for ($i = 0; $i < $fragmentNames1Length; $i++) {
            $this->collectConflictsBetweenFragments(
                $context,
                $conflicts,
                $areMutuallyExclusive,
                $fragmentNames1[$i],
                $fragmentName2
            );
        }
    }

    /**
     * Given a series of Conflicts which occurred between two sub-fields, generate
     * a single Conflict.
     *
     * @param mixed[][] $conflicts
     * @param string    $responseName
     *
     * @return mixed[]|null
     */
    private function subfieldConflicts(
        array $conflicts,
        $responseName,
        FieldNode $ast1,
        FieldNode $ast2
    ) {
        if (count($conflicts) === 0) {
            return null;
        }

        return [
            [
                $responseName,
                array_map(
                    static function ($conflict) {
                        return $conflict[0];
                    },
                    $conflicts
                ),
            ],
            array_reduce(
                $conflicts,
                static function ($allFields, $conflict) {
                    return array_merge($allFields, $conflict[1]);
                },
                [$ast1]
            ),
            array_reduce(
                $conflicts,
                static function ($allFields, $conflict) {
                    return array_merge($allFields, $conflict[2]);
                },
                [$ast2]
            ),
        ];
    }

    /**
     * @param string $responseName
     * @param string $reason
     */
    public static function fieldsConflictMessage($responseName, $reason)
    {
        $reasonMessage = self::reasonMessage($reason);

        return sprintf(
            'Fields "%s" conflict because %s. Use different aliases on the fields to fetch both if this was intentional.',
            $responseName,
            $reasonMessage
        );
    }

    public static function reasonMessage($reason)
    {
        if (is_array($reason)) {
            $tmp = array_map(
                static function ($tmp) {
                    [$responseName, $subReason] = $tmp;

                    $reasonMessage = self::reasonMessage($subReason);

                    return sprintf('subfields "%s" conflict because %s', $responseName, $reasonMessage);
                },
                $reason
            );

            return implode(' and ', $tmp);
        }

        return $reason;
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Type\Definition;

use Exception;
use GraphQL\Error\Error;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Utils\Utils;
use function is_array;
use function is_object;
use function is_scalar;
use function method_exists;

class StringType extends ScalarType
{
    /** @var string */
    public $name = Type::STRING;

    /** @var string */
    public $description =
        'The `String` scalar type represents textual data, represented as UTF-8
character sequences. The String type is most often used by GraphQL to
represent free-form human-readable text.';

    /**
     * @param mixed $value
     *
     * @return mixed|string
     *
     * @throws Error
     */
    public function serialize($value)
    {
        if ($value === true) {
            return 'true';
        }
        if ($value === false) {
            return 'false';
        }
        if ($value === null) {
            return 'null';
        }
        if (is_object($value) && method_exists($value, '__toString')) {
            return (string) $value;
        }
        if (! is_scalar($value)) {
            throw new Error('String cannot represent non scalar value: ' . Utils::printSafe($value));
        }

        return $this->coerceString($value);
    }

    private function coerceString($value)
    {
        if (is_array($value)) {
            throw new Error(
                'String cannot represent an array value: ' .
                Utils::printSafe($value)
            );
        }

        return (string) $value;
    }

    /**
     * @param mixed $value
     *
     * @return string
     *
     * @throws Error
     */
    public function parseValue($value)
    {
        return $this->coerceString($value);
    }

    /**
     * @param Node         $valueNode
     * @param mixed[]|null $variables
     *
     * @return string|null
     *
     * @throws Exception
     */
    public function parseLiteral($valueNode, ?array $variables = null)
    {
        if ($valueNode instanceof StringValueNode) {
            return $valueNode->value;
        }

        // Intentionally without message, as all information already in wrapped Exception
        throw new Exception();
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Validator;

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;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\Visitor;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\TypeInfo;
use SplObjectStorage;
use function array_pop;
use function call_user_func_array;
use function count;

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

    /** @var DocumentNode */
    private $ast;

    /** @var TypeInfo */
    private $typeInfo;

    /** @var Error[] */
    private $errors;

    /** @var FragmentDefinitionNode[] */
    private $fragments;

    /** @var SplObjectStorage */
    private $fragmentSpreads;

    /** @var SplObjectStorage */
    private $recursivelyReferencedFragments;

    /** @var SplObjectStorage */
    private $variableUsages;

    /** @var SplObjectStorage */
    private $recursiveVariableUsages;

    public function __construct(Schema $schema, DocumentNode $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();
    }

    public function reportError(Error $error)
    {
        $this->errors[] = $error;
    }

    /**
     * @return Error[]
     */
    public function getErrors()
    {
        return $this->errors;
    }

    /**
     * @return Schema
     */
    public function getSchema()
    {
        return $this->schema;
    }

    /**
     * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
     */
    public function getRecursiveVariableUsages(OperationDefinitionNode $operation)
    {
        $usages = $this->recursiveVariableUsages[$operation] ?? null;

        if ($usages === null) {
            $usages    = $this->getVariableUsages($operation);
            $fragments = $this->getRecursivelyReferencedFragments($operation);

            $tmp = [$usages];
            foreach ($fragments as $i => $fragment) {
                $tmp[] = $this->getVariableUsages($fragments[$i]);
            }
            $usages                                    = call_user_func_array('array_merge', $tmp);
            $this->recursiveVariableUsages[$operation] = $usages;
        }

        return $usages;
    }

    /**
     * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
     */
    private function getVariableUsages(HasSelectionSet $node)
    {
        $usages = $this->variableUsages[$node] ?? null;

        if ($usages === null) {
            $newUsages = [];
            $typeInfo  = new TypeInfo($this->schema);
            Visitor::visit(
                $node,
                Visitor::visitWithTypeInfo(
                    $typeInfo,
                    [
                        NodeKind::VARIABLE_DEFINITION => static function () {
                            return false;
                        },
                        NodeKind::VARIABLE            => static function (VariableNode $variable) use (
                            &$newUsages,
                            $typeInfo
                        ) {
                            $newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
                        },
                    ]
                )
            );
            $usages                      = $newUsages;
            $this->variableUsages[$node] = $usages;
        }

        return $usages;
    }

    /**
     * @return FragmentDefinitionNode[]
     */
    public function getRecursivelyReferencedFragments(OperationDefinitionNode $operation)
    {
        $fragments = $this->recursivelyReferencedFragments[$operation] ?? null;

        if ($fragments === null) {
            $fragments      = [];
            $collectedNames = [];
            $nodesToVisit   = [$operation];
            while (! empty($nodesToVisit)) {
                $node    = array_pop($nodesToVisit);
                $spreads = $this->getFragmentSpreads($node);
                foreach ($spreads as $spread) {
                    $fragName = $spread->name->value;

                    if (! empty($collectedNames[$fragName])) {
                        continue;
                    }

                    $collectedNames[$fragName] = true;
                    $fragment                  = $this->getFragment($fragName);
                    if (! $fragment) {
                        continue;
                    }

                    $fragments[]    = $fragment;
                    $nodesToVisit[] = $fragment;
                }
            }
            $this->recursivelyReferencedFragments[$operation] = $fragments;
        }

        return $fragments;
    }

    /**
     * @return FragmentSpreadNode[]
     */
    public function getFragmentSpreads(HasSelectionSet $node)
    {
        $spreads = $this->fragmentSpreads[$node] ?? null;
        if ($spreads === null) {
            $spreads = [];
            /** @var SelectionSetNode[] $setsToVisit */
            $setsToVisit = [$node->selectionSet];
            while (! empty($setsToVisit)) {
                $set = array_pop($setsToVisit);

                for ($i = 0, $selectionCount = count($set->selections); $i < $selectionCount; $i++) {
                    $selection = $set->selections[$i];
                    if ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
                        $spreads[] = $selection;
                    } elseif ($selection->selectionSet) {
                        $setsToVisit[] = $selection->selectionSet;
                    }
                }
            }
            $this->fragmentSpreads[$node] = $spreads;
        }

        return $spreads;
    }

    /**
     * @param string $name
     *
     * @return FragmentDefinitionNode|null
     */
    public function getFragment($name)
    {
        $fragments = $this->fragments;
        if (! $fragments) {
            $fragments = [];
            foreach ($this->getDocument()->definitions as $statement) {
                if ($statement->kind !== NodeKind::FRAGMENT_DEFINITION) {
                    continue;
                }

                $fragments[$statement->name->value] = $statement;
            }
            $this->fragments = $fragments;
        }

        return $fragments[$name] ?? null;
    }

    /**
     * @return DocumentNode
     */
    public function getDocument()
    {
        return $this->ast;
    }

    /**
     * Returns OutputType
     *
     * @return Type
     */
    public function getType()
    {
        return $this->typeInfo->getType();
    }

    /**
     * @return Type
     */
    public function getParentType()
    {
        return $this->typeInfo->getParentType();
    }

    /**
     * @return InputType
     */
    public function getInputType()
    {
        return $this->typeInfo->getInputType();
    }

    /**
     * @return InputType
     */
    public function getParentInputType()
    {
        return $this->typeInfo->getParentInputType();
    }

    /**
     * @return FieldDefinition
     */
    public function getFieldDef()
    {
        return $this->typeInfo->getFieldDef();
    }

    public function getDirective()
    {
        return $this->typeInfo->getDirective();
    }

    public function getArgument()
    {
        return $this->typeInfo->getArgument();
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Validator\Rules;

use GraphQL\Error\Error;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
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 ValidationRule
{
    public function getVisitor(ValidationContext $context)
    {
        return [
            NodeKind::FIELD => function (FieldNode $node) use ($context) {
                $type = $context->getParentType();
                if (! $type) {
                    return;
                }

                $fieldDef = $context->getFieldDef();
                if ($fieldDef) {
                    return;
                }

                // This isn't valid. Let's find suggestions, if any.
                $schema    = $context->getSchema();
                $fieldName = $node->name->value;
                // First determine if there are any suggested types to condition on.
                $suggestedTypeNames = $this->getSuggestedTypeNames(
                    $schema,
                    $type,
                    $fieldName
                );
                // If there are no suggested types, then perhaps this was a typo?
                $suggestedFieldNames = $suggestedTypeNames
                    ? []
                    : $this->getSuggestedFieldNames(
                        $schema,
                        $type,
                        $fieldName
                    );

                // Report an error, including helpful suggestions.
                $context->reportError(new Error(
                    static::undefinedFieldMessage(
                        $node->name->value,
                        $type->name,
                        $suggestedTypeNames,
                        $suggestedFieldNames
                    ),
                    [$node]
                ));
            },
        ];
    }

    /**
     * Go through all of the implementations of type, as well as the interfaces
     * that they implement. If any of those types include the provided field,
     * suggest them, sorted by how often the type is referenced, starting
     * with Interfaces.
     *
     * @param ObjectType|InterfaceType $type
     * @param string                   $fieldName
     *
     * @return string[]
     */
    private function getSuggestedTypeNames(Schema $schema, $type, $fieldName)
    {
        if (Type::isAbstractType($type)) {
            $suggestedObjectTypes = [];
            $interfaceUsageCount  = [];

            foreach ($schema->getPossibleTypes($type) as $possibleType) {
                $fields = $possibleType->getFields();
                if (! isset($fields[$fieldName])) {
                    continue;
                }
                // This object type defines this field.
                $suggestedObjectTypes[] = $possibleType->name;
                foreach ($possibleType->getInterfaces() as $possibleInterface) {
                    $fields = $possibleInterface->getFields();
                    if (! isset($fields[$fieldName])) {
                        continue;
                    }
                    // This interface type defines this field.
                    $interfaceUsageCount[$possibleInterface->name] =
                        ! isset($interfaceUsageCount[$possibleInterface->name])
                            ? 0
                            : $interfaceUsageCount[$possibleInterface->name] + 1;
                }
            }

            // Suggest interface types based on how common they are.
            arsort($interfaceUsageCount);
            $suggestedInterfaceTypes = array_keys($interfaceUsageCount);

            // Suggest both interface and object types.
            return array_merge($suggestedInterfaceTypes, $suggestedObjectTypes);
        }

        // Otherwise, must be an Object type, which does not have possible fields.
        return [];
    }

    /**
     * For the field name provided, determine if there are any similar field names
     * that may be the result of a typo.
     *
     * @param ObjectType|InterfaceType $type
     * @param string                   $fieldName
     *
     * @return array|string[]
     */
    private function getSuggestedFieldNames(Schema $schema, $type, $fieldName)
    {
        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;
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Type\Definition;

use Exception;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
use GraphQL\Utils\Utils;
use function call_user_func;
use function is_array;
use function is_callable;
use function is_string;
use function sprintf;

class InputObjectType extends Type implements InputType, NullableType, NamedType
{
    /** @var InputObjectTypeDefinitionNode|null */
    public $astNode;

    /** @var InputObjectField[] */
    private $fields;

    /** @var InputObjectTypeExtensionNode[] */
    public $extensionASTNodes;

    /**
     * @param mixed[] $config
     */
    public function __construct(array $config)
    {
        if (! isset($config['name'])) {
            $config['name'] = $this->tryInferName();
        }

        Utils::invariant(is_string($config['name']), 'Must provide name.');

        $this->config            = $config;
        $this->name              = $config['name'];
        $this->astNode           = $config['astNode'] ?? null;
        $this->description       = $config['description'] ?? null;
        $this->extensionASTNodes = $config['extensionASTNodes'] ?? null;
    }

    /**
     * @param string $name
     *
     * @return InputObjectField
     *
     * @throws Exception
     */
    public function getField($name)
    {
        if ($this->fields === null) {
            $this->getFields();
        }
        Utils::invariant(isset($this->fields[$name]), "Field '%s' is not defined for type '%s'", $name, $this->name);

        return $this->fields[$name];
    }

    /**
     * @return InputObjectField[]
     */
    public function getFields()
    {
        if ($this->fields === null) {
            $this->fields = [];
            $fields       = $this->config['fields'] ?? [];
            $fields       = is_callable($fields) ? call_user_func($fields) : $fields;

            if (! is_array($fields)) {
                throw new InvariantViolation(
                    sprintf('%s fields must be an array or a callable which returns such an array.', $this->name)
                );
            }

            foreach ($fields as $name => $field) {
                if ($field instanceof Type) {
                    $field = ['type' => $field];
                }
                $field                      = new InputObjectField($field + ['name' => $name]);
                $this->fields[$field->name] = $field;
            }
        }

        return $this->fields;
    }

    /**
     * Validates type config and throws if one of type options is invalid.
     * Note: this method is shallow, it won't validate object fields and their arguments.
     *
     * @throws InvariantViolation
     */
    public function assertValid()
    {
        parent::assertValid();

        Utils::invariant(
            ! empty($this->getFields()),
            sprintf(
                '%s fields must be an associative array with field names as keys or a callable which returns such an array.',
                $this->name
            )
        );

        foreach ($this->getFields() as $field) {
            $field->assertValid($this);
        }
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Executor;

use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use JsonSerializable;
use function array_map;

/**
 * Returned after [query execution](executing-queries.md).
 * Represents both - result of successful execution and of a failed one
 * (with errors collected in `errors` prop)
 *
 * Could be converted to [spec-compliant](https://facebook.github.io/graphql/#sec-Response-Format)
 * serializable array using `toArray()`
 */
class ExecutionResult implements JsonSerializable
{
    /**
     * Data collected from resolvers during query execution
     *
     * @api
     * @var mixed[]
     */
    public $data;

    /**
     * Errors registered during query execution.
     *
     * If an error was caused by exception thrown in resolver, $error->getPrevious() would
     * contain original exception.
     *
     * @api
     * @var Error[]
     */
    public $errors;

    /**
     * User-defined serializable array of extensions included in serialized result.
     * Conforms to
     *
     * @api
     * @var mixed[]
     */
    public $extensions;

    /** @var callable */
    private $errorFormatter;

    /** @var callable */
    private $errorsHandler;

    /**
     * @param mixed[] $data
     * @param Error[] $errors
     * @param mixed[] $extensions
     */
    public function __construct($data = null, array $errors = [], array $extensions = [])
    {
        $this->data       = $data;
        $this->errors     = $errors;
        $this->extensions = $extensions;
    }

    /**
     * Define custom error formatting (must conform to http://facebook.github.io/graphql/#sec-Errors)
     *
     * Expected signature is: function (GraphQL\Error\Error $error): array
     *
     * Default formatter is "GraphQL\Error\FormattedError::createFromException"
     *
     * Expected returned value must be an array:
     * array(
     *    'message' => 'errorMessage',
     *    // ... other keys
     * );
     *
     * @return self
     *
     * @api
     */
    public function setErrorFormatter(callable $errorFormatter)
    {
        $this->errorFormatter = $errorFormatter;

        return $this;
    }

    /**
     * Define custom logic for error handling (filtering, logging, etc).
     *
     * Expected handler signature is: function (array $errors, callable $formatter): array
     *
     * Default handler is:
     * function (array $errors, callable $formatter) {
     *     return array_map($formatter, $errors);
     * }
     *
     * @return self
     *
     * @api
     */
    public function setErrorsHandler(callable $handler)
    {
        $this->errorsHandler = $handler;

        return $this;
    }

    /**
     * @return mixed[]
     */
    public function jsonSerialize()
    {
        return $this->toArray();
    }

    /**
     * Converts GraphQL query result to spec-compliant serializable array using provided
     * errors handler and formatter.
     *
     * If debug argument is passed, output of error formatter is enriched which debugging information
     * ("debugMessage", "trace" keys depending on flags).
     *
     * $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from
     * GraphQL\Error\Debug
     *
     * @param bool|int $debug
     *
     * @return mixed[]
     *
     * @api
     */
    public function toArray($debug = false)
    {
        $result = [];

        if (! empty($this->errors)) {
            $errorsHandler = $this->errorsHandler ?: static function (array $errors, callable $formatter) {
                return array_map($formatter, $errors);
            };

            $result['errors'] = $errorsHandler(
                $this->errors,
                FormattedError::prepareFormatter($this->errorFormatter, $debug)
            );
        }

        if ($this->data !== null) {
            $result['data'] = $this->data;
        }

        if (! empty($this->extensions)) {
            $result['extensions'] = $this->extensions;
        }

        return $result;
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Utils;

use ArrayAccess;
use Exception;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\ListTypeNode;
use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\NonNullTypeNode;
use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\ObjectFieldNode;
use GraphQL\Language\AST\ObjectValueNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\IDType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use stdClass;
use Throwable;
use Traversable;
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 property_exists;

/**
 * Various utilities dealing with AST
 */
class AST
{
    /**
     * Convert representation of AST as an associative array to instance of GraphQL\Language\AST\Node.
     *
     * For example:
     *
     * ```php
     * AST::fromArray([
     *     'kind' => 'ListValue',
     *     'values' => [
     *         ['kind' => 'StringValue', 'value' => 'my str'],
     *         ['kind' => 'StringValue', 'value' => 'my other str']
     *     ],
     *     'loc' => ['start' => 21, 'end' => 25]
     * ]);
     * ```
     *
     * Will produce instance of `ListValueNode` where `values` prop is a lazily-evaluated `NodeList`
     * returning instances of `StringValueNode` on access.
     *
     * This is a reverse operation for AST::toArray($node)
     *
     * @param mixed[] $node
     *
     * @api
     */
    public static function fromArray(array $node) : Node
    {
        if (! isset($node['kind']) || ! isset(NodeKind::$classMap[$node['kind']])) {
            throw new InvariantViolation('Unexpected node structure: ' . Utils::printSafeJson($node));
        }

        $kind     = $node['kind'] ?? null;
        $class    = NodeKind::$classMap[$kind];
        $instance = new $class([]);

        if (isset($node['loc'], $node['loc']['start'], $node['loc']['end'])) {
            $instance->loc = Location::create($node['loc']['start'], $node['loc']['end']);
        }

        foreach ($node as $key => $value) {
            if ($key === 'loc' || $key === 'kind') {
                continue;
            }
            if (is_array($value)) {
                if (isset($value[0]) || empty($value)) {
                    $value = new NodeList($value);
                } else {
                    $value = self::fromArray($value);
                }
            }
            $instance->{$key} = $value;
        }

        return $instance;
    }

    /**
     * Convert AST node to serializable array
     *
     * @return mixed[]
     *
     * @api
     */
    public static function toArray(Node $node)
    {
        return $node->toArray(true);
    }

    /**
     * Produces a GraphQL Value AST given a PHP value.
     *
     * Optionally, a GraphQL type may be provided, which will be used to
     * disambiguate between value primitives.
     *
     * | PHP Value     | GraphQL Value        |
     * | ------------- | -------------------- |
     * | Object        | Input Object         |
     * | Assoc Array   | Input Object         |
     * | Array         | List                 |
     * | Boolean       | Boolean              |
     * | String        | String / Enum Value  |
     * | Int           | Int                  |
     * | Float         | Int / Float          |
     * | Mixed         | Enum Value           |
     * | null          | NullValue            |
     *
     * @param Type|mixed|null $value
     *
     * @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode
     *
     * @api
     */
    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;
        }

        if ($value === null) {
            return new NullValueNode([]);
        }

        // Convert PHP array to GraphQL list. If the GraphQLType is a list, but
        // the value is not an array, convert the value using the list's item type.
        if ($type instanceof ListOfType) {
            $itemType = $type->getWrappedType();
            if (is_array($value) || ($value instanceof Traversable)) {
                $valuesNodes = [];
                foreach ($value as $item) {
                    $itemNode = self::astFromValue($item, $itemType);
                    if (! $itemNode) {
                        continue;
                    }

                    $valuesNodes[] = $itemNode;
                }

                return new ListValueNode(['values' => $valuesNodes]);
            }

            return self::astFromValue($value, $itemType);
        }

        // Populate the fields of the input object by creating ASTs from each value
        // in the PHP object according to the fields in the input type.
        if ($type instanceof InputObjectType) {
            $isArray     = is_array($value);
            $isArrayLike = $isArray || $value instanceof ArrayAccess;
            if ($value === null || (! $isArrayLike && ! is_object($value))) {
                return null;
            }
            $fields     = $type->getFields();
            $fieldNodes = [];
            foreach ($fields as $fieldName => $field) {
                if ($isArrayLike) {
                    $fieldValue = $value[$fieldName] ?? null;
                } else {
                    $fieldValue = $value->{$fieldName} ?? null;
                }

                // Have to check additionally if key exists, since we differentiate between
                // "no key" and "value is null":
                if ($fieldValue !== null) {
                    $fieldExists = true;
                } elseif ($isArray) {
                    $fieldExists = array_key_exists($fieldName, $value);
                } elseif ($isArrayLike) {
                    /** @var ArrayAccess $value */
                    $fieldExists = $value->offsetExists($fieldName);
                } else {
                    $fieldExists = property_exists($value, $fieldName);
                }

                if (! $fieldExists) {
                    continue;
                }

                $fieldNode = self::astFromValue($fieldValue, $field->getType());

                if (! $fieldNode) {
                    continue;
                }

                $fieldNodes[] = new ObjectFieldNode([
                    'name'  => new NameNode(['value' => $fieldName]),
                    'value' => $fieldNode,
                ]);
            }

            return new ObjectValueNode(['fields' => $fieldNodes]);
        }

        if ($type instanceof ScalarType || $type instanceof EnumType) {
            // Since value is an internally represented value, it must be serialized
            // to an externally represented value before converting into an AST.
            try {
                $serialized = $type->serialize($value);
            } catch (Exception $error) {
                if ($error instanceof Error && $type instanceof EnumType) {
                    return null;
                }
                throw $error;
            } catch (Throwable $error) {
                if ($error instanceof Error && $type instanceof EnumType) {
                    return null;
                }
                throw $error;
            }

            // Others serialize based on their corresponding PHP scalar types.
            if (is_bool($serialized)) {
                return new BooleanValueNode(['value' => $serialized]);
            }
            if (is_int($serialized)) {
                return new IntValueNode(['value' => $serialized]);
            }
            if (is_float($serialized)) {
                // int cast with == used for performance reasons
                // phpcs:ignore
                if ((int) $serialized == $serialized) {
                    return new IntValueNode(['value' => $serialized]);
                }

                return new FloatValueNode(['value' => $serialized]);
            }
            if (is_string($serialized)) {
                // Enum types use Enum literals.
                if ($type instanceof EnumType) {
                    return new EnumValueNode(['value' => $serialized]);
                }

                // ID types can use Int literals.
                $asInt = (int) $serialized;
                if ($type instanceof IDType && (string) $asInt === $serialized) {
                    return new IntValueNode(['value' => $serialized]);
                }

                // Use json_encode, which uses the same string encoding as GraphQL,
                // then remove the quotes.
                return new StringValueNode(['value' => $serialized]);
            }

            throw new InvariantViolation('Cannot convert value to AST: ' . Utils::printSafe($serialized));
        }

        throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
    }

    /**
     * Produces a PHP value given a GraphQL Value AST.
     *
     * A GraphQL type must be provided, which will be used to interpret different
     * GraphQL Value literals.
     *
     * Returns `null` when the value could not be validly coerced according to
     * the provided type.
     *
     * | GraphQL Value        | PHP Value     |
     * | -------------------- | ------------- |
     * | Input Object         | Assoc Array   |
     * | List                 | Array         |
     * | Boolean              | Boolean       |
     * | String               | String        |
     * | Int / Float          | Int / Float   |
     * | Enum Value           | Mixed         |
     * | Null Value           | null          |
     *
     * @param ValueNode|null $valueNode
     * @param mixed[]|null   $variables
     *
     * @return mixed[]|stdClass|null
     *
     * @throws Exception
     *
     * @api
     */
    public static function valueFromAST($valueNode, InputType $type, ?array $variables = null)
    {
        $undefined = Utils::undefined();

        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;
        }

        if ($type instanceof NonNull) {
            if ($valueNode instanceof NullValueNode) {
                // Invalid: intentionally return no value.
                return $undefined;
            }

            return self::valueFromAST($valueNode, $type->getWrappedType(), $variables);
        }

        if ($valueNode instanceof NullValueNode) {
            // This is explicitly returning the value null.
            return null;
        }

        if ($valueNode instanceof VariableNode) {
            $variableName = $valueNode->name->value;

            if (! $variables || ! array_key_exists($variableName, $variables)) {
                // No valid return value.
                return $undefined;
            }

            // Note: we're not doing any checking that this variable is correct. We're
            // assuming that this query has been validated and the variable usage here
            // is of the correct type.
            return $variables[$variableName];
        }

        if ($type instanceof ListOfType) {
            $itemType = $type->getWrappedType();

            if ($valueNode instanceof ListValueNode) {
                $coercedValues = [];
                $itemNodes     = $valueNode->values;
                foreach ($itemNodes as $itemNode) {
                    if (self::isMissingVariable($itemNode, $variables)) {
                        // If an array contains a missing variable, it is either coerced to
                        // null or if the item type is non-null, it considered invalid.
                        if ($itemType instanceof NonNull) {
                            // Invalid: intentionally return no value.
                            return $undefined;
                        }
                        $coercedValues[] = null;
                    } else {
                        $itemValue = self::valueFromAST($itemNode, $itemType, $variables);
                        if ($undefined === $itemValue) {
                            // Invalid: intentionally return no value.
                            return $undefined;
                        }
                        $coercedValues[] = $itemValue;
                    }
                }

                return $coercedValues;
            }
            $coercedValue = self::valueFromAST($valueNode, $itemType, $variables);
            if ($undefined === $coercedValue) {
                // Invalid: intentionally return no value.
                return $undefined;
            }

            return [$coercedValue];
        }

        if ($type instanceof InputObjectType) {
            if (! $valueNode instanceof ObjectValueNode) {
                // Invalid: intentionally return no value.
                return $undefined;
            }

            $coercedObj = [];
            $fields     = $type->getFields();
            $fieldNodes = Utils::keyMap(
                $valueNode->fields,
                static function ($field) {
                    return $field->name->value;
                }
            );
            foreach ($fields as $field) {
                /** @var ValueNode $fieldNode */
                $fieldName = $field->name;
                $fieldNode = $fieldNodes[$fieldName] ?? null;

                if ($fieldNode === null || self::isMissingVariable($fieldNode->value, $variables)) {
                    if ($field->defaultValueExists()) {
                        $coercedObj[$fieldName] = $field->defaultValue;
                    } elseif ($field->getType() instanceof NonNull) {
                        // Invalid: intentionally return no value.
                        return $undefined;
                    }
                    continue;
                }

                $fieldValue = self::valueFromAST(
                    $fieldNode !== null ? $fieldNode->value : null,
                    $field->getType(),
                    $variables
                );

                if ($undefined === $fieldValue) {
                    // Invalid: intentionally return no value.
                    return $undefined;
                }
                $coercedObj[$fieldName] = $fieldValue;
            }

            return $coercedObj;
        }

        if ($type instanceof EnumType) {
            if (! $valueNode instanceof EnumValueNode) {
                return $undefined;
            }
            $enumValue = $type->getValue($valueNode->value);
            if (! $enumValue) {
                return $undefined;
            }

            return $enumValue->value;
        }

        if ($type instanceof ScalarType) {
            // Scalars fulfill parsing a literal value via parseLiteral().
            // Invalid values represent a failure to parse correctly, in which case
            // no value is returned.
            try {
                return $type->parseLiteral($valueNode, $variables);
            } catch (Exception $error) {
                return $undefined;
            } catch (Throwable $error) {
                return $undefined;
            }
        }

        throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
    }

    /**
     * Returns true if the provided valueNode is a variable which is not defined
     * in the set of variables.
     *
     * @param ValueNode $valueNode
     * @param mixed[]   $variables
     *
     * @return bool
     */
    private static function isMissingVariable($valueNode, $variables)
    {
        return $valueNode instanceof VariableNode &&
            (count($variables) === 0 || ! array_key_exists($valueNode->name->value, $variables));
    }

    /**
     * Produces a PHP value given a GraphQL Value AST.
     *
     * Unlike `valueFromAST()`, no type is provided. The resulting PHP value
     * will reflect the provided GraphQL value AST.
     *
     * | GraphQL Value        | PHP Value     |
     * | -------------------- | ------------- |
     * | Input Object         | Assoc Array   |
     * | List                 | Array         |
     * | Boolean              | Boolean       |
     * | String               | String        |
     * | Int / Float          | Int / Float   |
     * | Enum                 | Mixed         |
     * | Null                 | null          |
     *
     * @param Node         $valueNode
     * @param mixed[]|null $variables
     *
     * @return mixed
     *
     * @throws Exception
     *
     * @api
     */
    public static function valueFromASTUntyped($valueNode, ?array $variables = null)
    {
        switch (true) {
            case $valueNode instanceof NullValueNode:
                return null;
            case $valueNode instanceof IntValueNode:
                return intval($valueNode->value, 10);
            case $valueNode instanceof FloatValueNode:
                return floatval($valueNode->value);
            case $valueNode instanceof StringValueNode:
            case $valueNode instanceof EnumValueNode:
            case $valueNode instanceof BooleanValueNode:
                return $valueNode->value;
            case $valueNode instanceof ListValueNode:
                return array_map(
                    static function ($node) use ($variables) {
                        return self::valueFromASTUntyped($node, $variables);
                    },
                    iterator_to_array($valueNode->values)
                );
            case $valueNode instanceof ObjectValueNode:
                return array_combine(
                    array_map(
                        static function ($field) {
                            return $field->name->value;
                        },
                        iterator_to_array($valueNode->fields)
                    ),
                    array_map(
                        static 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;
        }

        throw new Error('Unexpected value kind: ' . $valueNode->kind . '.');
    }

    /**
     * Returns type definition for given AST Type node
     *
     * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
     *
     * @return Type|null
     *
     * @throws Exception
     *
     * @api
     */
    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) {
            return $schema->getType($inputTypeNode->name->value);
        }

        throw new Error('Unexpected type kind: ' . $inputTypeNode->kind . '.');
    }

    /**
     * Returns operation type ("query", "mutation" or "subscription") given a document and operation name
     *
     * @param string $operationName
     *
     * @return bool
     *
     * @api
     */
    public static function getOperation(DocumentNode $document, $operationName = null)
    {
        if ($document->definitions) {
            foreach ($document->definitions as $def) {
                if (! ($def instanceof OperationDefinitionNode)) {
                    continue;
                }

                if (! $operationName || (isset($def->name->value) && $def->name->value === $operationName)) {
                    return $def->operation;
                }
            }
        }

        return false;
    }
}


<?php

declare(strict_types=1);

namespace GraphQL;

use GraphQL\Error\Error;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Executor\ReferenceExecutor;
use GraphQL\Experimental\Executor\CoroutineExecutor;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema as SchemaType;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\ValidationRule;
use function array_values;
use function trigger_error;
use const E_USER_DEPRECATED;

/**
 * This is the primary facade for fulfilling GraphQL operations.
 * See [related documentation](executing-queries.md).
 */
class GraphQL
{
    /**
     * Executes graphql query.
     *
     * More sophisticated GraphQL servers, such as those which persist queries,
     * may wish to separate the validation and execution phases to a static time
     * tooling step, and a server runtime step.
     *
     * Available options:
     *
     * schema:
     *    The GraphQL type system to use when validating and executing a query.
     * source:
     *    A GraphQL language formatted string representing the requested operation.
     * rootValue:
     *    The value provided as the first argument to resolver functions on the top
     *    level type (e.g. the query object type).
     * context:
     *    The value provided as the third argument to all resolvers.
     *    Use this to pass current session, user data, etc
     * variableValues:
     *    A mapping of variable name to runtime value to use for all variables
     *    defined in the requestString.
     * operationName:
     *    The name of the operation to use if requestString contains multiple
     *    possible operations. Can be omitted if requestString contains only
     *    one operation.
     * fieldResolver:
     *    A resolver function to use when one is not provided by the schema.
     *    If not provided, the default field resolver is used (which looks for a
     *    value on the source value with the field's name).
     * validationRules:
     *    A set of rules for query validation step. Default value is all available rules.
     *    Empty array would allow to skip query validation (may be convenient for persisted
     *    queries which are validated before persisting and assumed valid during execution)
     *
     * @param string|DocumentNode $source
     * @param mixed               $rootValue
     * @param mixed               $context
     * @param mixed[]|null        $variableValues
     * @param ValidationRule[]    $validationRules
     *
     * @api
     */
    public static function executeQuery(
        SchemaType $schema,
        $source,
        $rootValue = null,
        $context = null,
        $variableValues = null,
        ?string $operationName = null,
        ?callable $fieldResolver = null,
        ?array $validationRules = null
    ) : ExecutionResult {
        $promiseAdapter = new SyncPromiseAdapter();

        $promise = self::promiseToExecute(
            $promiseAdapter,
            $schema,
            $source,
            $rootValue,
            $context,
            $variableValues,
            $operationName,
            $fieldResolver,
            $validationRules
        );

        return $promiseAdapter->wait($promise);
    }

    /**
     * Same as executeQuery(), but requires PromiseAdapter and always returns a Promise.
     * Useful for Async PHP platforms.
     *
     * @param string|DocumentNode   $source
     * @param mixed                 $rootValue
     * @param mixed                 $context
     * @param mixed[]|null          $variableValues
     * @param ValidationRule[]|null $validationRules
     *
     * @api
     */
    public static function promiseToExecute(
        PromiseAdapter $promiseAdapter,
        SchemaType $schema,
        $source,
        $rootValue = null,
        $context = null,
        $variableValues = null,
        ?string $operationName = null,
        ?callable $fieldResolver = null,
        ?array $validationRules = null
    ) : Promise {
        try {
            if ($source instanceof DocumentNode) {
                $documentNode = $source;
            } else {
                $documentNode = Parser::parse(new Source($source ?: '', 'GraphQL'));
            }

            // FIXME
            if (empty($validationRules)) {
                /** @var QueryComplexity $queryComplexity */
                $queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
                $queryComplexity->setRawVariableValues($variableValues);
            } else {
                foreach ($validationRules as $rule) {
                    if (! ($rule instanceof QueryComplexity)) {
                        continue;
                    }

                    $rule->setRawVariableValues($variableValues);
                }
            }

            $validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);

            if (! empty($validationErrors)) {
                return $promiseAdapter->createFulfilled(
                    new ExecutionResult(null, $validationErrors)
                );
            }

            return Executor::promiseToExecute(
                $promiseAdapter,
                $schema,
                $documentNode,
                $rootValue,
                $context,
                $variableValues,
                $operationName,
                $fieldResolver
            );
        } catch (Error $e) {
            return $promiseAdapter->createFulfilled(
                new ExecutionResult(null, [$e])
            );
        }
    }

    /**
     * @deprecated Use executeQuery()->toArray() instead
     *
     * @param string|DocumentNode $source
     * @param mixed               $rootValue
     * @param mixed               $contextValue
     * @param mixed[]|null        $variableValues
     *
     * @return Promise|mixed[]
     */
    public static function execute(
        SchemaType $schema,
        $source,
        $rootValue = null,
        $contextValue = null,
        $variableValues = null,
        ?string $operationName = null
    ) {
        trigger_error(
            __METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement',
            E_USER_DEPRECATED
        );

        $promiseAdapter = Executor::getPromiseAdapter();
        $result         = self::promiseToExecute(
            $promiseAdapter,
            $schema,
            $source,
            $rootValue,
            $contextValue,
            $variableValues,
            $operationName
        );

        if ($promiseAdapter instanceof SyncPromiseAdapter) {
            $result = $promiseAdapter->wait($result)->toArray();
        } else {
            $result = $result->then(static function (ExecutionResult $r) {
                return $r->toArray();
            });
        }

        return $result;
    }

    /**
     * @deprecated renamed to executeQuery()
     *
     * @param string|DocumentNode $source
     * @param mixed               $rootValue
     * @param mixed               $contextValue
     * @param mixed[]|null        $variableValues
     *
     * @return ExecutionResult|Promise
     */
    public static function executeAndReturnResult(
        SchemaType $schema,
        $source,
        $rootValue = null,
        $contextValue = null,
        $variableValues = null,
        ?string $operationName = null
    ) {
        trigger_error(
            __METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement',
            E_USER_DEPRECATED
        );

        $promiseAdapter = Executor::getPromiseAdapter();
        $result         = self::promiseToExecute(
            $promiseAdapter,
            $schema,
            $source,
            $rootValue,
            $contextValue,
            $variableValues,
            $operationName
        );

        if ($promiseAdapter instanceof SyncPromiseAdapter) {
            $result = $promiseAdapter->wait($result);
        }

        return $result;
    }

    /**
     * Returns directives defined in GraphQL spec
     *
     * @return Directive[]
     *
     * @api
     */
    public static function getStandardDirectives() : array
    {
        return array_values(Directive::getInternalDirectives());
    }

    /**
     * Returns types defined in GraphQL spec
     *
     * @return Type[]
     *
     * @api
     */
    public static function getStandardTypes() : array
    {
        return array_values(Type::getStandardTypes());
    }

    /**
     * Replaces standard types with types from this list (matching by name)
     * Standard types not listed here remain untouched.
     *
     * @param Type[] $types
     *
     * @api
     */
    public static function overrideStandardTypes(array $types)
    {
        Type::overrideStandardTypes($types);
    }

    /**
     * Returns standard validation rules implementing GraphQL spec
     *
     * @return ValidationRule[]
     *
     * @api
     */
    public static function getStandardValidationRules() : array
    {
        return array_values(DocumentValidator::defaultRules());
    }

    /**
     * Set default resolver implementation
     *
     * @api
     */
    public static function setDefaultFieldResolver(callable $fn) : void
    {
        Executor::setDefaultFieldResolver($fn);
    }

    public static function setPromiseAdapter(?PromiseAdapter $promiseAdapter = null) : void
    {
        Executor::setPromiseAdapter($promiseAdapter);
    }

    /**
     * Experimental: Switch to the new executor
     */
    public static function useExperimentalExecutor()
    {
        Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
    }

    /**
     * Experimental: Switch back to the default executor
     */
    public static function useReferenceExecutor()
    {
        Executor::setImplementationFactory([ReferenceExecutor::class, 'create']);
    }

    /**
     * Returns directives defined in GraphQL spec
     *
     * @deprecated Renamed to getStandardDirectives
     *
     * @return Directive[]
     */
    public static function getInternalDirectives() : array
    {
        return self::getStandardDirectives();
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Error;

use function trigger_error;
use const E_USER_WARNING;

/**
 * Encapsulates warnings produced by the library.
 *
 * Warnings can be suppressed (individually or all) if required.
 * Also it is possible to override warning handler (which is **trigger_error()** by default)
 */
final class Warning
{
    const WARNING_ASSIGN             = 2;
    const WARNING_CONFIG             = 4;
    const WARNING_FULL_SCHEMA_SCAN   = 8;
    const WARNING_CONFIG_DEPRECATION = 16;
    const WARNING_NOT_A_TYPE         = 32;
    const ALL                        = 63;

    /** @var int */
    private static $enableWarnings = self::ALL;

    /** @var mixed[] */
    private static $warned = [];

    /** @var callable|null */
    private static $warningHandler;

    /**
     * Sets warning handler which can intercept all system warnings.
     * When not set, trigger_error() is used to notify about warnings.
     *
     * @api
     */
    public static function setWarningHandler(?callable $warningHandler = null)
    {
        self::$warningHandler = $warningHandler;
    }

    /**
     * Suppress warning by id (has no effect when custom warning handler is set)
     *
     * Usage example:
     * Warning::suppress(Warning::WARNING_NOT_A_TYPE)
     *
     * When passing true - suppresses all warnings.
     *
     * @param bool|int $suppress
     *
     * @api
     */
    public static function suppress($suppress = true)
    {
        if ($suppress === true) {
            self::$enableWarnings = 0;
        } elseif ($suppress === false) {
            self::$enableWarnings = self::ALL;
        } else {
            self::$enableWarnings &= ~$suppress;
        }
    }

    /**
     * Re-enable previously suppressed warning by id
     *
     * Usage example:
     * Warning::suppress(Warning::WARNING_NOT_A_TYPE)
     *
     * When passing true - re-enables all warnings.
     *
     * @param bool|int $enable
     *
     * @api
     */
    public static function enable($enable = true)
    {
        if ($enable === true) {
            self::$enableWarnings = self::ALL;
        } elseif ($enable === false) {
            self::$enableWarnings = 0;
        } else {
            self::$enableWarnings |= $enable;
        }
    }

    public static function warnOnce($errorMessage, $warningId, $messageLevel = null)
    {
        if (self::$warningHandler) {
            $fn = self::$warningHandler;
            $fn($errorMessage, $warningId);
        } elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) {
            self::$warned[$warningId] = true;
            trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
        }
    }

    public static function warn($errorMessage, $warningId, $messageLevel = null)
    {
        if (self::$warningHandler) {
            $fn = self::$warningHandler;
            $fn($errorMessage, $warningId);
        } elseif ((self::$enableWarnings & $warningId) > 0) {
            trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
        }
    }
}


<?php

declare(strict_types=1);

namespace GraphQL\Type\Definition;

use Exception;
use GraphQL\Error\Error;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Utils\Utils;
use function is_int;
use function is_object;
use function is_scalar;
use function is_string;
use function method_exists;

class IDType extends ScalarType
{
    /** @var string */
    public $name = 'ID';

    /** @var string */
    public $description =
        'The `ID` scalar type represents a unique identifier, often used to
refetch an object or as key for a cache. The ID type appears in a JSON
response as a String; however, it is not intended to be human-readable.
When expected as an input type, any string (such as `"4"`) or integer
(such as `4`) input value will be accepted as an ID.';

    /**
     * @param mixed $value
     *
     * @return string
     *
     * @throws Error
     */
    public function serialize($value)
    {
        if ($value === true) {
            return 'true';
        }
        if ($value === false) {
            return 'false';
        }
        if ($value === null) {
            return 'null';
        }
        if (! is_scalar($value) && (! is_object($value) || ! method_exists($value, '__toString'))) {
            throw new Error('ID type cannot represent non scalar value: ' . Utils::printSafe($value));
        }

        return (string) $value;
    }

    /**
     * @param mixed $value
     *
     * @return string
     *
     * @throws Error
     */
    public function parseValue($value)
    {
        if (is_string($value) || is_int($value)) {
            return (string) $value;
        }

        throw new Error('Cannot represent value as ID: ' . Utils::printSafe($value));
    }

    /**
     * @param Node         $valueNode
     * @param mixed[]|null $variables
     *
     * @return string|null
     *
     * @throws Exception
     */
    public function parseLiteral($valueNode, ?array $variables = null)
    {
        if ($valueNode instanceof StringValueNode || $valueNode instanceof IntValueNode) {
            return $valueNode->value;
        }

        // Intentionally without message, as all information already in wrapped Exception
        throw new Exception();
    }
}