This commit is contained in:
Your Name
2021-07-26 19:46:18 +02:00
parent e7a49138bb
commit aae17f10a6
818 changed files with 70695 additions and 16408 deletions
+11 -50
View File
@@ -4,62 +4,23 @@ declare(strict_types=1);
namespace GraphQL;
use Exception;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use SplQueue;
use Throwable;
class Deferred
class Deferred extends SyncPromise
{
/** @var SplQueue|null */
private static $queue;
/** @var callable */
private $callback;
/** @var SyncPromise */
public $promise;
public function __construct(callable $callback)
/**
* @param callable() : mixed $executor
*/
public static function create(callable $executor) : self
{
$this->callback = $callback;
$this->promise = new SyncPromise();
self::getQueue()->enqueue($this);
return new self($executor);
}
public static function getQueue() : SplQueue
/**
* @param callable() : mixed $executor
*/
public function __construct(callable $executor)
{
if (self::$queue === null) {
self::$queue = new SplQueue();
}
return self::$queue;
}
public static function runQueue() : void
{
$queue = self::getQueue();
while (! $queue->isEmpty()) {
/** @var self $dequeuedNodeValue */
$dequeuedNodeValue = $queue->dequeue();
$dequeuedNodeValue->run();
}
}
public function then($onFulfilled = null, $onRejected = null)
{
return $this->promise->then($onFulfilled, $onRejected);
}
public function run() : void
{
try {
$cb = $this->callback;
$this->promise->resolve($cb());
} catch (Exception $e) {
$this->promise->reject($e);
} catch (Throwable $e) {
$this->promise->reject($e);
}
parent::__construct($executor);
}
}
-16
View File
@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
/**
* Collection of flags for [error debugging](error-handling.md#debugging-tools).
*/
class Debug
{
const INCLUDE_DEBUG_MESSAGE = 1;
const INCLUDE_TRACE = 2;
const RETHROW_INTERNAL_EXCEPTIONS = 4;
const RETHROW_UNSAFE_EXCEPTIONS = 8;
}
+17
View File
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
/**
* Collection of flags for [error debugging](error-handling.md#debugging-tools).
*/
final class DebugFlag
{
public const NONE = 0;
public const INCLUDE_DEBUG_MESSAGE = 1;
public const INCLUDE_TRACE = 2;
public const RETHROW_INTERNAL_EXCEPTIONS = 4;
public const RETHROW_UNSAFE_EXCEPTIONS = 8;
}
+44 -42
View File
@@ -15,6 +15,7 @@ use Traversable;
use function array_filter;
use function array_map;
use function array_values;
use function count;
use function is_array;
use function iterator_to_array;
@@ -38,13 +39,10 @@ class Error extends Exception implements JsonSerializable, ClientAware
const CATEGORY_INTERNAL = 'internal';
/**
* A message describing the Error for debugging purposes.
* Lazily initialized.
*
* @var string
* @var SourceLocation[]
*/
public $message;
/** @var SourceLocation[] */
private $locations;
/**
@@ -72,7 +70,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
*/
private $source;
/** @var int[]|null */
/** @var int[] */
private $positions;
/** @var bool */
@@ -87,16 +85,16 @@ class Error extends Exception implements JsonSerializable, ClientAware
/**
* @param string $message
* @param Node|Node[]|Traversable|null $nodes
* @param mixed[]|null $positions
* @param mixed[] $positions
* @param mixed[]|null $path
* @param Throwable $previous
* @param mixed[] $extensions
*/
public function __construct(
$message,
$message = '',
$nodes = null,
?Source $source = null,
$positions = null,
array $positions = [],
$path = null,
$previous = null,
array $extensions = []
@@ -106,7 +104,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
// Compute list of blame nodes.
if ($nodes instanceof Traversable) {
$nodes = iterator_to_array($nodes);
} elseif ($nodes && ! is_array($nodes)) {
} elseif ($nodes !== null && ! is_array($nodes)) {
$nodes = [$nodes];
}
@@ -114,16 +112,17 @@ class Error extends Exception implements JsonSerializable, ClientAware
$this->source = $source;
$this->positions = $positions;
$this->path = $path;
$this->extensions = $extensions ?: (
$previous && $previous instanceof self
$this->extensions = count($extensions) > 0 ? $extensions : (
$previous instanceof self
? $previous->extensions
: []
);
if ($previous instanceof ClientAware) {
$this->isClientSafe = $previous->isClientSafe();
$this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL;
} elseif ($previous) {
$cat = $previous->getCategory();
$this->category = $cat === '' || $cat === null ? self::CATEGORY_INTERNAL: $cat;
} elseif ($previous !== null) {
$this->isClientSafe = false;
$this->category = self::CATEGORY_INTERNAL;
} else {
@@ -146,25 +145,27 @@ class Error extends Exception implements JsonSerializable, ClientAware
public static function createLocatedError($error, $nodes = null, $path = null)
{
if ($error instanceof self) {
if ($error->path && $error->nodes) {
if ($error->path !== null && $error->nodes !== null && count($error->nodes) !== 0) {
return $error;
}
$nodes = $nodes ?: $error->nodes;
$path = $path ?: $error->path;
$nodes = $nodes ?? $error->nodes;
$path = $path ?? $error->path;
}
$source = $positions = $originalError = null;
$extensions = [];
$source = null;
$originalError = null;
$positions = [];
$extensions = [];
if ($error instanceof self) {
$message = $error->getMessage();
$originalError = $error;
$nodes = $error->nodes ?: $nodes;
$nodes = $error->nodes ?? $nodes;
$source = $error->source;
$positions = $error->positions;
$extensions = $error->extensions;
} elseif ($error instanceof Exception || $error instanceof Throwable) {
} elseif ($error instanceof Throwable) {
$message = $error->getMessage();
$originalError = $error;
} else {
@@ -172,7 +173,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
}
return new static(
$message ?: 'An unknown error occurred.',
$message === '' || $message === null ? 'An unknown error occurred.' : $message,
$nodes,
$source,
$positions,
@@ -206,13 +207,10 @@ class Error extends Exception implements JsonSerializable, ClientAware
return $this->category;
}
/**
* @return Source|null
*/
public function getSource()
public function getSource() : ?Source
{
if ($this->source === null) {
if (! empty($this->nodes[0]) && ! empty($this->nodes[0]->loc)) {
if (isset($this->nodes[0]) && $this->nodes[0]->loc !== null) {
$this->source = $this->nodes[0]->loc->source;
}
}
@@ -223,11 +221,11 @@ class Error extends Exception implements JsonSerializable, ClientAware
/**
* @return int[]
*/
public function getPositions()
public function getPositions() : array
{
if ($this->positions === null && ! empty($this->nodes)) {
if (count($this->positions) === 0 && count($this->nodes ?? []) > 0) {
$positions = array_map(
static function ($node) {
static function ($node) : ?int {
return isset($node->loc) ? $node->loc->start : null;
},
$this->nodes
@@ -235,7 +233,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
$positions = array_filter(
$positions,
static function ($p) {
static function ($p) : bool {
return $p !== null;
}
);
@@ -261,27 +259,29 @@ class Error extends Exception implements JsonSerializable, ClientAware
*
* @api
*/
public function getLocations()
public function getLocations() : array
{
if ($this->locations === null) {
if (! isset($this->locations)) {
$positions = $this->getPositions();
$source = $this->getSource();
$nodes = $this->nodes;
if ($positions && $source) {
if ($source !== null && count($positions) !== 0) {
$this->locations = array_map(
static function ($pos) use ($source) {
static function ($pos) use ($source) : SourceLocation {
return $source->getLocation($pos);
},
$positions
);
} elseif ($nodes) {
} elseif ($nodes !== null && count($nodes) !== 0) {
$locations = array_filter(
array_map(
static function ($node) {
if ($node->loc && $node->loc->source) {
static function ($node) : ?SourceLocation {
if (isset($node->loc->source)) {
return $node->loc->source->getLocation($node->loc->start);
}
return null;
},
$nodes
)
@@ -330,6 +330,8 @@ class Error extends Exception implements JsonSerializable, ClientAware
* @deprecated Use FormattedError::createFromException() instead
*
* @return mixed[]
*
* @codeCoverageIgnore
*/
public function toSerializableArray()
{
@@ -339,18 +341,18 @@ class Error extends Exception implements JsonSerializable, ClientAware
$locations = Utils::map(
$this->getLocations(),
static function (SourceLocation $loc) {
static function (SourceLocation $loc) : array {
return $loc->toSerializableArray();
}
);
if (! empty($locations)) {
if (count($locations) > 0) {
$arr['locations'] = $locations;
}
if (! empty($this->path)) {
if (count($this->path ?? []) > 0) {
$arr['path'] = $this->path;
}
if (! empty($this->extensions)) {
if (count($this->extensions ?? []) > 0) {
$arr['extensions'] = $this->extensions;
}
+45 -67
View File
@@ -6,7 +6,6 @@ namespace GraphQL\Error;
use Countable;
use ErrorException;
use Exception;
use GraphQL\Language\AST\Node;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
@@ -67,10 +66,10 @@ class FormattedError
public static function printError(Error $error)
{
$printedLocations = [];
if ($error->nodes) {
if (count($error->nodes ?? []) !== 0) {
/** @var Node $node */
foreach ($error->nodes as $node) {
if (! $node->loc) {
if ($node->loc === null) {
continue;
}
@@ -83,14 +82,14 @@ class FormattedError
$node->loc->source->getLocation($node->loc->start)
);
}
} elseif ($error->getSource() && $error->getLocations()) {
} elseif ($error->getSource() !== null && count($error->getLocations()) !== 0) {
$source = $error->getSource();
foreach ($error->getLocations() as $location) {
foreach (($error->getLocations() ?? []) as $location) {
$printedLocations[] = self::highlightSourceAtLocation($source, $location);
}
}
return ! $printedLocations
return count($printedLocations) === 0
? $error->getMessage()
: implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
}
@@ -162,11 +161,9 @@ class FormattedError
* This method only exposes exception message when exception implements ClientAware interface
* (or when debug flags are passed).
*
* For a list of available debug flags see GraphQL\Error\Debug constants.
* For a list of available debug flags @see \GraphQL\Error\DebugFlag constants.
*
* @param Throwable $e
* @param bool|int $debug
* @param string $internalErrorMessage
* @param string $internalErrorMessage
*
* @return mixed[]
*
@@ -174,21 +171,15 @@ class FormattedError
*
* @api
*/
public static function createFromException($e, $debug = false, $internalErrorMessage = null)
public static function createFromException(Throwable $exception, int $debug = DebugFlag::NONE, $internalErrorMessage = null) : array
{
Utils::invariant(
$e instanceof Exception || $e instanceof Throwable,
'Expected exception, got %s',
Utils::getVariableType($e)
);
$internalErrorMessage = $internalErrorMessage ?? self::$internalErrorMessage;
$internalErrorMessage = $internalErrorMessage ?: self::$internalErrorMessage;
if ($e instanceof ClientAware) {
if ($exception instanceof ClientAware) {
$formattedError = [
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
'message' => $exception->isClientSafe() ? $exception->getMessage() : $internalErrorMessage,
'extensions' => [
'category' => $e->getCategory(),
'category' => $exception->getCategory(),
],
];
} else {
@@ -200,26 +191,27 @@ class FormattedError
];
}
if ($e instanceof Error) {
if ($exception instanceof Error) {
$locations = Utils::map(
$e->getLocations(),
static function (SourceLocation $loc) {
$exception->getLocations(),
static function (SourceLocation $loc) : array {
return $loc->toSerializableArray();
}
);
if (! empty($locations)) {
if (count($locations) > 0) {
$formattedError['locations'] = $locations;
}
if (! empty($e->path)) {
$formattedError['path'] = $e->path;
if (count($exception->path ?? []) > 0) {
$formattedError['path'] = $exception->path;
}
if (! empty($e->getExtensions())) {
$formattedError['extensions'] = $e->getExtensions() + $formattedError['extensions'];
if (count($exception->getExtensions() ?? []) > 0) {
$formattedError['extensions'] = $exception->getExtensions() + $formattedError['extensions'];
}
}
if ($debug) {
$formattedError = self::addDebugEntries($formattedError, $e, $debug);
if ($debug !== DebugFlag::NONE) {
$formattedError = self::addDebugEntries($formattedError, $exception, $debug);
}
return $formattedError;
@@ -227,54 +219,42 @@ class FormattedError
/**
* Decorates spec-compliant $formattedError with debug entries according to $debug flags
* (see GraphQL\Error\Debug for available flags)
* (@see \GraphQL\Error\DebugFlag for available flags)
*
* @param mixed[] $formattedError
* @param Throwable $e
* @param bool|int $debug
* @param mixed[] $formattedError
*
* @return mixed[]
*
* @throws Throwable
*/
public static function addDebugEntries(array $formattedError, $e, $debug)
public static function addDebugEntries(array $formattedError, Throwable $e, int $debugFlag) : array
{
if (! $debug) {
if ($debugFlag === DebugFlag::NONE) {
return $formattedError;
}
Utils::invariant(
$e instanceof Exception || $e instanceof Throwable,
'Expected exception, got %s',
Utils::getVariableType($e)
);
$debug = (int) $debug;
if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
if (( $debugFlag & DebugFlag::RETHROW_INTERNAL_EXCEPTIONS) !== 0) {
if (! $e instanceof Error) {
throw $e;
}
if ($e->getPrevious()) {
if ($e->getPrevious() !== null) {
throw $e->getPrevious();
}
}
$isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) && $isUnsafe) {
if ($e->getPrevious()) {
throw $e->getPrevious();
}
if (($debugFlag & DebugFlag::RETHROW_UNSAFE_EXCEPTIONS) !== 0 && $isUnsafe && $e->getPrevious() !== null) {
throw $e->getPrevious();
}
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isUnsafe) {
if (($debugFlag & DebugFlag::INCLUDE_DEBUG_MESSAGE) !== 0 && $isUnsafe) {
// Displaying debugMessage as a first entry:
$formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
}
if ($debug & Debug::INCLUDE_TRACE) {
if (($debugFlag & DebugFlag::INCLUDE_TRACE) !== 0) {
if ($e instanceof ErrorException || $e instanceof \Error) {
$formattedError += [
'file' => $e->getFile(),
@@ -282,10 +262,10 @@ class FormattedError
];
}
$isTrivial = $e instanceof Error && ! $e->getPrevious();
$isTrivial = $e instanceof Error && $e->getPrevious() === null;
if (! $isTrivial) {
$debugging = $e->getPrevious() ?: $e;
$debugging = $e->getPrevious() ?? $e;
$formattedError['trace'] = static::toSafeTrace($debugging);
}
}
@@ -296,18 +276,14 @@ class FormattedError
/**
* Prepares final error formatter taking in account $debug flags.
* If initial formatter is not set, FormattedError::createFromException is used
*
* @param bool|int $debug
*
* @return callable|callable
*/
public static function prepareFormatter(?callable $formatter = null, $debug)
public static function prepareFormatter(?callable $formatter, int $debug) : callable
{
$formatter = $formatter ?: static function ($e) {
$formatter = $formatter ?? static function ($e) : array {
return FormattedError::createFromException($e);
};
if ($debug) {
$formatter = static function ($e) use ($formatter, $debug) {
if ($debug !== DebugFlag::NONE) {
$formatter = static function ($e) use ($formatter, $debug) : array {
return FormattedError::addDebugEntries($formatter($e), $e, $debug);
};
}
@@ -338,12 +314,12 @@ class FormattedError
}
return array_map(
static function ($err) {
static function ($err) : array {
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
if (isset($err['function'])) {
$func = $err['function'];
$args = ! empty($err['args']) ? array_map([self::class, 'printVar'], $err['args']) : [];
$args = array_map([self::class, 'printVar'], $err['args'] ?? []);
$funcStr = $func . '(' . implode(', ', $args) . ')';
if (isset($err['class'])) {
@@ -412,9 +388,9 @@ class FormattedError
{
$formatted = ['message' => $error];
if (! empty($locations)) {
if (count($locations) > 0) {
$formatted['locations'] = array_map(
static function ($loc) {
static function ($loc) : array {
return $loc->toArray();
},
$locations
@@ -428,6 +404,8 @@ class FormattedError
* @deprecated as of v0.10.0, use general purpose method createFromException() instead
*
* @return mixed[]
*
* @codeCoverageIgnore
*/
public static function createFromPHPError(ErrorException $e)
{
@@ -13,4 +13,8 @@ use LogicException;
*/
class InvariantViolation extends LogicException
{
public static function shouldNotHappen() : self
{
return new self('This should not have happened');
}
}
+29 -19
View File
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace GraphQL\Error;
use GraphQL\Exception\InvalidArgument;
use function is_int;
use function trigger_error;
use const E_USER_WARNING;
@@ -15,12 +17,12 @@ use const E_USER_WARNING;
*/
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;
public const WARNING_ASSIGN = 2;
public const WARNING_CONFIG = 4;
public const WARNING_FULL_SCHEMA_SCAN = 8;
public const WARNING_CONFIG_DEPRECATION = 16;
public const WARNING_NOT_A_TYPE = 32;
public const ALL = 63;
/** @var int */
private static $enableWarnings = self::ALL;
@@ -37,7 +39,7 @@ final class Warning
*
* @api
*/
public static function setWarningHandler(?callable $warningHandler = null)
public static function setWarningHandler(?callable $warningHandler = null) : void
{
self::$warningHandler = $warningHandler;
}
@@ -54,14 +56,16 @@ final class Warning
*
* @api
*/
public static function suppress($suppress = true)
public static function suppress($suppress = true) : void
{
if ($suppress === true) {
self::$enableWarnings = 0;
} elseif ($suppress === false) {
self::$enableWarnings = self::ALL;
} else {
} elseif (is_int($suppress)) {
self::$enableWarnings &= ~$suppress;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress);
}
}
@@ -77,35 +81,41 @@ final class Warning
*
* @api
*/
public static function enable($enable = true)
public static function enable($enable = true) : void
{
if ($enable === true) {
self::$enableWarnings = self::ALL;
} elseif ($enable === false) {
self::$enableWarnings = 0;
} else {
} elseif (is_int($enable)) {
self::$enableWarnings |= $enable;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable);
}
}
public static function warnOnce($errorMessage, $warningId, $messageLevel = null)
public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
{
if (self::$warningHandler) {
$messageLevel = $messageLevel ?? E_USER_WARNING;
if (self::$warningHandler !== null) {
$fn = self::$warningHandler;
$fn($errorMessage, $warningId);
$fn($errorMessage, $warningId, $messageLevel);
} elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) {
self::$warned[$warningId] = true;
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
trigger_error($errorMessage, $messageLevel);
}
}
public static function warn($errorMessage, $warningId, $messageLevel = null)
public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
{
if (self::$warningHandler) {
$messageLevel = $messageLevel ?? E_USER_WARNING;
if (self::$warningHandler !== null) {
$fn = self::$warningHandler;
$fn($errorMessage, $warningId);
$fn($errorMessage, $warningId, $messageLevel);
} elseif ((self::$enableWarnings & $warningId) > 0) {
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
trigger_error($errorMessage, $messageLevel);
}
}
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Exception;
use InvalidArgumentException;
use function gettype;
use function sprintf;
final class InvalidArgument extends InvalidArgumentException
{
/**
* @param mixed $argument
*/
public static function fromExpectedTypeAndArgument(string $expectedType, $argument) : self
{
return new self(sprintf('Expected type "%s", got "%s"', $expectedType, gettype($argument)));
}
}
@@ -14,7 +14,7 @@ use GraphQL\Type\Schema;
* Data that must be available at all points during query execution.
*
* Namely, schema of the type system that is currently executing,
* and the fragments defined in the query document
* and the fragments defined in the query document.
*
* @internal
*/
@@ -45,28 +45,28 @@ class ExecutionContext
public $errors;
/** @var PromiseAdapter */
public $promises;
public $promiseAdapter;
public function __construct(
$schema,
$fragments,
$root,
$rootValue,
$contextValue,
$operation,
$variables,
$variableValues,
$errors,
$fieldResolver,
$promiseAdapter
) {
$this->schema = $schema;
$this->fragments = $fragments;
$this->rootValue = $root;
$this->rootValue = $rootValue;
$this->contextValue = $contextValue;
$this->operation = $operation;
$this->variableValues = $variables;
$this->errors = $errors ?: [];
$this->variableValues = $variableValues;
$this->errors = $errors ?? [];
$this->fieldResolver = $fieldResolver;
$this->promises = $promiseAdapter;
$this->promiseAdapter = $promiseAdapter;
}
public function addError(Error $error)
+13 -9
View File
@@ -4,10 +4,12 @@ declare(strict_types=1);
namespace GraphQL\Executor;
use GraphQL\Error\DebugFlag;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use JsonSerializable;
use function array_map;
use function count;
/**
* Returned after [query execution](executing-queries.md).
@@ -125,35 +127,37 @@ class ExecutionResult implements JsonSerializable
* 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
* $debug argument must sum of flags from @see \GraphQL\Error\DebugFlag
*
* @return mixed[]
*
* @api
*/
public function toArray($debug = false)
public function toArray(int $debug = DebugFlag::NONE) : array
{
$result = [];
if (! empty($this->errors)) {
$errorsHandler = $this->errorsHandler ?: static function (array $errors, callable $formatter) {
if (count($this->errors ?? []) > 0) {
$errorsHandler = $this->errorsHandler ?? static function (array $errors, callable $formatter) : array {
return array_map($formatter, $errors);
};
$result['errors'] = $errorsHandler(
$handledErrors = $errorsHandler(
$this->errors,
FormattedError::prepareFormatter($this->errorFormatter, $debug)
);
// While we know that there were errors initially, they might have been discarded
if ($handledErrors !== []) {
$result['errors'] = $handledErrors;
}
}
if ($this->data !== null) {
$result['data'] = $this->data;
}
if (! empty($this->extensions)) {
if (count($this->extensions ?? []) > 0) {
$result['extensions'] = $this->extensions;
}
+32 -29
View File
@@ -20,7 +20,7 @@ use function is_object;
*/
class Executor
{
/** @var callable|string[] */
/** @var callable */
private static $defaultFieldResolver = [self::class, 'defaultFieldResolver'];
/** @var PromiseAdapter */
@@ -35,7 +35,7 @@ class Executor
}
/**
* Custom default resolve function.
* Set a custom default resolve function.
*/
public static function setDefaultFieldResolver(callable $fieldResolver)
{
@@ -44,9 +44,12 @@ class Executor
public static function getPromiseAdapter() : PromiseAdapter
{
return self::$defaultPromiseAdapter ?: (self::$defaultPromiseAdapter = new SyncPromiseAdapter());
return self::$defaultPromiseAdapter ?? (self::$defaultPromiseAdapter = new SyncPromiseAdapter());
}
/**
* Set a custom default promise adapter.
*/
public static function setPromiseAdapter(?PromiseAdapter $defaultPromiseAdapter = null)
{
self::$defaultPromiseAdapter = $defaultPromiseAdapter;
@@ -58,9 +61,7 @@ class Executor
}
/**
* Custom executor implementation factory.
*
* Will be called with as
* Set a custom executor implementation factory.
*/
public static function setImplementationFactory(callable $implementationFactory)
{
@@ -70,13 +71,13 @@ class Executor
/**
* Executes DocumentNode against given $schema.
*
* Always returns ExecutionResult and never throws. All errors which occur during operation
* execution are collected in `$result->errors`.
* Always returns ExecutionResult and never throws.
* All errors which occur during operation execution are collected in `$result->errors`.
*
* @param mixed|null $rootValue
* @param mixed|null $contextValue
* @param mixed[]|ArrayAccess|null $variableValues
* @param string|null $operationName
* @param mixed|null $rootValue
* @param mixed|null $contextValue
* @param array<mixed>|ArrayAccess|null $variableValues
* @param string|null $operationName
*
* @return ExecutionResult|Promise
*
@@ -119,10 +120,10 @@ class Executor
*
* Useful for async PHP platforms.
*
* @param mixed|null $rootValue
* @param mixed|null $contextValue
* @param mixed[]|null $variableValues
* @param string|null $operationName
* @param mixed|null $rootValue
* @param mixed|null $contextValue
* @param array<mixed>|null $variableValues
* @param string|null $operationName
*
* @return Promise
*
@@ -149,7 +150,7 @@ class Executor
$contextValue,
$variableValues,
$operationName,
$fieldResolver ?: self::$defaultFieldResolver
$fieldResolver ?? self::$defaultFieldResolver
);
return $executor->doExecute();
@@ -157,31 +158,33 @@ class Executor
/**
* If a resolve function is not given, then a default resolve behavior is used
* which takes the property of the source object of the same name as the field
* which takes the property of the root value of the same name as the field
* and returns it as the result, or if it's a function, returns the result
* of calling that function while passing along args and context.
*
* @param mixed $source
* @param mixed[] $args
* @param mixed|null $context
* @param mixed $objectValue
* @param array<string, mixed> $args
* @param mixed|null $contextValue
*
* @return mixed|null
*/
public static function defaultFieldResolver($source, $args, $context, ResolveInfo $info)
public static function defaultFieldResolver($objectValue, $args, $contextValue, ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
if (is_array($source) || $source instanceof ArrayAccess) {
if (isset($source[$fieldName])) {
$property = $source[$fieldName];
if (is_array($objectValue) || $objectValue instanceof ArrayAccess) {
if (isset($objectValue[$fieldName])) {
$property = $objectValue[$fieldName];
}
} elseif (is_object($source)) {
if (isset($source->{$fieldName})) {
$property = $source->{$fieldName};
} elseif (is_object($objectValue)) {
if (isset($objectValue->{$fieldName})) {
$property = $objectValue->{$fieldName};
}
}
return $property instanceof Closure ? $property($source, $args, $context, $info) : $property;
return $property instanceof Closure
? $property($objectValue, $args, $contextValue, $info)
: $property;
}
}
@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use Amp\Deferred;
use Amp\Failure;
use Amp\Promise as AmpPromise;
use Amp\Success;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use Throwable;
use function Amp\Promise\all;
use function array_replace;
class AmpPromiseAdapter implements PromiseAdapter
{
/**
* @inheritdoc
*/
public function isThenable($value) : bool
{
return $value instanceof AmpPromise;
}
/**
* @inheritdoc
*/
public function convertThenable($thenable) : Promise
{
return new Promise($thenable, $this);
}
/**
* @inheritdoc
*/
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null) : Promise
{
$deferred = new Deferred();
$onResolve = static function (?Throwable $reason, $value) use ($onFulfilled, $onRejected, $deferred) : void {
if ($reason === null && $onFulfilled !== null) {
self::resolveWithCallable($deferred, $onFulfilled, $value);
} elseif ($reason === null) {
$deferred->resolve($value);
} elseif ($onRejected !== null) {
self::resolveWithCallable($deferred, $onRejected, $reason);
} else {
$deferred->fail($reason);
}
};
/** @var AmpPromise $adoptedPromise */
$adoptedPromise = $promise->adoptedPromise;
$adoptedPromise->onResolve($onResolve);
return new Promise($deferred->promise(), $this);
}
/**
* @inheritdoc
*/
public function create(callable $resolver) : Promise
{
$deferred = new Deferred();
$resolver(
static function ($value) use ($deferred) : void {
$deferred->resolve($value);
},
static function (Throwable $exception) use ($deferred) : void {
$deferred->fail($exception);
}
);
return new Promise($deferred->promise(), $this);
}
/**
* @inheritdoc
*/
public function createFulfilled($value = null) : Promise
{
$promise = new Success($value);
return new Promise($promise, $this);
}
/**
* @inheritdoc
*/
public function createRejected($reason) : Promise
{
$promise = new Failure($reason);
return new Promise($promise, $this);
}
/**
* @inheritdoc
*/
public function all(array $promisesOrValues) : Promise
{
/** @var AmpPromise[] $promises */
$promises = [];
foreach ($promisesOrValues as $key => $item) {
if ($item instanceof Promise) {
$promises[$key] = $item->adoptedPromise;
} elseif ($item instanceof AmpPromise) {
$promises[$key] = $item;
}
}
$deferred = new Deferred();
$onResolve = static function (?Throwable $reason, ?array $values) use ($promisesOrValues, $deferred) : void {
if ($reason === null) {
$deferred->resolve(array_replace($promisesOrValues, $values));
return;
}
$deferred->fail($reason);
};
all($promises)->onResolve($onResolve);
return new Promise($deferred->promise(), $this);
}
private static function resolveWithCallable(Deferred $deferred, callable $callback, $argument) : void
{
try {
$result = $callback($argument);
} catch (Throwable $exception) {
$deferred->fail($exception);
return;
}
if ($result instanceof Promise) {
$result = $result->adoptedPromise;
}
$deferred->resolve($result);
}
}
@@ -85,7 +85,7 @@ class ReactPromiseAdapter implements PromiseAdapter
}
);
$promise = all($promisesOrValues)->then(static function ($values) use ($promisesOrValues) {
$promise = all($promisesOrValues)->then(static function ($values) use ($promisesOrValues) : array {
$orderedResults = [];
foreach ($promisesOrValues as $key => $value) {
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use Exception;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Utils\Utils;
use SplQueue;
use Throwable;
@@ -15,6 +14,14 @@ use function method_exists;
/**
* Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
* (using queue to defer promises execution)
*
* Note:
* Library users are not supposed to use SyncPromise class in their resolvers.
* Instead they should use GraphQL\Deferred which enforces $executor callback in the constructor.
*
* Root SyncPromise without explicit $executor will never resolve (actually throw while trying).
* The whole point of Deferred is to ensure it never happens and that any resolver creates
* at least one $executor to start the promise chain.
*/
class SyncPromise
{
@@ -28,7 +35,7 @@ class SyncPromise
/** @var string */
public $state = self::PENDING;
/** @var ExecutionResult|Throwable */
/** @var mixed */
public $result;
/**
@@ -38,16 +45,33 @@ class SyncPromise
*/
private $waiting = [];
public static function runQueue()
public static function runQueue() : void
{
$q = self::$queue;
while ($q && ! $q->isEmpty()) {
while ($q !== null && ! $q->isEmpty()) {
$task = $q->dequeue();
$task();
}
}
public function resolve($value)
/**
* @param callable() : mixed $executor
*/
public function __construct(?callable $executor = null)
{
if ($executor === null) {
return;
}
self::getQueue()->enqueue(function () use ($executor) : void {
try {
$this->resolve($executor());
} catch (Throwable $e) {
$this->reject($e);
}
});
}
public function resolve($value) : self
{
switch ($this->state) {
case self::PENDING:
@@ -56,10 +80,10 @@ class SyncPromise
}
if (is_object($value) && method_exists($value, 'then')) {
$value->then(
function ($resolvedValue) {
function ($resolvedValue) : void {
$this->resolve($resolvedValue);
},
function ($reason) {
function ($reason) : void {
$this->reject($reason);
}
);
@@ -83,9 +107,9 @@ class SyncPromise
return $this;
}
public function reject($reason)
public function reject($reason) : self
{
if (! $reason instanceof Exception && ! $reason instanceof Throwable) {
if (! $reason instanceof Throwable) {
throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
}
@@ -107,7 +131,7 @@ class SyncPromise
return $this;
}
private function enqueueWaitingPromises()
private function enqueueWaitingPromises() : void
{
Utils::invariant(
$this->state !== self::PENDING,
@@ -115,15 +139,13 @@ class SyncPromise
);
foreach ($this->waiting as $descriptor) {
self::getQueue()->enqueue(function () use ($descriptor) {
/** @var $promise self */
self::getQueue()->enqueue(function () use ($descriptor) : void {
/** @var self $promise */
[$promise, $onFulfilled, $onRejected] = $descriptor;
if ($this->state === self::FULFILLED) {
try {
$promise->resolve($onFulfilled === null ? $this->result : $onFulfilled($this->result));
} catch (Exception $e) {
$promise->reject($e);
} catch (Throwable $e) {
$promise->reject($e);
}
@@ -134,8 +156,6 @@ class SyncPromise
} else {
$promise->resolve($onRejected($this->result));
}
} catch (Exception $e) {
$promise->reject($e);
} catch (Throwable $e) {
$promise->reject($e);
}
@@ -145,17 +165,21 @@ class SyncPromise
$this->waiting = [];
}
public static function getQueue()
public static function getQueue() : SplQueue
{
return self::$queue ?: self::$queue = new SplQueue();
return self::$queue ?? self::$queue = new SplQueue();
}
public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
/**
* @param callable(mixed) : mixed $onFulfilled
* @param callable(Throwable) : mixed $onRejected
*/
public function then(?callable $onFulfilled = null, ?callable $onRejected = null) : self
{
if ($this->state === self::REJECTED && ! $onRejected) {
if ($this->state === self::REJECTED && $onRejected === null) {
return $this;
}
if ($this->state === self::FULFILLED && ! $onFulfilled) {
if ($this->state === self::FULFILLED && $onFulfilled === null) {
return $this;
}
$tmp = new self();
@@ -167,4 +191,12 @@ class SyncPromise
return $tmp;
}
/**
* @param callable(Throwable) : mixed $onRejected
*/
public function catch(callable $onRejected) : self
{
return $this->then(null, $onRejected);
}
}
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use Exception;
use GraphQL\Deferred;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Promise\Promise;
@@ -25,7 +23,7 @@ class SyncPromiseAdapter implements PromiseAdapter
*/
public function isThenable($value)
{
return $value instanceof Deferred;
return $value instanceof SyncPromise;
}
/**
@@ -33,11 +31,12 @@ class SyncPromiseAdapter implements PromiseAdapter
*/
public function convertThenable($thenable)
{
if (! $thenable instanceof Deferred) {
if (! $thenable instanceof SyncPromise) {
// End-users should always use Deferred (and don't use SyncPromise directly)
throw new InvariantViolation('Expected instance of GraphQL\Deferred, got ' . Utils::printSafe($thenable));
}
return new Promise($thenable->promise, $this);
return new Promise($thenable, $this);
}
/**
@@ -69,8 +68,6 @@ class SyncPromiseAdapter implements PromiseAdapter
'reject',
]
);
} catch (Exception $e) {
$promise->reject($e);
} catch (Throwable $e) {
$promise->reject($e);
}
@@ -113,7 +110,7 @@ class SyncPromiseAdapter implements PromiseAdapter
if ($promiseOrValue instanceof Promise) {
$result[$index] = null;
$promiseOrValue->then(
static function ($value) use ($index, &$count, $total, &$result, $all) {
static function ($value) use ($index, &$count, $total, &$result, $all) : void {
$result[$index] = $value;
$count++;
if ($count < $total) {
@@ -144,13 +141,11 @@ class SyncPromiseAdapter implements PromiseAdapter
public function wait(Promise $promise)
{
$this->beforeWait($promise);
$dfdQueue = Deferred::getQueue();
$promiseQueue = SyncPromise::getQueue();
$taskQueue = SyncPromise::getQueue();
while ($promise->adoptedPromise->state === SyncPromise::PENDING &&
! ($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
! $taskQueue->isEmpty()
) {
Deferred::runQueue();
SyncPromise::runQueue();
$this->onWait($promise);
}
File diff suppressed because it is too large Load Diff
+140 -84
View File
@@ -6,22 +6,34 @@ namespace GraphQL\Executor;
use GraphQL\Error\Error;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\ObjectValueNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\Printer;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\FieldDefinition;
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 GraphQL\Utils\AST;
@@ -32,6 +44,7 @@ use stdClass;
use Throwable;
use function array_key_exists;
use function array_map;
use function count;
use function sprintf;
class Values
@@ -55,48 +68,9 @@ class Values
/** @var InputType|Type $varType */
$varType = TypeInfo::typeFromAST($schema, $varDefNode->type);
if (Type::isInputType($varType)) {
if (array_key_exists($varName, $inputs)) {
$value = $inputs[$varName];
$coerced = Value::coerceValue($value, $varType, $varDefNode);
/** @var Error[] $coercionErrors */
$coercionErrors = $coerced['errors'];
if (empty($coercionErrors)) {
$coercedValues[$varName] = $coerced['value'];
} else {
$messagePrelude = sprintf(
'Variable "$%s" got invalid value %s; ',
$varName,
Utils::printSafeJson($value)
);
foreach ($coercionErrors as $error) {
$errors[] = new Error(
$messagePrelude . $error->getMessage(),
$error->getNodes(),
$error->getSource(),
$error->getPositions(),
$error->getPath(),
$error->getPrevious(),
$error->getExtensions()
);
}
}
} else {
if ($varType instanceof NonNull) {
$errors[] = new Error(
sprintf(
'Variable "$%s" of required type "%s" was not provided.',
$varName,
$varType
),
[$varDefNode]
);
} elseif ($varDefNode->defaultValue) {
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
}
}
} else {
if (! Type::isInputType($varType)) {
// Must use input types for variables. This should be caught during
// validation, however is checked again here for safety.
$errors[] = new Error(
sprintf(
'Variable "$%s" expected value of type "%s" which cannot be used as an input type.',
@@ -105,10 +79,65 @@ class Values
),
[$varDefNode->type]
);
} else {
$hasValue = array_key_exists($varName, $inputs);
$value = $hasValue ? $inputs[$varName] : Utils::undefined();
if (! $hasValue && ($varDefNode->defaultValue !== null)) {
// If no value was provided to a variable with a default value,
// use the default value.
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
} elseif ((! $hasValue || $value === null) && ($varType instanceof NonNull)) {
// If no value or a nullish value was provided to a variable with a
// non-null type (required), produce an error.
$errors[] = new Error(
sprintf(
$hasValue
? 'Variable "$%s" of non-null type "%s" must not be null.'
: 'Variable "$%s" of required type "%s" was not provided.',
$varName,
Utils::printSafe($varType)
),
[$varDefNode]
);
} elseif ($hasValue) {
if ($value === null) {
// If the explicit value `null` was provided, an entry in the coerced
// values must exist as the value `null`.
$coercedValues[$varName] = null;
} else {
// Otherwise, a non-null value was provided, coerce it to the expected
// type or report an error if coercion fails.
$coerced = Value::coerceValue($value, $varType, $varDefNode);
/** @var Error[] $coercionErrors */
$coercionErrors = $coerced['errors'];
if (count($coercionErrors ?? []) > 0) {
$messagePrelude = sprintf(
'Variable "$%s" got invalid value %s; ',
$varName,
Utils::printSafeJson($value)
);
foreach ($coercionErrors as $error) {
$errors[] = new Error(
$messagePrelude . $error->getMessage(),
$error->getNodes(),
$error->getSource(),
$error->getPositions(),
$error->getPath(),
$error->getPrevious(),
$error->getExtensions()
);
}
} else {
$coercedValues[$varName] = $coerced['value'];
}
}
}
}
}
if (! empty($errors)) {
if (count($errors) > 0) {
return [$errors, null];
}
@@ -132,7 +161,7 @@ class Values
if (isset($node->directives) && $node->directives instanceof NodeList) {
$directiveNode = Utils::find(
$node->directives,
static function (DirectiveNode $directive) use ($directiveDef) {
static function (DirectiveNode $directive) use ($directiveDef) : bool {
return $directive->name->value === $directiveDef->name;
}
);
@@ -159,15 +188,11 @@ class Values
*/
public static function getArgumentValues($def, $node, $variableValues = null)
{
if (empty($def->args)) {
return [];
}
$argumentNodes = $node->arguments;
if (empty($argumentNodes)) {
if (count($def->args) === 0) {
return [];
}
$argumentNodes = $node->arguments;
$argumentValueMap = [];
foreach ($argumentNodes as $argumentNode) {
$argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
@@ -196,27 +221,32 @@ class Values
$argType = $argumentDefinition->getType();
$argumentValueNode = $argumentValueMap[$name] ?? null;
if (! $argumentValueNode) {
if ($argumentDefinition->defaultValueExists()) {
$coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ($argType instanceof NonNull) {
if ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value;
$hasValue = array_key_exists($variableName, $variableValues ?? []);
$isNull = $hasValue ? $variableValues[$variableName] === null : false;
} else {
$hasValue = $argumentValueNode !== null;
$isNull = $argumentValueNode instanceof NullValueNode;
}
if (! $hasValue && $argumentDefinition->defaultValueExists()) {
// If no argument was provided where the definition has a default value,
// use the default value.
$coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ((! $hasValue || $isNull) && ($argType instanceof NonNull)) {
// If no argument or a null value was provided to an argument with a
// non-null type (required), produce a field error.
if ($isNull) {
throw new Error(
'Argument "' . $name . '" of required type ' .
'"' . Utils::printSafe($argType) . '" was not provided.',
'Argument "' . $name . '" of non-null type ' .
'"' . Utils::printSafe($argType) . '" must not be null.',
$referenceNode
);
}
} elseif ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value;
if ($variableValues && array_key_exists($variableName, $variableValues)) {
// Note: this does not check that this variable value is correct.
// This assumes that this query has been validated and the variable
// usage here is of the correct type.
$coercedValues[$name] = $variableValues[$variableName];
} elseif ($argumentDefinition->defaultValueExists()) {
$coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ($argType instanceof NonNull) {
if ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value;
throw new Error(
'Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' .
'provided the variable "$' . $variableName . '" which was not provided ' .
@@ -224,19 +254,38 @@ class Values
[$argumentValueNode]
);
}
} else {
$valueNode = $argumentValueNode;
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
if (Utils::isInvalid($coercedValue)) {
// Note: ValuesOfCorrectType validation should catch this before
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new Error(
'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.',
[$argumentValueNode]
);
throw new Error(
'Argument "' . $name . '" of required type ' .
'"' . Utils::printSafe($argType) . '" was not provided.',
$referenceNode
);
} elseif ($hasValue) {
if ($argumentValueNode instanceof NullValueNode) {
// If the explicit value `null` was provided, an entry in the coerced
// values must exist as the value `null`.
$coercedValues[$name] = null;
} elseif ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value;
Utils::invariant($variableValues !== null, 'Must exist for hasValue to be true.');
// Note: This does no further checking that this variable is correct.
// This assumes that this query has been validated and the variable
// usage here is of the correct type.
$coercedValues[$name] = $variableValues[$variableName] ?? null;
} else {
$valueNode = $argumentValueNode;
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
if (Utils::isInvalid($coercedValue)) {
// Note: ValuesOfCorrectType validation should catch this before
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new Error(
'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.',
[$argumentValueNode]
);
}
$coercedValues[$name] = $coercedValue;
}
$coercedValues[$name] = $coercedValue;
}
}
@@ -246,12 +295,15 @@ class Values
/**
* @deprecated as of 8.0 (Moved to \GraphQL\Utils\AST::valueFromAST)
*
* @param ValueNode $valueNode
* @param mixed[]|null $variables
* @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $valueNode
* @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type
* @param mixed[]|null $variables
*
* @return mixed[]|stdClass|null
*
* @codeCoverageIgnore
*/
public static function valueFromAST($valueNode, InputType $type, ?array $variables = null)
public static function valueFromAST(ValueNode $valueNode, InputType $type, ?array $variables = null)
{
return AST::valueFromAST($valueNode, $type, $variables);
}
@@ -259,9 +311,12 @@ class Values
/**
* @deprecated as of 0.12 (Use coerceValue() directly for richer information)
*
* @param mixed[] $value
* @param mixed[] $value
* @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type
*
* @return string[]
*
* @codeCoverageIgnore
*/
public static function isValidPHPValue($value, InputType $type)
{
@@ -269,10 +324,11 @@ class Values
return $errors
? array_map(
static function (Throwable $error) {
static function (Throwable $error) : string {
return $error->getMessage();
},
$errors
) : [];
)
: [];
}
}
@@ -6,23 +6,31 @@ namespace GraphQL\Experimental\Executor;
use Generator;
use GraphQL\Error\Error;
use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\DefinitionNode;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\ObjectValueNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use function count;
use function sprintf;
/**
@@ -48,7 +56,7 @@ class Collector
/** @var FieldNode[][] */
private $fields;
/** @var string[] */
/** @var array<string, bool> */
private $visitedFragments;
public function __construct(Schema $schema, Runtime $runtime)
@@ -64,8 +72,7 @@ class Collector
foreach ($documentNode->definitions as $definitionNode) {
/** @var DefinitionNode|Node $definitionNode */
if ($definitionNode->kind === NodeKind::OPERATION_DEFINITION) {
/** @var OperationDefinitionNode $definitionNode */
if ($definitionNode instanceof OperationDefinitionNode) {
if ($operationName === null && $this->operation !== null) {
$hasMultipleAssumedOperations = true;
}
@@ -74,8 +81,7 @@ class Collector
) {
$this->operation = $definitionNode;
}
} elseif ($definitionNode->kind === NodeKind::FRAGMENT_DEFINITION) {
/** @var FragmentDefinitionNode $definitionNode */
} elseif ($definitionNode instanceof FragmentDefinitionNode) {
$this->fragments[$definitionNode->name->value] = $definitionNode;
}
}
@@ -122,7 +128,7 @@ class Collector
$fieldName = $fieldNode->name->value;
$argumentValueMap = null;
if (! empty($fieldNode->arguments)) {
if (count($fieldNode->arguments) > 0) {
foreach ($fieldNode->arguments as $argumentNode) {
$argumentValueMap = $argumentValueMap ?? [];
$argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
@@ -149,11 +155,10 @@ class Collector
foreach ($selectionSet->selections as $selection) {
/** @var FieldNode|FragmentSpreadNode|InlineFragmentNode $selection */
if (! empty($selection->directives)) {
if (count($selection->directives) > 0) {
foreach ($selection->directives as $directiveNode) {
if ($directiveNode->name->value === Directive::SKIP_NAME) {
/** @var ValueNode|null $condition */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $condition */
$condition = null;
foreach ($directiveNode->arguments as $argumentNode) {
if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
@@ -173,7 +178,7 @@ class Collector
}
}
} elseif ($directiveNode->name->value === Directive::INCLUDE_NAME) {
/** @var ValueNode|null $condition */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $condition */
$condition = null;
foreach ($directiveNode->arguments as $argumentNode) {
if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
@@ -196,19 +201,15 @@ class Collector
}
}
if ($selection->kind === NodeKind::FIELD) {
/** @var FieldNode $selection */
$resultName = $selection->alias ? $selection->alias->value : $selection->name->value;
if ($selection instanceof FieldNode) {
$resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value;
if (! isset($this->fields[$resultName])) {
$this->fields[$resultName] = [];
}
$this->fields[$resultName][] = $selection;
} elseif ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
/** @var FragmentSpreadNode $selection */
} elseif ($selection instanceof FragmentSpreadNode) {
$fragmentName = $selection->name->value;
if (isset($this->visitedFragments[$fragmentName])) {
@@ -243,15 +244,13 @@ class Collector
continue;
}
} elseif ($conditionType instanceof AbstractType) {
if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
if (! $this->schema->isSubType($conditionType, $runtimeType)) {
continue;
}
}
$this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet);
} elseif ($selection->kind === NodeKind::INLINE_FRAGMENT) {
/** @var InlineFragmentNode $selection */
} elseif ($selection instanceof InlineFragmentNode) {
if ($selection->typeCondition !== null) {
$conditionTypeName = $selection->typeCondition->name->value;
@@ -270,7 +269,7 @@ class Collector
continue;
}
} elseif ($conditionType instanceof AbstractType) {
if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
if (! $this->schema->isSubType($conditionType, $runtimeType)) {
continue;
}
}
@@ -27,7 +27,7 @@ class CoroutineContext
/** @var string[] */
public $path;
/** @var ResolveInfo|null */
/** @var ResolveInfo */
public $resolveInfo;
/** @var string[]|null */
@@ -18,6 +18,8 @@ use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\LeafType;
@@ -25,6 +27,7 @@ use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Introspection;
@@ -34,6 +37,7 @@ use GraphQL\Utils\Utils;
use SplQueue;
use stdClass;
use Throwable;
use function count;
use function is_array;
use function is_string;
use function sprintf;
@@ -70,10 +74,10 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
/** @var string|null */
private $operationName;
/** @var Collector */
/** @var Collector|null */
private $collector;
/** @var Error[] */
/** @var array<Error> */
private $errors;
/** @var SplQueue */
@@ -82,10 +86,10 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
/** @var SplQueue */
private $schedule;
/** @var stdClass */
/** @var stdClass|null */
private $rootResult;
/** @var int */
/** @var int|null */
private $pending;
/** @var callable */
@@ -105,6 +109,9 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
self::$undefined = Utils::undefined();
}
$this->errors = [];
$this->queue = new SplQueue();
$this->schedule = new SplQueue();
$this->schema = $schema;
$this->fieldResolver = $fieldResolver;
$this->promiseAdapter = $promiseAdapter;
@@ -140,11 +147,12 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
private static function resultToArray($value, $emptyObjectAsStdClass = true)
{
if ($value instanceof stdClass) {
$array = [];
foreach ($value as $propertyName => $propertyValue) {
$array = (array) $value;
foreach ($array as $propertyName => $propertyValue) {
$array[$propertyName] = self::resultToArray($propertyValue);
}
if ($emptyObjectAsStdClass && empty($array)) {
if ($emptyObjectAsStdClass && count($array) === 0) {
return new stdClass();
}
@@ -174,17 +182,17 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$this->collector = new Collector($this->schema, $this);
$this->collector->initialize($this->documentNode, $this->operationName);
if (! empty($this->errors)) {
if (count($this->errors) > 0) {
return $this->promiseAdapter->createFulfilled($this->finishExecute(null, $this->errors));
}
[$errors, $coercedVariableValues] = Values::getVariableValues(
$this->schema,
$this->collector->operation->variableDefinitions ?: [],
$this->rawVariableValues ?: []
$this->collector->operation->variableDefinitions ?? [],
$this->rawVariableValues ?? []
);
if (! empty($errors)) {
if (count($errors ?? []) > 0) {
return $this->promiseAdapter->createFulfilled($this->finishExecute(null, $errors));
}
@@ -219,7 +227,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$this->run();
if ($this->pending > 0) {
return $this->promiseAdapter->create(function (callable $resolve) {
return $this->promiseAdapter->create(function (callable $resolve) : void {
$this->doResolve = $resolve;
});
}
@@ -234,9 +242,9 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
private function finishExecute($value, array $errors) : ExecutionResult
{
$this->rootResult = null;
$this->errors = null;
$this->queue = null;
$this->schedule = null;
$this->errors = [];
$this->queue = new SplQueue();
$this->schedule = new SplQueue();
$this->pending = null;
$this->collector = null;
$this->variableValues = null;
@@ -250,6 +258,8 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
/**
* @internal
*
* @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type
*/
public function evaluate(ValueNode $valueNode, InputType $type)
{
@@ -304,13 +314,13 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$this->promiseAdapter
->then(
$value,
function ($value) use ($strand) {
function ($value) use ($strand) : void {
$strand->success = true;
$strand->value = $value;
$this->queue->enqueue($strand);
$this->done();
},
function (Throwable $throwable) use ($strand) {
function (Throwable $throwable) use ($strand) : void {
$strand->success = false;
$strand->value = $throwable;
$this->queue->enqueue($strand);
@@ -396,9 +406,8 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$returnType = $fieldDefinition->getType();
$ctx->resolveInfo = new ResolveInfo(
$ctx->shared->fieldName,
$fieldDefinition,
$ctx->shared->fieldNodes,
$returnType,
$ctx->type,
$ctx->path,
$this->schema,
@@ -498,7 +507,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($type !== $this->schema->getType($type->name)) {
$hint = '';
if ($this->schema->getConfig()->typeLoader) {
if ($this->schema->getConfig()->typeLoader !== null) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type,
@@ -543,7 +552,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($nonNull && $returnValue === null) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Cannot return null for non-nullable field %s.%s.',
'Cannot return null for non-nullable field "%s.%s".',
$ctx->type->name,
$ctx->shared->fieldName
)),
@@ -620,8 +629,9 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
foreach ($value as $itemValue) {
++$index;
$itemPath = $path;
$itemPath[] = $index; // !!! use arrays COW semantics
$itemPath = $path;
$itemPath[] = $index; // !!! use arrays COW semantics
$ctx->resolveInfo->path = $itemPath;
try {
if (! $this->completeValueFast($ctx, $itemType, $itemValue, $itemPath, $itemReturnValue)) {
@@ -646,7 +656,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
} else {
if ($type !== $this->schema->getType($type->name)) {
$hint = '';
if ($this->schema->getConfig()->typeLoader) {
if ($this->schema->getConfig()->typeLoader !== null) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type,
@@ -735,7 +745,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$returnValue = null;
goto CHECKED_RETURN;
} elseif (! $this->schema->isPossibleType($type, $objectType)) {
} elseif (! $this->schema->isSubType($type, $objectType)) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Runtime Object type "%s" is not a possible type for "%s".',
@@ -820,9 +830,16 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
} else {
$childContexts = [];
foreach ($this->collector->collectFields($objectType, $ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)) as $childShared) {
/** @var CoroutineContextShared $childShared */
$fields = [];
if ($this->collector !== null) {
$fields = $this->collector->collectFields(
$objectType,
$ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)
);
}
/** @var CoroutineContextShared $childShared */
foreach ($fields as $childShared) {
$childPath = $path;
$childPath[] = $childShared->resultName; // !!! uses array COW semantics
$childCtx = new CoroutineContext(
@@ -863,7 +880,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($nonNull && $returnValue === null) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Cannot return null for non-nullable field %s.%s.',
'Cannot return null for non-nullable field "%s.%s".',
$ctx->type->name,
$ctx->shared->fieldName
)),
@@ -894,6 +911,11 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
return $ctx->shared->mergedSelectionSet = new SelectionSetNode(['selections' => $selections]);
}
/**
* @param InterfaceType|UnionType $abstractType
*
* @return Generator|ObjectType|Type|null
*/
private function resolveTypeSlow(CoroutineContext $ctx, $value, AbstractType $abstractType)
{
if ($value !== null &&
@@ -904,7 +926,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
return $this->schema->getType($value['__typename']);
}
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader) {
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader !== null) {
Warning::warnOnce(
sprintf(
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
@@ -926,7 +948,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$selectedType = null;
foreach ($possibleTypes as $type) {
$typeCheck = yield $type->isTypeOf($value, $this->contextValue, $ctx->resolveInfo);
if ($selectedType !== null || $typeCheck !== true) {
if ($selectedType !== null || ! $typeCheck) {
continue;
}
@@ -5,13 +5,21 @@ declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\EnumType;
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;
/**
* @internal
*/
interface Runtime
{
/**
* @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type
*/
public function evaluate(ValueNode $valueNode, InputType $type);
public function addError($error);
+25 -11
View File
@@ -16,12 +16,14 @@ use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ScalarType;
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 count;
use function trigger_error;
use const E_USER_DEPRECATED;
@@ -47,9 +49,11 @@ class GraphQL
* 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
* contextValue:
* The context value is provided as an argument to resolver functions after
* field arguments. It is used to pass shared information useful at any point
* during executing this query, for example the currently logged in user and
* connections to databases or other services.
* variableValues:
* A mapping of variable name to runtime value to use for all variables
* defined in the requestString.
@@ -68,7 +72,7 @@ class GraphQL
*
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param mixed $contextValue
* @param mixed[]|null $variableValues
* @param ValidationRule[] $validationRules
*
@@ -78,7 +82,7 @@ class GraphQL
SchemaType $schema,
$source,
$rootValue = null,
$context = null,
$contextValue = null,
$variableValues = null,
?string $operationName = null,
?callable $fieldResolver = null,
@@ -91,7 +95,7 @@ class GraphQL
$schema,
$source,
$rootValue,
$context,
$contextValue,
$variableValues,
$operationName,
$fieldResolver,
@@ -128,11 +132,11 @@ class GraphQL
if ($source instanceof DocumentNode) {
$documentNode = $source;
} else {
$documentNode = Parser::parse(new Source($source ?: '', 'GraphQL'));
$documentNode = Parser::parse(new Source($source ?? '', 'GraphQL'));
}
// FIXME
if (empty($validationRules)) {
if (count($validationRules ?? []) === 0) {
/** @var QueryComplexity $queryComplexity */
$queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
$queryComplexity->setRawVariableValues($variableValues);
@@ -148,7 +152,7 @@ class GraphQL
$validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);
if (! empty($validationErrors)) {
if (count($validationErrors) > 0) {
return $promiseAdapter->createFulfilled(
new ExecutionResult(null, $validationErrors)
);
@@ -180,6 +184,8 @@ class GraphQL
* @param mixed[]|null $variableValues
*
* @return Promise|mixed[]
*
* @codeCoverageIgnore
*/
public static function execute(
SchemaType $schema,
@@ -208,7 +214,7 @@ class GraphQL
if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result)->toArray();
} else {
$result = $result->then(static function (ExecutionResult $r) {
$result = $result->then(static function (ExecutionResult $r) : array {
return $r->toArray();
});
}
@@ -225,6 +231,8 @@ class GraphQL
* @param mixed[]|null $variableValues
*
* @return ExecutionResult|Promise
*
* @codeCoverageIgnore
*/
public static function executeAndReturnResult(
SchemaType $schema,
@@ -285,7 +293,7 @@ class GraphQL
* Replaces standard types with types from this list (matching by name)
* Standard types not listed here remain untouched.
*
* @param Type[] $types
* @param array<string, ScalarType> $types
*
* @api
*/
@@ -326,6 +334,10 @@ class GraphQL
*/
public static function useExperimentalExecutor()
{
trigger_error(
'Experimental Executor is deprecated and will be removed in the next major version',
E_USER_DEPRECATED
);
Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
}
@@ -343,6 +355,8 @@ class GraphQL
* @deprecated Renamed to getStandardDirectives
*
* @return Directive[]
*
* @codeCoverageIgnore
*/
public static function getInternalDirectives() : array
{
@@ -9,7 +9,7 @@ class ArgumentNode extends Node
/** @var string */
public $kind = NodeKind::ARGUMENT;
/** @var ValueNode */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode */
public $value;
/** @var NameNode */
@@ -7,7 +7,7 @@ namespace GraphQL\Language\AST;
/**
* export type DefinitionNode =
* | ExecutableDefinitionNode
* | TypeSystemDefinitionNode; // experimental non-spec addition.
* | TypeSystemDefinitionNode;
*/
interface DefinitionNode
{
@@ -12,12 +12,15 @@ class DirectiveDefinitionNode extends Node implements TypeSystemDefinitionNode
/** @var NameNode */
public $name;
/** @var ArgumentNode[] */
public $arguments;
/** @var NameNode[] */
public $locations;
/** @var StringValueNode|null */
public $description;
/** @var NodeList<InputValueDefinitionNode> */
public $arguments;
/** @var bool */
public $repeatable;
/** @var NodeList<NameNode> */
public $locations;
}
@@ -12,6 +12,6 @@ class DirectiveNode extends Node
/** @var NameNode */
public $name;
/** @var ArgumentNode[] */
/** @var NodeList<ArgumentNode> */
public $arguments;
}
@@ -9,6 +9,6 @@ class DocumentNode extends Node
/** @var string */
public $kind = NodeKind::DOCUMENT;
/** @var NodeList|DefinitionNode[] */
/** @var NodeList<DefinitionNode&Node> */
public $definitions;
}
@@ -12,10 +12,10 @@ class EnumTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var EnumValueDefinitionNode[]|NodeList|null */
/** @var NodeList<EnumValueDefinitionNode> */
public $values;
/** @var StringValueNode|null */
@@ -12,9 +12,9 @@ class EnumTypeExtensionNode extends Node implements TypeExtensionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var EnumValueDefinitionNode[]|null */
/** @var NodeList<EnumValueDefinitionNode> */
public $values;
}
@@ -12,7 +12,7 @@ class EnumValueDefinitionNode extends Node
/** @var NameNode */
public $name;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var StringValueNode|null */
@@ -12,13 +12,13 @@ class FieldDefinitionNode extends Node
/** @var NameNode */
public $name;
/** @var InputValueDefinitionNode[]|NodeList */
/** @var NodeList<InputValueDefinitionNode> */
public $arguments;
/** @var TypeNode */
/** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */
public $type;
/** @var DirectiveNode[]|NodeList */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var StringValueNode|null */
+2 -2
View File
@@ -15,10 +15,10 @@ class FieldNode extends Node implements SelectionNode
/** @var NameNode|null */
public $alias;
/** @var ArgumentNode[]|null */
/** @var NodeList<ArgumentNode> */
public $arguments;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var SelectionSetNode|null */
@@ -16,14 +16,14 @@ class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, H
* Note: fragment variable definitions are experimental and may be changed
* or removed in the future.
*
* @var VariableDefinitionNode[]|NodeList
* @var NodeList<VariableDefinitionNode>
*/
public $variableDefinitions;
/** @var NamedTypeNode */
public $typeCondition;
/** @var DirectiveNode[]|NodeList */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var SelectionSetNode */
@@ -12,6 +12,6 @@ class FragmentSpreadNode extends Node implements SelectionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
}
@@ -4,10 +4,12 @@ declare(strict_types=1);
namespace GraphQL\Language\AST;
/**
* export type DefinitionNode = OperationDefinitionNode
* | FragmentDefinitionNode
*
* @property SelectionSetNode $selectionSet
*/
interface HasSelectionSet
{
/**
* export type DefinitionNode = OperationDefinitionNode
* | FragmentDefinitionNode
*/
}
@@ -12,7 +12,7 @@ class InlineFragmentNode extends Node implements SelectionNode
/** @var NamedTypeNode */
public $typeCondition;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var SelectionSetNode */
@@ -12,10 +12,10 @@ class InputObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var InputValueDefinitionNode[]|null */
/** @var NodeList<InputValueDefinitionNode> */
public $fields;
/** @var StringValueNode|null */
@@ -12,9 +12,9 @@ class InputObjectTypeExtensionNode extends Node implements TypeExtensionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var InputValueDefinitionNode[]|null */
/** @var NodeList<InputValueDefinitionNode> */
public $fields;
}
@@ -12,13 +12,13 @@ class InputValueDefinitionNode extends Node
/** @var NameNode */
public $name;
/** @var TypeNode */
/** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */
public $type;
/** @var ValueNode */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null */
public $defaultValue;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var StringValueNode|null */
@@ -12,10 +12,13 @@ class InterfaceTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var FieldDefinitionNode[]|null */
/** @var NodeList<NamedTypeNode> */
public $interfaces;
/** @var NodeList<FieldDefinitionNode> */
public $fields;
/** @var StringValueNode|null */
@@ -12,9 +12,12 @@ class InterfaceTypeExtensionNode extends Node implements TypeExtensionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var FieldDefinitionNode[]|null */
/** @var NodeList<InterfaceTypeDefinitionNode> */
public $interfaces;
/** @var NodeList<FieldDefinitionNode> */
public $fields;
}
@@ -9,6 +9,6 @@ class ListTypeNode extends Node implements TypeNode
/** @var string */
public $kind = NodeKind::LIST_TYPE;
/** @var Node */
/** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */
public $type;
}
@@ -9,6 +9,6 @@ class ListValueNode extends Node implements ValueNode
/** @var string */
public $kind = NodeKind::LST;
/** @var ValueNode[]|NodeList */
/** @var NodeList<ValueNode&Node> */
public $values;
}
+3 -3
View File
@@ -30,14 +30,14 @@ class Location
/**
* The Token at which this Node begins.
*
* @var Token
* @var Token|null
*/
public $startToken;
/**
* The Token at which this Node ends.
*
* @var Token
* @var Token|null
*/
public $endToken;
@@ -69,7 +69,7 @@ class Location
$this->endToken = $endToken;
$this->source = $source;
if (! $startToken || ! $endToken) {
if ($startToken === null || $endToken === null) {
return;
}
+10 -11
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace GraphQL\Language\AST;
use GraphQL\Utils\Utils;
use function count;
use function get_object_vars;
use function is_array;
use function is_scalar;
@@ -36,15 +37,18 @@ use function json_encode;
*/
abstract class Node
{
/** @var Location */
/** @var Location|null */
public $loc;
/** @var string */
public $kind;
/**
* @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars
*/
public function __construct(array $vars)
{
if (empty($vars)) {
if (count($vars) === 0) {
return;
}
@@ -83,10 +87,7 @@ abstract class Node
return $cloned;
}
/**
* @return string
*/
public function __toString()
public function __toString() : string
{
$tmp = $this->toArray(true);
@@ -94,11 +95,9 @@ abstract class Node
}
/**
* @param bool $recursive
*
* @return mixed[]
*/
public function toArray($recursive = false)
public function toArray(bool $recursive = false) : array
{
if ($recursive) {
return $this->recursiveToArray($this);
@@ -106,7 +105,7 @@ abstract class Node
$tmp = (array) $this;
if ($this->loc) {
if ($this->loc !== null) {
$tmp['loc'] = [
'start' => $this->loc->start,
'end' => $this->loc->end,
@@ -125,7 +124,7 @@ abstract class Node
'kind' => $node->kind,
];
if ($node->loc) {
if ($node->loc !== null) {
$result['loc'] = [
'start' => $node->loc->start,
'end' => $node->loc->end,
+58 -35
View File
@@ -6,31 +6,43 @@ namespace GraphQL\Language\AST;
use ArrayAccess;
use Countable;
use Generator;
use GraphQL\Utils\AST;
use InvalidArgumentException;
use IteratorAggregate;
use Traversable;
use function array_merge;
use function array_splice;
use function count;
use function is_array;
/**
* @template T of Node
* @phpstan-implements ArrayAccess<int|string, T>
* @phpstan-implements IteratorAggregate<T>
*/
class NodeList implements ArrayAccess, IteratorAggregate, Countable
{
/** @var Node[]|mixed[] */
/**
* @var Node[]
* @phpstan-var array<T>
*/
private $nodes;
/**
* @param Node[]|mixed[] $nodes
* @param Node[] $nodes
*
* @return static
* @phpstan-param array<T> $nodes
* @phpstan-return self<T>
*/
public static function create(array $nodes)
public static function create(array $nodes) : self
{
return new static($nodes);
}
/**
* @param Node[]|mixed[] $nodes
* @param Node[] $nodes
*
* @phpstan-param array<T> $nodes
*/
public function __construct(array $nodes)
{
@@ -38,59 +50,75 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
}
/**
* @param mixed $offset
*
* @return bool
* @param int|string $offset
*/
public function offsetExists($offset)
public function offsetExists($offset) : bool
{
return isset($this->nodes[$offset]);
}
/**
* @param mixed $offset
* TODO enable strict typing by changing how the Visitor deals with NodeList.
* Ideally, this function should always return a Node instance.
* However, the Visitor currently allows mutation of the NodeList
* and puts arbitrary values in the NodeList, such as strings.
* We will have to switch to using an array or a less strict
* type instead so we can enable strict typing in this class.
*
* @return mixed
* @param int|string $offset
*
* @phpstan-return T
*/
public function offsetGet($offset)
public function offsetGet($offset)// : Node
{
$item = $this->nodes[$offset];
if (is_array($item) && isset($item['kind'])) {
$this->nodes[$offset] = $item = AST::fromArray($item);
/** @phpstan-var T $node */
$node = AST::fromArray($item);
$this->nodes[$offset] = $node;
}
return $item;
return $this->nodes[$offset];
}
/**
* @param mixed $offset
* @param mixed $value
* @param int|string|null $offset
* @param Node|mixed[] $value
*
* @phpstan-param T|mixed[] $value
*/
public function offsetSet($offset, $value)
public function offsetSet($offset, $value) : void
{
if (is_array($value) && isset($value['kind'])) {
if (is_array($value)) {
/** @phpstan-var T $value */
$value = AST::fromArray($value);
}
// Happens when a Node is pushed via []=
if ($offset === null) {
$this->nodes[] = $value;
return;
}
$this->nodes[$offset] = $value;
}
/**
* @param mixed $offset
* @param int|string $offset
*/
public function offsetUnset($offset)
public function offsetUnset($offset) : void
{
unset($this->nodes[$offset]);
}
/**
* @param int $offset
* @param int $length
* @param mixed $replacement
*
* @return NodeList
* @phpstan-return NodeList<T>
*/
public function splice($offset, $length, $replacement = null)
public function splice(int $offset, int $length, $replacement = null) : NodeList
{
return new NodeList(array_splice($this->nodes, $offset, $length, $replacement));
}
@@ -98,9 +126,10 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
/**
* @param NodeList|Node[] $list
*
* @return NodeList
* @phpstan-param NodeList<T>|array<T> $list
* @phpstan-return NodeList<T>
*/
public function merge($list)
public function merge($list) : NodeList
{
if ($list instanceof self) {
$list = $list->nodes;
@@ -109,20 +138,14 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
return new NodeList(array_merge($this->nodes, $list));
}
/**
* @return Generator
*/
public function getIterator()
public function getIterator() : Traversable
{
foreach ($this->nodes as $key => $_) {
yield $this->offsetGet($key);
}
}
/**
* @return int
*/
public function count()
public function count() : int
{
return count($this->nodes);
}
@@ -9,6 +9,6 @@ class NonNullTypeNode extends Node implements TypeNode
/** @var string */
public $kind = NodeKind::NON_NULL_TYPE;
/** @var NameNode | ListTypeNode */
/** @var NamedTypeNode|ListTypeNode */
public $type;
}
@@ -12,6 +12,6 @@ class ObjectFieldNode extends Node
/** @var NameNode */
public $name;
/** @var ValueNode */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode */
public $value;
}
@@ -12,13 +12,13 @@ class ObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NameNode */
public $name;
/** @var NamedTypeNode[] */
public $interfaces = [];
/** @var NodeList<NamedTypeNode> */
public $interfaces;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var FieldDefinitionNode[]|null */
/** @var NodeList<FieldDefinitionNode> */
public $fields;
/** @var StringValueNode|null */
@@ -12,12 +12,12 @@ class ObjectTypeExtensionNode extends Node implements TypeExtensionNode
/** @var NameNode */
public $name;
/** @var NamedTypeNode[] */
public $interfaces = [];
/** @var NodeList<NamedTypeNode> */
public $interfaces;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var FieldDefinitionNode[] */
/** @var NodeList<FieldDefinitionNode> */
public $fields;
}
@@ -9,6 +9,6 @@ class ObjectValueNode extends Node implements ValueNode
/** @var string */
public $kind = NodeKind::OBJECT;
/** @var ObjectFieldNode[]|NodeList */
/** @var NodeList<ObjectFieldNode> */
public $fields;
}
@@ -9,16 +9,16 @@ class OperationDefinitionNode extends Node implements ExecutableDefinitionNode,
/** @var string */
public $kind = NodeKind::OPERATION_DEFINITION;
/** @var NameNode */
/** @var NameNode|null */
public $name;
/** @var string (oneOf 'query', 'mutation')) */
/** @var string (oneOf 'query', 'mutation', 'subscription')) */
public $operation;
/** @var VariableDefinitionNode[] */
/** @var NodeList<VariableDefinitionNode> */
public $variableDefinitions;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var SelectionSetNode */
@@ -12,7 +12,7 @@ class ScalarTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var StringValueNode|null */
@@ -12,6 +12,6 @@ class ScalarTypeExtensionNode extends Node implements TypeExtensionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
}
@@ -9,9 +9,9 @@ class SchemaDefinitionNode extends Node implements TypeSystemDefinitionNode
/** @var string */
public $kind = NodeKind::SCHEMA_DEFINITION;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var OperationTypeDefinitionNode[] */
/** @var NodeList<OperationTypeDefinitionNode> */
public $operationTypes;
}
@@ -9,9 +9,9 @@ class SchemaTypeExtensionNode extends Node implements TypeExtensionNode
/** @var string */
public $kind = NodeKind::SCHEMA_EXTENSION;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var OperationTypeDefinitionNode[]|null */
/** @var NodeList<OperationTypeDefinitionNode> */
public $operationTypes;
}
@@ -9,6 +9,6 @@ class SelectionSetNode extends Node
/** @var string */
public $kind = NodeKind::SELECTION_SET;
/** @var SelectionNode[] */
/** @var NodeList<SelectionNode&Node> */
public $selections;
}
@@ -12,6 +12,6 @@ class StringValueNode extends Node implements ValueNode
/** @var string */
public $value;
/** @var bool|null */
/** @var bool */
public $block;
}
@@ -10,6 +10,8 @@ namespace GraphQL\Language\AST;
* | TypeDefinitionNode
* | TypeExtensionNode
* | DirectiveDefinitionNode
*
* @property NameNode $name
*/
interface TypeSystemDefinitionNode extends DefinitionNode
{
@@ -12,10 +12,10 @@ class UnionTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var NamedTypeNode[]|null */
/** @var NodeList<NamedTypeNode> */
public $types;
/** @var StringValueNode|null */
@@ -12,9 +12,9 @@ class UnionTypeExtensionNode extends Node implements TypeExtensionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var NamedTypeNode[]|null */
/** @var NodeList<NamedTypeNode> */
public $types;
}
@@ -12,9 +12,12 @@ class VariableDefinitionNode extends Node implements DefinitionNode
/** @var VariableNode */
public $variable;
/** @var TypeNode */
/** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */
public $type;
/** @var ValueNode|null */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null */
public $defaultValue;
/** @var NodeList<DirectiveNode> */
public $directives;
}
@@ -17,6 +17,7 @@ class DirectiveLocation
const FRAGMENT_DEFINITION = 'FRAGMENT_DEFINITION';
const FRAGMENT_SPREAD = 'FRAGMENT_SPREAD';
const INLINE_FRAGMENT = 'INLINE_FRAGMENT';
const VARIABLE_DEFINITION = 'VARIABLE_DEFINITION';
// Type System Definitions
const SCHEMA = 'SCHEMA';
@@ -53,12 +54,7 @@ class DirectiveLocation
self::INPUT_FIELD_DEFINITION => self::INPUT_FIELD_DEFINITION,
];
/**
* @param string $name
*
* @return bool
*/
public static function has($name)
public static function has(string $name) : bool
{
return isset(self::$locations[$name]);
}
+29 -7
View File
@@ -9,8 +9,11 @@ use GraphQL\Utils\BlockString;
use GraphQL\Utils\Utils;
use function chr;
use function hexdec;
use function mb_convert_encoding;
use function ord;
use function pack;
use function preg_match;
use function substr;
/**
* A Lexer is a stateful stream generator in that every time
@@ -118,7 +121,7 @@ class Lexer
$token = $this->token;
if ($token->kind !== Token::EOF) {
do {
$token = $token->next ?: ($token->next = $this->readToken($token));
$token = $token->next ?? ($token->next = $this->readToken($token));
} while ($token->kind === Token::COMMENT);
}
@@ -314,11 +317,11 @@ class Lexer
$start = $this->position;
[$char, $code] = $this->readChar();
while ($code && (
while ($code !== null && (
$code === 95 || // _
$code >= 48 && $code <= 57 || // 0-9
$code >= 65 && $code <= 90 || // A-Z
$code >= 97 && $code <= 122 // a-z
($code >= 48 && $code <= 57) || // 0-9
($code >= 65 && $code <= 90) || // A-Z
($code >= 97 && $code <= 122) // a-z
)) {
$value .= $char;
[$char, $code] = $this->moveStringCursor(1, 1)->readChar();
@@ -522,8 +525,27 @@ class Lexer
'Invalid character escape sequence: \\u' . $hex
);
}
$code = hexdec($hex);
// UTF-16 surrogate pair detection and handling.
$highOrderByte = $code >> 8;
if (0xD8 <= $highOrderByte && $highOrderByte <= 0xDF) {
[$utf16Continuation] = $this->readChars(6, true);
if (! preg_match('/^\\\u[0-9a-fA-F]{4}$/', $utf16Continuation)) {
throw new SyntaxError(
$this->source,
$this->position - 5,
'Invalid UTF-16 trailing surrogate: ' . $utf16Continuation
);
}
$surrogatePairHex = $hex . substr($utf16Continuation, 2, 4);
$value .= mb_convert_encoding(pack('H*', $surrogatePairHex), 'UTF-8', 'UTF-16');
break;
}
$this->assertValidStringCharacterCode($code, $position - 2);
$value .= Utils::chr($code);
break;
default:
@@ -695,7 +717,7 @@ class Lexer
do {
[$char, $code, $bytes] = $this->moveStringCursor(1, $bytes)->readChar();
$value .= $char;
} while ($code &&
} while ($code !== null &&
// SourceCharacter but not LineTerminator
($code > 0x001F || $code === 0x0009)
);
@@ -771,7 +793,7 @@ class Lexer
{
$result = '';
$totalBytes = 0;
$byteOffset = $byteStreamPosition ?: $this->byteStreamPosition;
$byteOffset = $byteStreamPosition ?? $this->byteStreamPosition;
for ($i = 0; $i < $charCount; $i++) {
[$char, $code, $bytes] = $this->readChar(false, $byteOffset);
File diff suppressed because it is too large Load Diff
+73 -54
View File
@@ -28,6 +28,7 @@ use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\ListTypeNode;
use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NonNullTypeNode;
@@ -47,6 +48,7 @@ use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\UnionTypeExtensionNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Utils\Utils;
use function count;
use function implode;
@@ -54,6 +56,7 @@ use function json_encode;
use function preg_replace;
use function sprintf;
use function str_replace;
use function strlen;
use function strpos;
/**
@@ -82,7 +85,7 @@ class Printer
public static function doPrint($ast)
{
static $instance;
$instance = $instance ?: new static();
$instance = $instance ?? new static();
return $instance->printAST($ast);
}
@@ -91,25 +94,31 @@ class Printer
{
}
/**
* Traverse an AST bottom-up, converting all nodes to strings.
*
* That means the AST is manipulated in such a way that it no longer
* resembles the well-formed result of parsing.
*/
public function printAST($ast)
{
return Visitor::visit(
$ast,
[
'leave' => [
NodeKind::NAME => static function (Node $node) {
return '' . $node->value;
NodeKind::NAME => static function (NameNode $node) : string {
return $node->value;
},
NodeKind::VARIABLE => static function ($node) {
NodeKind::VARIABLE => static function (VariableNode $node) : string {
return '$' . $node->name;
},
NodeKind::DOCUMENT => function (DocumentNode $node) {
NodeKind::DOCUMENT => function (DocumentNode $node) : string {
return $this->join($node->definitions, "\n\n") . "\n";
},
NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) {
NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) : string {
$op = $node->operation;
$name = $node->name;
$varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
@@ -118,20 +127,24 @@ class Printer
// Anonymous queries with no directives or variable definitions can use
// the query short form.
return ! $name && ! $directives && ! $varDefs && $op === 'query'
return $name === null && strlen($directives ?? '') === 0 && ! $varDefs && $op === 'query'
? $selectionSet
: $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' ');
},
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) {
return $node->variable . ': ' . $node->type . $this->wrap(' = ', $node->defaultValue);
NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) : string {
return $node->variable
. ': '
. $node->type
. $this->wrap(' = ', $node->defaultValue)
. $this->wrap(' ', $this->join($node->directives, ' '));
},
NodeKind::SELECTION_SET => function (SelectionSetNode $node) {
return $this->block($node->selections);
},
NodeKind::FIELD => function (FieldNode $node) {
NodeKind::FIELD => function (FieldNode $node) : string {
return $this->join(
[
$this->wrap('', $node->alias, ': ') . $node->name . $this->wrap(
@@ -146,15 +159,17 @@ class Printer
);
},
NodeKind::ARGUMENT => static function (ArgumentNode $node) {
NodeKind::ARGUMENT => static function (ArgumentNode $node) : string {
return $node->name . ': ' . $node->value;
},
NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) {
return '...' . $node->name . $this->wrap(' ', $this->join($node->directives, ' '));
NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) : string {
return '...'
. $node->name
. $this->wrap(' ', $this->join($node->directives, ' '));
},
NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) {
NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) : string {
return $this->join(
[
'...',
@@ -166,7 +181,7 @@ class Printer
);
},
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) {
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) : string {
// Note: fragment variable definitions are experimental and may be changed or removed in the future.
return sprintf('fragment %s', $node->name)
. $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')')
@@ -175,15 +190,15 @@ class Printer
. $node->selectionSet;
},
NodeKind::INT => static function (IntValueNode $node) {
NodeKind::INT => static function (IntValueNode $node) : string {
return $node->value;
},
NodeKind::FLOAT => static function (FloatValueNode $node) {
NodeKind::FLOAT => static function (FloatValueNode $node) : string {
return $node->value;
},
NodeKind::STRING => function (StringValueNode $node, $key) {
NodeKind::STRING => function (StringValueNode $node, $key) : string {
if ($node->block) {
return $this->printBlockString($node->value, $key === 'description');
}
@@ -191,47 +206,48 @@ class Printer
return json_encode($node->value);
},
NodeKind::BOOLEAN => static function (BooleanValueNode $node) {
NodeKind::BOOLEAN => static function (BooleanValueNode $node) : string {
return $node->value ? 'true' : 'false';
},
NodeKind::NULL => static function (NullValueNode $node) {
NodeKind::NULL => static function (NullValueNode $node) : string {
return 'null';
},
NodeKind::ENUM => static function (EnumValueNode $node) {
NodeKind::ENUM => static function (EnumValueNode $node) : string {
return $node->value;
},
NodeKind::LST => function (ListValueNode $node) {
NodeKind::LST => function (ListValueNode $node) : string {
return '[' . $this->join($node->values, ', ') . ']';
},
NodeKind::OBJECT => function (ObjectValueNode $node) {
NodeKind::OBJECT => function (ObjectValueNode $node) : string {
return '{' . $this->join($node->fields, ', ') . '}';
},
NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) {
NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) : string {
return $node->name . ': ' . $node->value;
},
NodeKind::DIRECTIVE => function (DirectiveNode $node) {
NodeKind::DIRECTIVE => function (DirectiveNode $node) : string {
return '@' . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')');
},
NodeKind::NAMED_TYPE => static function (NamedTypeNode $node) {
NodeKind::NAMED_TYPE => static function (NamedTypeNode $node) : string {
// @phpstan-ignore-next-line the printer works bottom up, so this is already a string here
return $node->name;
},
NodeKind::LIST_TYPE => static function (ListTypeNode $node) {
NodeKind::LIST_TYPE => static function (ListTypeNode $node) : string {
return '[' . $node->type . ']';
},
NodeKind::NON_NULL_TYPE => static function (NonNullTypeNode $node) {
NodeKind::NON_NULL_TYPE => static function (NonNullTypeNode $node) : string {
return $node->type . '!';
},
NodeKind::SCHEMA_DEFINITION => function (SchemaDefinitionNode $def) {
NodeKind::SCHEMA_DEFINITION => function (SchemaDefinitionNode $def) : string {
return $this->join(
[
'schema',
@@ -242,15 +258,15 @@ class Printer
);
},
NodeKind::OPERATION_TYPE_DEFINITION => static function (OperationTypeDefinitionNode $def) {
NodeKind::OPERATION_TYPE_DEFINITION => static function (OperationTypeDefinitionNode $def) : string {
return $def->operation . ': ' . $def->type;
},
NodeKind::SCALAR_TYPE_DEFINITION => $this->addDescription(function (ScalarTypeDefinitionNode $def) {
NodeKind::SCALAR_TYPE_DEFINITION => $this->addDescription(function (ScalarTypeDefinitionNode $def) : string {
return $this->join(['scalar', $def->name, $this->join($def->directives, ' ')], ' ');
}),
NodeKind::OBJECT_TYPE_DEFINITION => $this->addDescription(function (ObjectTypeDefinitionNode $def) {
NodeKind::OBJECT_TYPE_DEFINITION => $this->addDescription(function (ObjectTypeDefinitionNode $def) : string {
return $this->join(
[
'type',
@@ -263,8 +279,8 @@ class Printer
);
}),
NodeKind::FIELD_DEFINITION => $this->addDescription(function (FieldDefinitionNode $def) {
$noIndent = Utils::every($def->arguments, static function (string $arg) {
NodeKind::FIELD_DEFINITION => $this->addDescription(function (FieldDefinitionNode $def) : string {
$noIndent = Utils::every($def->arguments, static function (string $arg) : bool {
return strpos($arg, "\n") === false;
});
@@ -276,7 +292,7 @@ class Printer
. $this->wrap(' ', $this->join($def->directives, ' '));
}),
NodeKind::INPUT_VALUE_DEFINITION => $this->addDescription(function (InputValueDefinitionNode $def) {
NodeKind::INPUT_VALUE_DEFINITION => $this->addDescription(function (InputValueDefinitionNode $def) : string {
return $this->join(
[
$def->name . ': ' . $def->type,
@@ -288,11 +304,12 @@ class Printer
}),
NodeKind::INTERFACE_TYPE_DEFINITION => $this->addDescription(
function (InterfaceTypeDefinitionNode $def) {
function (InterfaceTypeDefinitionNode $def) : string {
return $this->join(
[
'interface',
$def->name,
$this->wrap('implements ', $this->join($def->interfaces, ' & ')),
$this->join($def->directives, ' '),
$this->block($def->fields),
],
@@ -301,13 +318,13 @@ class Printer
}
),
NodeKind::UNION_TYPE_DEFINITION => $this->addDescription(function (UnionTypeDefinitionNode $def) {
NodeKind::UNION_TYPE_DEFINITION => $this->addDescription(function (UnionTypeDefinitionNode $def) : string {
return $this->join(
[
'union',
$def->name,
$this->join($def->directives, ' '),
$def->types
count($def->types ?? []) > 0
? '= ' . $this->join($def->types, ' | ')
: '',
],
@@ -315,7 +332,7 @@ class Printer
);
}),
NodeKind::ENUM_TYPE_DEFINITION => $this->addDescription(function (EnumTypeDefinitionNode $def) {
NodeKind::ENUM_TYPE_DEFINITION => $this->addDescription(function (EnumTypeDefinitionNode $def) : string {
return $this->join(
[
'enum',
@@ -327,13 +344,13 @@ class Printer
);
}),
NodeKind::ENUM_VALUE_DEFINITION => $this->addDescription(function (EnumValueDefinitionNode $def) {
NodeKind::ENUM_VALUE_DEFINITION => $this->addDescription(function (EnumValueDefinitionNode $def) : string {
return $this->join([$def->name, $this->join($def->directives, ' ')], ' ');
}),
NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $this->addDescription(function (
InputObjectTypeDefinitionNode $def
) {
) : string {
return $this->join(
[
'input',
@@ -345,7 +362,7 @@ class Printer
);
}),
NodeKind::SCHEMA_EXTENSION => function (SchemaTypeExtensionNode $def) {
NodeKind::SCHEMA_EXTENSION => function (SchemaTypeExtensionNode $def) : string {
return $this->join(
[
'extend schema',
@@ -356,7 +373,7 @@ class Printer
);
},
NodeKind::SCALAR_TYPE_EXTENSION => function (ScalarTypeExtensionNode $def) {
NodeKind::SCALAR_TYPE_EXTENSION => function (ScalarTypeExtensionNode $def) : string {
return $this->join(
[
'extend scalar',
@@ -367,7 +384,7 @@ class Printer
);
},
NodeKind::OBJECT_TYPE_EXTENSION => function (ObjectTypeExtensionNode $def) {
NodeKind::OBJECT_TYPE_EXTENSION => function (ObjectTypeExtensionNode $def) : string {
return $this->join(
[
'extend type',
@@ -380,11 +397,12 @@ class Printer
);
},
NodeKind::INTERFACE_TYPE_EXTENSION => function (InterfaceTypeExtensionNode $def) {
NodeKind::INTERFACE_TYPE_EXTENSION => function (InterfaceTypeExtensionNode $def) : string {
return $this->join(
[
'extend interface',
$def->name,
$this->wrap('implements ', $this->join($def->interfaces, ' & ')),
$this->join($def->directives, ' '),
$this->block($def->fields),
],
@@ -392,13 +410,13 @@ class Printer
);
},
NodeKind::UNION_TYPE_EXTENSION => function (UnionTypeExtensionNode $def) {
NodeKind::UNION_TYPE_EXTENSION => function (UnionTypeExtensionNode $def) : string {
return $this->join(
[
'extend union',
$def->name,
$this->join($def->directives, ' '),
$def->types
count($def->types ?? []) > 0
? '= ' . $this->join($def->types, ' | ')
: '',
],
@@ -406,7 +424,7 @@ class Printer
);
},
NodeKind::ENUM_TYPE_EXTENSION => function (EnumTypeExtensionNode $def) {
NodeKind::ENUM_TYPE_EXTENSION => function (EnumTypeExtensionNode $def) : string {
return $this->join(
[
'extend enum',
@@ -418,7 +436,7 @@ class Printer
);
},
NodeKind::INPUT_OBJECT_TYPE_EXTENSION => function (InputObjectTypeExtensionNode $def) {
NodeKind::INPUT_OBJECT_TYPE_EXTENSION => function (InputObjectTypeExtensionNode $def) : string {
return $this->join(
[
'extend input',
@@ -430,8 +448,8 @@ class Printer
);
},
NodeKind::DIRECTIVE_DEFINITION => $this->addDescription(function (DirectiveDefinitionNode $def) {
$noIndent = Utils::every($def->arguments, static function (string $arg) {
NodeKind::DIRECTIVE_DEFINITION => $this->addDescription(function (DirectiveDefinitionNode $def) : string {
$noIndent = Utils::every($def->arguments, static function (string $arg) : bool {
return strpos($arg, "\n") === false;
});
@@ -440,6 +458,7 @@ class Printer
. ($noIndent
? $this->wrap('(', $this->join($def->arguments, ', '), ')')
: $this->wrap("(\n", $this->indent($this->join($def->arguments, "\n")), "\n"))
. ($def->repeatable ? ' repeatable' : '')
. ' on ' . $this->join($def->locations, ' | ');
}),
],
@@ -449,7 +468,7 @@ class Printer
public function addDescription(callable $cb)
{
return function ($node) use ($cb) {
return function ($node) use ($cb) : string {
return $this->join([$node->description, $cb($node)], "\n");
};
}
@@ -489,14 +508,14 @@ class Printer
return $maybeArray ? count($maybeArray) : 0;
}
public function join($maybeArray, $separator = '')
public function join($maybeArray, $separator = '') : string
{
return $maybeArray
? implode(
$separator,
Utils::filter(
$maybeArray,
static function ($x) {
static function ($x) : bool {
return (bool) $x;
}
)
+2 -2
View File
@@ -46,8 +46,8 @@ class Source
$this->body = $body;
$this->length = mb_strlen($body, 'UTF-8');
$this->name = $name ?: 'GraphQL request';
$this->locationOffset = $location ?: new SourceLocation(1, 1);
$this->name = $name === '' || $name === null ? 'GraphQL request' : $name;
$this->locationOffset = $location ?? new SourceLocation(1, 1);
Utils::invariant(
$this->locationOffset->line > 0,
+28 -36
View File
@@ -11,28 +11,28 @@ namespace GraphQL\Language;
class Token
{
// Each kind of token.
const SOF = '<SOF>';
const EOF = '<EOF>';
const BANG = '!';
const DOLLAR = '$';
const AMP = '&';
const PAREN_L = '(';
const PAREN_R = ')';
const SPREAD = '...';
const COLON = ':';
const EQUALS = '=';
const AT = '@';
const BRACKET_L = '[';
const BRACKET_R = ']';
const BRACE_L = '{';
const PIPE = '|';
const BRACE_R = '}';
const NAME = 'Name';
const INT = 'Int';
const FLOAT = 'Float';
const STRING = 'String';
const BLOCK_STRING = 'BlockString';
const COMMENT = 'Comment';
public const SOF = '<SOF>';
public const EOF = '<EOF>';
public const BANG = '!';
public const DOLLAR = '$';
public const AMP = '&';
public const PAREN_L = '(';
public const PAREN_R = ')';
public const SPREAD = '...';
public const COLON = ':';
public const EQUALS = '=';
public const AT = '@';
public const BRACKET_L = '[';
public const BRACKET_R = ']';
public const BRACE_L = '{';
public const PIPE = '|';
public const BRACE_R = '}';
public const NAME = 'Name';
public const INT = 'Int';
public const FLOAT = 'Float';
public const STRING = 'String';
public const BLOCK_STRING = 'BlockString';
public const COMMENT = 'Comment';
/**
* The kind of Token (see one of constants above).
@@ -81,18 +81,13 @@ class Token
*/
public $prev;
/** @var Token */
/** @var Token|null */
public $next;
/**
* @param string $kind
* @param int $start
* @param int $end
* @param int $line
* @param int $column
* @param mixed|null $value
* @param mixed $value
*/
public function __construct($kind, $start, $end, $line, $column, ?Token $previous = null, $value = null)
public function __construct(string $kind, int $start, int $end, int $line, int $column, ?Token $previous = null, $value = null)
{
$this->kind = $kind;
$this->start = $start;
@@ -104,18 +99,15 @@ class Token
$this->value = $value;
}
/**
* @return string
*/
public function getDescription()
public function getDescription() : string
{
return $this->kind . ($this->value ? ' "' . $this->value . '"' : '');
return $this->kind . ($this->value === null ? '' : ' "' . $this->value . '"');
}
/**
* @return (string|int|null)[]
*/
public function toArray()
public function toArray() : array
{
return [
'kind' => $this->kind,
+42 -47
View File
@@ -14,8 +14,6 @@ use SplFixedArray;
use stdClass;
use function array_pop;
use function array_splice;
use function call_user_func;
use function call_user_func_array;
use function count;
use function func_get_args;
use function is_array;
@@ -116,7 +114,7 @@ class Visitor
NodeKind::NAME => [],
NodeKind::DOCUMENT => ['definitions'],
NodeKind::OPERATION_DEFINITION => ['name', 'variableDefinitions', 'directives', 'selectionSet'],
NodeKind::VARIABLE_DEFINITION => ['variable', 'type', 'defaultValue'],
NodeKind::VARIABLE_DEFINITION => ['variable', 'type', 'defaultValue', 'directives'],
NodeKind::VARIABLE => ['name'],
NodeKind::SELECTION_SET => ['selections'],
NodeKind::FIELD => ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
@@ -153,7 +151,7 @@ class Visitor
NodeKind::OBJECT_TYPE_DEFINITION => ['description', 'name', 'interfaces', 'directives', 'fields'],
NodeKind::FIELD_DEFINITION => ['description', 'name', 'arguments', 'type', 'directives'],
NodeKind::INPUT_VALUE_DEFINITION => ['description', 'name', 'type', 'defaultValue', 'directives'],
NodeKind::INTERFACE_TYPE_DEFINITION => ['description', 'name', 'directives', 'fields'],
NodeKind::INTERFACE_TYPE_DEFINITION => ['description', 'name', 'interfaces', 'directives', 'fields'],
NodeKind::UNION_TYPE_DEFINITION => ['description', 'name', 'directives', 'types'],
NodeKind::ENUM_TYPE_DEFINITION => ['description', 'name', 'directives', 'values'],
NodeKind::ENUM_VALUE_DEFINITION => ['description', 'name', 'directives'],
@@ -161,7 +159,7 @@ class Visitor
NodeKind::SCALAR_TYPE_EXTENSION => ['name', 'directives'],
NodeKind::OBJECT_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'],
NodeKind::INTERFACE_TYPE_EXTENSION => ['name', 'directives', 'fields'],
NodeKind::INTERFACE_TYPE_EXTENSION => ['name', 'interfaces', 'directives', 'fields'],
NodeKind::UNION_TYPE_EXTENSION => ['name', 'directives', 'types'],
NodeKind::ENUM_TYPE_EXTENSION => ['name', 'directives', 'values'],
NodeKind::INPUT_OBJECT_TYPE_EXTENSION => ['name', 'directives', 'fields'],
@@ -186,7 +184,7 @@ class Visitor
*/
public static function visit($root, $visitor, $keyMap = null)
{
$visitorKeys = $keyMap ?: self::$visitorKeys;
$visitorKeys = $keyMap ?? self::$visitorKeys;
$stack = null;
$inArray = $root instanceof NodeList || is_array($root);
@@ -205,7 +203,7 @@ class Visitor
$isLeaving = $index === count($keys);
$key = null;
$node = null;
$isEdited = $isLeaving && count($edits) !== 0;
$isEdited = $isLeaving && count($edits) > 0;
if ($isLeaving) {
$key = ! $ancestors ? $UNDEFINED : $path[count($path) - 1];
@@ -230,11 +228,7 @@ class Visitor
$editKey -= $editOffset;
}
if ($inArray && $editValue === null) {
if ($node instanceof NodeList) {
$node->splice($editKey, 1);
} else {
array_splice($node, $editKey, 1);
}
$node->splice($editKey, 1);
$editOffset++;
} else {
if ($node instanceof NodeList || is_array($node)) {
@@ -251,8 +245,18 @@ class Visitor
$inArray = $stack['inArray'];
$stack = $stack['prev'];
} else {
$key = $parent !== null ? ($inArray ? $index : $keys[$index]) : $UNDEFINED;
$node = $parent !== null ? ($parent instanceof NodeList || is_array($parent) ? $parent[$key] : $parent->{$key}) : $newRoot;
$key = $parent !== null
? ($inArray
? $index
: $keys[$index]
)
: $UNDEFINED;
$node = $parent !== null
? ($parent instanceof NodeList || is_array($parent)
? $parent[$key]
: $parent->{$key}
)
: $newRoot;
if ($node === null || $node === $UNDEFINED) {
continue;
}
@@ -269,8 +273,8 @@ class Visitor
$visitFn = self::getVisitFn($visitor, $node->kind, $isLeaving);
if ($visitFn) {
$result = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors);
if ($visitFn !== null) {
$result = $visitFn($node, $key, $parent, $path, $ancestors);
$editValue = null;
if ($result !== null) {
@@ -318,7 +322,7 @@ class Visitor
];
$inArray = $node instanceof NodeList || is_array($node);
$keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?: [];
$keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?? [];
$index = -1;
$edits = [];
if ($parent !== null) {
@@ -328,7 +332,7 @@ class Visitor
}
} while ($stack);
if (count($edits) !== 0) {
if (count($edits) > 0) {
$newRoot = $edits[0][1];
}
@@ -383,7 +387,7 @@ class Visitor
/**
* @param callable[][] $visitors
*
* @return callable[][]
* @return array<string, callable>
*/
public static function visitInParallel($visitors)
{
@@ -393,7 +397,7 @@ class Visitor
return [
'enter' => static function (Node $node) use ($visitors, $skipping, $visitorsCount) {
for ($i = 0; $i < $visitorsCount; $i++) {
if (! empty($skipping[$i])) {
if ($skipping[$i] !== null) {
continue;
}
@@ -407,7 +411,7 @@ class Visitor
continue;
}
$result = call_user_func_array($fn, func_get_args());
$result = $fn(...func_get_args());
if ($result instanceof VisitorOperation) {
if ($result->doContinue) {
@@ -424,15 +428,15 @@ class Visitor
},
'leave' => static function (Node $node) use ($visitors, $skipping, $visitorsCount) {
for ($i = 0; $i < $visitorsCount; $i++) {
if (empty($skipping[$i])) {
if ($skipping[$i] === null) {
$fn = self::getVisitFn(
$visitors[$i],
$node->kind, /* isLeaving */
true
);
if ($fn) {
$result = call_user_func_array($fn, func_get_args());
if (isset($fn)) {
$result = $fn(...func_get_args());
if ($result instanceof VisitorOperation) {
if ($result->doBreak) {
$skipping[$i] = $result;
@@ -462,8 +466,8 @@ class Visitor
$typeInfo->enter($node);
$fn = self::getVisitFn($visitor, $node->kind, false);
if ($fn) {
$result = call_user_func_array($fn, func_get_args());
if (isset($fn)) {
$result = $fn(...func_get_args());
if ($result !== null) {
$typeInfo->leave($node);
if ($result instanceof Node) {
@@ -478,7 +482,10 @@ class Visitor
},
'leave' => static function (Node $node) use ($typeInfo, $visitor) {
$fn = self::getVisitFn($visitor, $node->kind, true);
$result = $fn ? call_user_func_array($fn, func_get_args()) : null;
$result = $fn !== null
? $fn(...func_get_args())
: null;
$typeInfo->leave($node);
return $result;
@@ -490,10 +497,8 @@ class Visitor
* @param callable[]|null $visitor
* @param string $kind
* @param bool $isLeaving
*
* @return callable|null
*/
public static function getVisitFn($visitor, $kind, $isLeaving)
public static function getVisitFn($visitor, $kind, $isLeaving) : ?callable
{
if ($visitor === null) {
return null;
@@ -501,11 +506,6 @@ class Visitor
$kindVisitor = $visitor[$kind] ?? null;
if (! $isLeaving && is_callable($kindVisitor)) {
// { Kind() {} }
return $kindVisitor;
}
if (is_array($kindVisitor)) {
if ($isLeaving) {
$kindSpecificVisitor = $kindVisitor['leave'] ?? null;
@@ -513,29 +513,24 @@ class Visitor
$kindSpecificVisitor = $kindVisitor['enter'] ?? null;
}
if ($kindSpecificVisitor && is_callable($kindSpecificVisitor)) {
// { Kind: { enter() {}, leave() {} } }
return $kindSpecificVisitor;
}
return $kindSpecificVisitor;
}
return null;
if ($kindVisitor !== null && ! $isLeaving) {
return $kindVisitor;
}
$visitor += ['leave' => null, 'enter' => null];
$specificVisitor = $isLeaving ? $visitor['leave'] : $visitor['enter'];
if ($specificVisitor) {
if (is_callable($specificVisitor)) {
if (isset($specificVisitor)) {
if (! is_array($specificVisitor)) {
// { enter() {}, leave() {} }
return $specificVisitor;
}
$specificKindVisitor = $specificVisitor[$kind] ?? null;
if (is_callable($specificKindVisitor)) {
// { enter: { Kind() {} }, leave: { Kind() {} } }
return $specificKindVisitor;
}
return $specificVisitor[$kind] ?? null;
}
return null;
-22
View File
@@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL;
use function trigger_error;
use const E_USER_DEPRECATED;
trigger_error(
'GraphQL\Schema is moved to GraphQL\Type\Schema',
E_USER_DEPRECATED
);
/**
* Schema Definition
*
* @deprecated moved to GraphQL\Type\Schema
*/
class Schema extends \GraphQL\Type\Schema
{
}
+54 -34
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace GraphQL\Server;
use GraphQL\Error\DebugFlag;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use GraphQL\Error\InvariantViolation;
@@ -18,11 +19,14 @@ use GraphQL\Language\Parser;
use GraphQL\Utils\AST;
use GraphQL\Utils\Utils;
use JsonSerializable;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use function count;
use function file_get_contents;
use function header;
use function html_entity_decode;
use function is_array;
use function is_callable;
use function is_string;
@@ -30,6 +34,7 @@ use function json_decode;
use function json_encode;
use function json_last_error;
use function json_last_error_msg;
use function parse_str;
use function sprintf;
use function stripos;
@@ -73,11 +78,15 @@ class Helper
}
if (stripos($contentType, 'application/graphql') !== false) {
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
$bodyParams = ['query' => $rawBody ?: ''];
$rawBody = $readRawBodyFn
? $readRawBodyFn()
: $this->readRawBody();
$bodyParams = ['query' => $rawBody ?? ''];
} elseif (stripos($contentType, 'application/json') !== false) {
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
$bodyParams = json_decode($rawBody ?: '', true);
$rawBody = $readRawBodyFn ?
$readRawBodyFn()
: $this->readRawBody();
$bodyParams = json_decode($rawBody ?? '', true);
if (json_last_error()) {
throw new RequestError('Could not parse JSON: ' . json_last_error_msg());
@@ -142,7 +151,7 @@ class Helper
* Checks validity of OperationParams extracted from HTTP request and returns an array of errors
* if params are invalid (or empty array when params are valid)
*
* @return Error[]
* @return array<int, RequestError>
*
* @api
*/
@@ -157,21 +166,21 @@ class Helper
$errors[] = new RequestError('GraphQL Request parameters "query" and "queryId" are mutually exclusive');
}
if ($params->query !== null && (! is_string($params->query) || empty($params->query))) {
if ($params->query !== null && ! is_string($params->query)) {
$errors[] = new RequestError(
'GraphQL Request parameter "query" must be string, but got ' .
Utils::printSafeJson($params->query)
);
}
if ($params->queryId !== null && (! is_string($params->queryId) || empty($params->queryId))) {
if ($params->queryId !== null && ! is_string($params->queryId)) {
$errors[] = new RequestError(
'GraphQL Request parameter "queryId" must be string, but got ' .
Utils::printSafeJson($params->queryId)
);
}
if ($params->operation !== null && (! is_string($params->operation) || empty($params->operation))) {
if ($params->operation !== null && ! is_string($params->operation)) {
$errors[] = new RequestError(
'GraphQL Request parameter "operation" must be string, but got ' .
Utils::printSafeJson($params->operation)
@@ -198,7 +207,7 @@ class Helper
*/
public function executeOperation(ServerConfig $config, OperationParams $op)
{
$promiseAdapter = $config->getPromiseAdapter() ?: Executor::getPromiseAdapter();
$promiseAdapter = $config->getPromiseAdapter() ?? Executor::getPromiseAdapter();
$result = $this->promiseToExecuteOperation($promiseAdapter, $config, $op);
if ($promiseAdapter instanceof SyncPromiseAdapter) {
@@ -220,7 +229,7 @@ class Helper
*/
public function executeBatch(ServerConfig $config, array $operations)
{
$promiseAdapter = $config->getPromiseAdapter() ?: Executor::getPromiseAdapter();
$promiseAdapter = $config->getPromiseAdapter() ?? Executor::getPromiseAdapter();
$result = [];
foreach ($operations as $operation) {
@@ -249,7 +258,7 @@ class Helper
$isBatch = false
) {
try {
if (! $config->getSchema()) {
if ($config->getSchema() === null) {
throw new InvariantViolation('Schema is required for the server');
}
@@ -259,10 +268,10 @@ class Helper
$errors = $this->validateOperationParams($op);
if (! empty($errors)) {
if (count($errors) > 0) {
$errors = Utils::map(
$errors,
static function (RequestError $err) {
static function (RequestError $err) : Error {
return Error::createLocatedError($err, null, null);
}
);
@@ -272,13 +281,20 @@ class Helper
);
}
$doc = $op->queryId ? $this->loadPersistedQuery($config, $op) : $op->query;
$doc = $op->queryId
? $this->loadPersistedQuery($config, $op)
: $op->query;
if (! $doc instanceof DocumentNode) {
$doc = Parser::parse($doc);
}
$operationType = AST::getOperation($doc, $op->operation);
if ($operationType === false) {
throw new RequestError('Failed to determine operation type');
}
if ($operationType !== 'query' && $op->isReadOnly()) {
throw new RequestError('GET supports only query operation');
}
@@ -304,15 +320,15 @@ class Helper
);
}
$applyErrorHandling = static function (ExecutionResult $result) use ($config) {
$applyErrorHandling = static function (ExecutionResult $result) use ($config) : ExecutionResult {
if ($config->getErrorsHandler()) {
$result->setErrorsHandler($config->getErrorsHandler());
}
if ($config->getErrorFormatter() || $config->getDebug()) {
if ($config->getErrorFormatter() || $config->getDebugFlag() !== DebugFlag::NONE) {
$result->setErrorFormatter(
FormattedError::prepareFormatter(
$config->getErrorFormatter(),
$config->getDebug()
$config->getDebugFlag()
)
);
}
@@ -333,7 +349,7 @@ class Helper
// Load query if we got persisted query id:
$loader = $config->getPersistentQueryLoader();
if (! $loader) {
if ($loader === null) {
throw new RequestError('Persisted queries are not supported by this server');
}
@@ -379,19 +395,17 @@ class Helper
}
/**
* @param string $operationType
*
* @return mixed
*/
private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType)
private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, string $operationType)
{
$root = $config->getRootValue();
$rootValue = $config->getRootValue();
if (is_callable($root)) {
$root = $root($params, $doc, $operationType);
if (is_callable($rootValue)) {
$rootValue = $rootValue($params, $doc, $operationType);
}
return $root;
return $rootValue;
}
/**
@@ -425,7 +439,7 @@ class Helper
public function sendResponse($result, $exitWhenDone = false)
{
if ($result instanceof Promise) {
$result->then(function ($actualResult) use ($exitWhenDone) {
$result->then(function ($actualResult) use ($exitWhenDone) : void {
$this->doSendResponse($actualResult, $exitWhenDone);
});
} else {
@@ -473,7 +487,7 @@ class Helper
if (is_array($result) && isset($result[0])) {
Utils::each(
$result,
static function ($executionResult, $index) {
static function ($executionResult, $index) : void {
if (! $executionResult instanceof ExecutionResult) {
throw new InvariantViolation(sprintf(
'Expecting every entry of batched query result to be instance of %s but entry at position %d is %s',
@@ -493,7 +507,7 @@ class Helper
Utils::printSafe($result)
));
}
if ($result->data === null && ! empty($result->errors)) {
if ($result->data === null && count($result->errors) > 0) {
$httpStatus = 400;
} else {
$httpStatus = 200;
@@ -512,7 +526,7 @@ class Helper
*
* @api
*/
public function parsePsrRequest(ServerRequestInterface $request)
public function parsePsrRequest(RequestInterface $request)
{
if ($request->getMethod() === 'GET') {
$bodyParams = [];
@@ -524,13 +538,17 @@ class Helper
}
if (stripos($contentType[0], 'application/graphql') !== false) {
$bodyParams = ['query' => $request->getBody()->getContents()];
$bodyParams = ['query' => (string) $request->getBody()];
} elseif (stripos($contentType[0], 'application/json') !== false) {
$bodyParams = $request->getParsedBody();
$bodyParams = $request instanceof ServerRequestInterface
? $request->getParsedBody()
: json_decode((string) $request->getBody(), true);
if ($bodyParams === null) {
throw new InvariantViolation(
'PSR-7 request is expected to provide parsed body for "application/json" requests but got null'
$request instanceof ServerRequestInterface
? 'Expected to receive a parsed body for "application/json" PSR-7 request but got null'
: 'Expected to receive a JSON array in body for "application/json" PSR-7 request'
);
}
@@ -541,7 +559,7 @@ class Helper
);
}
} else {
$bodyParams = $request->getParsedBody();
parse_str((string) $request->getBody(), $bodyParams);
if (! is_array($bodyParams)) {
throw new RequestError('Unexpected content type: ' . Utils::printSafeJson($contentType[0]));
@@ -549,10 +567,12 @@ class Helper
}
}
parse_str(html_entity_decode($request->getUri()->getQuery()), $queryParams);
return $this->parseRequestParams(
$request->getMethod(),
$bodyParams,
$request->getQueryParams()
$queryParams
);
}
+5 -3
View File
@@ -8,7 +8,9 @@ use function array_change_key_case;
use function is_string;
use function json_decode;
use function json_last_error;
use function strlen;
use const CASE_LOWER;
use const JSON_ERROR_NONE;
/**
* Structure representing parsed HTTP parameters for GraphQL operation
@@ -93,7 +95,7 @@ class OperationParams
}
$tmp = json_decode($params[$param], true);
if (json_last_error()) {
if (json_last_error() !== JSON_ERROR_NONE) {
continue;
}
@@ -101,14 +103,14 @@ class OperationParams
}
$instance->query = $params['query'];
$instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id'];
$instance->queryId = $params['queryid'] ?? $params['documentid'] ?? $params['id'];
$instance->operation = $params['operationname'];
$instance->variables = $params['variables'];
$instance->extensions = $params['extensions'];
$instance->readOnly = $readonly;
// Apollo server/client compatibility: look for the queryid in extensions
if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) {
if (isset($instance->extensions['persistedQuery']['sha256Hash']) && strlen($instance->query ?? '') === 0 && strlen($instance->queryId ?? '') === 0) {
$instance->queryId = $instance->extensions['persistedQuery']['sha256Hash'];
}
+19 -25
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace GraphQL\Server;
use GraphQL\Error\DebugFlag;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Type\Schema;
@@ -54,7 +55,7 @@ class ServerConfig
return $instance;
}
/** @var Schema */
/** @var Schema|null */
private $schema;
/** @var mixed|callable */
@@ -69,22 +70,22 @@ class ServerConfig
/** @var callable|null */
private $errorsHandler;
/** @var bool */
private $debug = false;
/** @var int */
private $debugFlag = DebugFlag::NONE;
/** @var bool */
private $queryBatching = false;
/** @var ValidationRule[]|callable */
/** @var ValidationRule[]|callable|null */
private $validationRules;
/** @var callable */
/** @var callable|null */
private $fieldResolver;
/** @var PromiseAdapter */
/** @var PromiseAdapter|null */
private $promiseAdapter;
/** @var callable */
/** @var callable|null */
private $persistentQueryLoader;
/**
@@ -158,7 +159,7 @@ class ServerConfig
/**
* Set validation rules for this server.
*
* @param ValidationRule[]|callable $validationRules
* @param ValidationRule[]|callable|null $validationRules
*
* @return self
*
@@ -207,17 +208,13 @@ class ServerConfig
}
/**
* Set response debug flags. See GraphQL\Error\Debug class for a list of all available flags
*
* @param bool|int $set
*
* @return self
* Set response debug flags. @see \GraphQL\Error\DebugFlag class for a list of all available flags
*
* @api
*/
public function setDebug($set = true)
public function setDebugFlag(int $debugFlag = DebugFlag::INCLUDE_DEBUG_MESSAGE) : self
{
$this->debug = $set;
$this->debugFlag = $debugFlag;
return $this;
}
@@ -263,7 +260,7 @@ class ServerConfig
}
/**
* @return Schema
* @return Schema|null
*/
public function getSchema()
{
@@ -287,7 +284,7 @@ class ServerConfig
}
/**
* @return PromiseAdapter
* @return PromiseAdapter|null
*/
public function getPromiseAdapter()
{
@@ -295,7 +292,7 @@ class ServerConfig
}
/**
* @return ValidationRule[]|callable
* @return ValidationRule[]|callable|null
*/
public function getValidationRules()
{
@@ -303,7 +300,7 @@ class ServerConfig
}
/**
* @return callable
* @return callable|null
*/
public function getFieldResolver()
{
@@ -311,19 +308,16 @@ class ServerConfig
}
/**
* @return callable
* @return callable|null
*/
public function getPersistentQueryLoader()
{
return $this->persistentQueryLoader;
}
/**
* @return bool
*/
public function getDebug()
public function getDebugFlag() : int
{
return $this->debug;
return $this->debugFlag;
}
/**
+6 -5
View File
@@ -4,13 +4,14 @@ declare(strict_types=1);
namespace GraphQL\Server;
use GraphQL\Error\DebugFlag;
use GraphQL\Error\FormattedError;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Utils\Utils;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Throwable;
use function is_array;
@@ -50,12 +51,12 @@ class StandardServer
* (e.g. during schema instantiation).
*
* @param Throwable $error
* @param bool $debug
* @param int $debug
* @param bool $exitWhenDone
*
* @api
*/
public static function send500Error($error, $debug = false, $exitWhenDone = false)
public static function send500Error($error, $debug = DebugFlag::NONE, $exitWhenDone = false)
{
$response = [
'errors' => [FormattedError::createFromException($error, $debug)],
@@ -146,7 +147,7 @@ class StandardServer
* @api
*/
public function processPsrRequest(
ServerRequestInterface $request,
RequestInterface $request,
ResponseInterface $response,
StreamInterface $writableBodyStream
) {
@@ -163,7 +164,7 @@ class StandardServer
*
* @api
*/
public function executePsrRequest(ServerRequestInterface $request)
public function executePsrRequest(RequestInterface $request)
{
$parsedBody = $this->helper->parsePsrRequest($request);
@@ -4,12 +4,11 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition;
/*
export type GraphQLAbstractType =
GraphQLInterfaceType |
GraphQLUnionType;
*/
/**
export type AbstractType =
InterfaceType |
UnionType;
*/
interface AbstractType
{
/**
@@ -20,11 +20,14 @@ class BooleanType extends ScalarType
public $description = 'The `Boolean` scalar type represents `true` or `false`.';
/**
* @param mixed $value
* Serialize the given value to a boolean.
*
* @return bool
* The GraphQL spec leaves this up to the implementations, so we just do what
* PHP does natively to make this intuitive for developers.
*
* @param mixed $value
*/
public function serialize($value)
public function serialize($value) : bool
{
return (bool) $value;
}
@@ -42,22 +45,19 @@ class BooleanType extends ScalarType
return $value;
}
throw new Error('Cannot represent value as boolean: ' . Utils::printSafe($value));
throw new Error('Boolean cannot represent a non boolean value: ' . Utils::printSafe($value));
}
/**
* @param Node $valueNode
* @param mixed[]|null $variables
*
* @return bool|null
*
* @throws Exception
*/
public function parseLiteral($valueNode, ?array $variables = null)
public function parseLiteral(Node $valueNode, ?array $variables = null)
{
if (! $valueNode instanceof BooleanValueNode) {
// Intentionally without message, as all information already in wrapped Exception
throw new Exception();
throw new Error();
}
return $valueNode->value;
@@ -8,7 +8,6 @@ use Exception;
use GraphQL\Language\AST\Node;
use GraphQL\Utils\AST;
use GraphQL\Utils\Utils;
use function call_user_func;
use function is_callable;
use function sprintf;
@@ -21,7 +20,7 @@ class CustomScalarType extends ScalarType
*/
public function serialize($value)
{
return call_user_func($this->config['serialize'], $value);
return $this->config['serialize']($value);
}
/**
@@ -32,26 +31,23 @@ class CustomScalarType extends ScalarType
public function parseValue($value)
{
if (isset($this->config['parseValue'])) {
return call_user_func($this->config['parseValue'], $value);
return $this->config['parseValue']($value);
}
return $value;
}
/**
* @param Node $valueNode
* @param mixed[]|null $variables
*
* @return mixed
*
* @throws Exception
*/
public function parseLiteral(/* GraphQL\Language\AST\ValueNode */
$valueNode,
?array $variables = null
) {
public function parseLiteral(Node $valueNode, ?array $variables = null)
{
if (isset($this->config['parseLiteral'])) {
return call_user_func($this->config['parseLiteral'], $valueNode, $variables);
return $this->config['parseLiteral']($valueNode, $variables);
}
return AST::valueFromASTUntyped($valueNode, $variables);
+32 -22
View File
@@ -4,25 +4,23 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\DirectiveLocation;
use GraphQL\Utils\Utils;
use function array_key_exists;
use function array_keys;
use function in_array;
use function is_array;
class Directive
{
public const DEFAULT_DEPRECATION_REASON = 'No longer supported';
const INCLUDE_NAME = 'include';
const IF_ARGUMENT_NAME = 'if';
const SKIP_NAME = 'skip';
const DEPRECATED_NAME = 'deprecated';
const REASON_ARGUMENT_NAME = 'reason';
public const INCLUDE_NAME = 'include';
public const IF_ARGUMENT_NAME = 'if';
public const SKIP_NAME = 'skip';
public const DEPRECATED_NAME = 'deprecated';
public const REASON_ARGUMENT_NAME = 'reason';
/** @var Directive[] */
/** @var Directive[]|null */
public static $internalDirectives;
// Schema Definitions
@@ -33,12 +31,15 @@ class Directive
/** @var string|null */
public $description;
/** @var string[] */
public $locations;
/** @var FieldArgument[] */
public $args = [];
/** @var bool */
public $isRepeatable;
/** @var string[] */
public $locations;
/** @var DirectiveDefinitionNode|null */
public $astNode;
@@ -50,6 +51,13 @@ class Directive
*/
public function __construct(array $config)
{
if (! isset($config['name'])) {
throw new InvariantViolation('Directive must be named.');
}
$this->name = $config['name'];
$this->description = $config['description'] ?? null;
if (isset($config['args'])) {
$args = [];
foreach ($config['args'] as $name => $arg) {
@@ -60,14 +68,16 @@ class Directive
}
}
$this->args = $args;
unset($config['args']);
}
foreach ($config as $key => $value) {
$this->{$key} = $value;
}
Utils::invariant($this->name, 'Directive must be named.');
Utils::invariant(is_array($this->locations), 'Must provide locations for directive.');
if (! isset($config['locations']) || ! is_array($config['locations'])) {
throw new InvariantViolation('Must provide locations for directive.');
}
$this->locations = $config['locations'];
$this->isRepeatable = $config['isRepeatable'] ?? false;
$this->astNode = $config['astNode'] ?? null;
$this->config = $config;
}
@@ -84,9 +94,9 @@ class Directive
/**
* @return Directive[]
*/
public static function getInternalDirectives()
public static function getInternalDirectives() : array
{
if (! self::$internalDirectives) {
if (self::$internalDirectives === null) {
self::$internalDirectives = [
'include' => new self([
'name' => self::INCLUDE_NAME,
@@ -130,8 +140,8 @@ class Directive
'type' => Type::string(),
'description' =>
'Explains why this element was deprecated, usually also including a ' .
'suggestion for how to access supported similar data. Formatted ' .
'in [Markdown](https://daringfireball.net/projects/markdown/).',
'suggestion for how to access supported similar data. Formatted using ' .
'the Markdown syntax (as specified by [CommonMark](https://commonmark.org/).',
'defaultValue' => self::DEFAULT_DEPRECATION_REASON,
]),
],
+21 -14
View File
@@ -24,10 +24,20 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
/** @var EnumTypeDefinitionNode|null */
public $astNode;
/** @var EnumValueDefinition[] */
/**
* Lazily initialized.
*
* @var EnumValueDefinition[]
*/
private $values;
/** @var MixedStore<mixed, EnumValueDefinition> */
/**
* Lazily initialized.
*
* Actually a MixedStore<mixed, EnumValueDefinition>, PHPStan won't let us type it that way.
*
* @var MixedStore
*/
private $valueLookup;
/** @var ArrayObject<string, EnumValueDefinition> */
@@ -67,12 +77,10 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
return $lookup[$name] ?? null;
}
/**
* @return ArrayObject<string, EnumValueDefinition>
*/
private function getNameLookup()
private function getNameLookup() : ArrayObject
{
if (! $this->nameLookup) {
/** @var ArrayObject<string, EnumValueDefinition> $lookup */
$lookup = new ArrayObject();
foreach ($this->getValues() as $value) {
$lookup[$value->name] = $value;
@@ -86,9 +94,9 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
/**
* @return EnumValueDefinition[]
*/
public function getValues()
public function getValues() : array
{
if ($this->values === null) {
if (! isset($this->values)) {
$this->values = [];
$config = $this->config;
@@ -139,11 +147,11 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
}
/**
* @return MixedStore<mixed, EnumValueDefinition>
* Actually returns a MixedStore<mixed, EnumValueDefinition>, PHPStan won't let us type it that way
*/
private function getValueLookup()
private function getValueLookup() : MixedStore
{
if ($this->valueLookup === null) {
if (! isset($this->valueLookup)) {
$this->valueLookup = new MixedStore();
foreach ($this->getValues() as $valueName => $value) {
@@ -172,14 +180,13 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
}
/**
* @param Node $valueNode
* @param mixed[]|null $variables
*
* @return null
*
* @throws Exception
*/
public function parseLiteral($valueNode, ?array $variables = null)
public function parseLiteral(Node $valueNode, ?array $variables = null)
{
if ($valueNode instanceof EnumValueNode) {
$lookup = $this->getNameLookup();
@@ -192,7 +199,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
}
// Intentionally without message, as all information already in wrapped Exception
throw new Exception();
throw new Error();
}
/**
@@ -6,7 +6,9 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use function array_key_exists;
use function is_array;
use function is_string;
use function sprintf;
@@ -28,28 +30,19 @@ class FieldArgument
/** @var mixed[] */
public $config;
/** @var InputType */
/** @var Type&InputType */
private $type;
/** @var bool */
private $defaultValueExists = false;
/**
* @param mixed[] $def
*/
/** @param mixed[] $def */
public function __construct(array $def)
{
foreach ($def as $key => $value) {
switch ($key) {
case 'type':
$this->type = $value;
break;
case 'name':
$this->name = $value;
break;
case 'defaultValue':
$this->defaultValue = $value;
$this->defaultValueExists = true;
$this->defaultValue = $value;
break;
case 'description':
$this->description = $value;
@@ -67,7 +60,7 @@ class FieldArgument
*
* @return FieldArgument[]
*/
public static function createMap(array $config)
public static function createMap(array $config) : array
{
$map = [];
foreach ($config as $name => $argConfig) {
@@ -80,20 +73,29 @@ class FieldArgument
return $map;
}
/**
* @return InputType
*/
public function getType()
public function getType() : Type
{
if (! isset($this->type)) {
/**
* TODO: replace this phpstan cast with native assert
*
* @var Type&InputType
*/
$type = Schema::resolveType($this->config['type']);
$this->type = $type;
}
return $this->type;
}
/**
* @return bool
*/
public function defaultValueExists()
public function defaultValueExists() : bool
{
return $this->defaultValueExists;
return array_key_exists('defaultValue', $this->config);
}
public function isRequired() : bool
{
return $this->getType() instanceof NonNull && ! $this->defaultValueExists();
}
public function assertValid(FieldDefinition $parentField, Type $parentType)
@@ -105,7 +107,7 @@ class FieldArgument
sprintf('%s.%s(%s:) %s', $parentType->name, $parentField->name, $this->name, $e->getMessage())
);
}
$type = $this->type;
$type = $this->getType();
if ($type instanceof WrappingType) {
$type = $type->getWrappedType(true);
}
@@ -6,7 +6,9 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Error\Warning;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use function is_array;
use function is_callable;
@@ -30,7 +32,7 @@ class FieldDefinition
* Callback for resolving field value given parent value.
* Mutually exclusive with `map`
*
* @var callable
* @var callable|null
*/
public $resolveFn;
@@ -38,7 +40,7 @@ class FieldDefinition
* Callback for mapping list of parent values to list of field values.
* Mutually exclusive with `resolve`
*
* @var callable
* @var callable|null
*/
public $mapFn;
@@ -58,8 +60,8 @@ class FieldDefinition
*/
public $config;
/** @var OutputType */
public $type;
/** @var OutputType&Type */
private $type;
/** @var callable|string */
private $complexityFn;
@@ -70,7 +72,6 @@ class FieldDefinition
protected function __construct(array $config)
{
$this->name = $config['name'];
$this->type = $config['type'];
$this->resolveFn = $config['resolve'] ?? null;
$this->mapFn = $config['map'] ?? null;
$this->args = isset($config['args']) ? FieldArgument::createMap($config['args']) : [];
@@ -84,7 +85,12 @@ class FieldDefinition
$this->complexityFn = $config['complexity'] ?? self::DEFAULT_COMPLEXITY_FN;
}
public static function defineFieldMap(Type $type, $fields)
/**
* @param (callable():mixed[])|mixed[] $fields
*
* @return array<string, self>
*/
public static function defineFieldMap(Type $type, $fields) : array
{
if (is_callable($fields)) {
$fields = $fields();
@@ -117,6 +123,17 @@ class FieldDefinition
$fieldDef = self::create($field);
} elseif ($field instanceof self) {
$fieldDef = $field;
} elseif (is_callable($field)) {
if (! is_string($name)) {
throw new InvariantViolation(
sprintf(
'%s lazy fields must be an associative array with field names as keys.',
$type->name
)
);
}
$fieldDef = new UnresolvedFieldDefinition($type, $name, $field);
} else {
if (! is_string($name) || ! $field) {
throw new InvariantViolation(
@@ -131,7 +148,8 @@ class FieldDefinition
$fieldDef = self::create(['name' => $name, 'type' => $field]);
}
$map[$fieldDef->name] = $fieldDef;
$map[$fieldDef->getName()] = $fieldDef;
}
return $map;
@@ -164,7 +182,7 @@ class FieldDefinition
*/
public function getArg($name)
{
foreach ($this->args ?: [] as $arg) {
foreach ($this->args ?? [] as $arg) {
/** @var FieldArgument $arg */
if ($arg->name === $name) {
return $arg;
@@ -174,14 +192,78 @@ class FieldDefinition
return null;
}
/**
* @return Type
*/
public function getType()
public function getName() : string
{
return $this->name;
}
public function getType() : Type
{
if (! isset($this->type)) {
/**
* TODO: replace this phpstan cast with native assert
*
* @var Type&OutputType
*/
$type = Schema::resolveType($this->config['type']);
$this->type = $type;
}
return $this->type;
}
public function __isset(string $name) : bool
{
switch ($name) {
case 'type':
Warning::warnOnce(
"The public getter for 'type' on FieldDefinition has been deprecated and will be removed" .
" in the next major version. Please update your code to use the 'getType' method.",
Warning::WARNING_CONFIG_DEPRECATION
);
return isset($this->type);
}
return isset($this->$name);
}
public function __get(string $name)
{
switch ($name) {
case 'type':
Warning::warnOnce(
"The public getter for 'type' on FieldDefinition has been deprecated and will be removed" .
" in the next major version. Please update your code to use the 'getType' method.",
Warning::WARNING_CONFIG_DEPRECATION
);
return $this->getType();
default:
return $this->$name;
}
return null;
}
public function __set(string $name, $value)
{
switch ($name) {
case 'type':
Warning::warnOnce(
"The public setter for 'type' on FieldDefinition has been deprecated and will be removed" .
' in the next major version.',
Warning::WARNING_CONFIG_DEPRECATION
);
$this->type = $value;
break;
default:
$this->$name = $value;
break;
}
}
/**
* @return bool
*/
@@ -217,7 +299,7 @@ class FieldDefinition
)
);
$type = $this->type;
$type = $this->getType();
if ($type instanceof WrappingType) {
$type = $type->getWrappedType(true);
}
@@ -239,5 +321,9 @@ class FieldDefinition
Utils::printSafe($this->resolveFn)
)
);
foreach ($this->args as $fieldArgument) {
$fieldArgument->assertValid($this, $type);
}
}
}
+27 -23
View File
@@ -10,6 +10,11 @@ use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils;
use function floatval;
use function is_bool;
use function is_finite;
use function is_float;
use function is_int;
use function is_numeric;
class FloatType extends ScalarType
@@ -26,60 +31,59 @@ values as specified by
/**
* @param mixed $value
*
* @return float|null
*
* @throws Error
*/
public function serialize($value)
public function serialize($value) : float
{
return $this->coerceFloat($value);
}
$float = is_numeric($value) || is_bool($value)
? (float) $value
: null;
private function coerceFloat($value)
{
if ($value === '') {
throw new Error(
'Float cannot represent non numeric value: (empty string)'
);
}
if (! is_numeric($value) && $value !== true && $value !== false) {
if ($float === null || ! is_finite($float)) {
throw new Error(
'Float cannot represent non numeric value: ' .
Utils::printSafe($value)
);
}
return (float) $value;
return $float;
}
/**
* @param mixed $value
*
* @return float|null
*
* @throws Error
*/
public function parseValue($value)
public function parseValue($value) : float
{
return $this->coerceFloat($value);
$float = is_float($value) || is_int($value)
? (float) $value
: null;
if ($float === null || ! is_finite($float)) {
throw new Error(
'Float cannot represent non numeric value: ' .
Utils::printSafe($value)
);
}
return $float;
}
/**
* @param Node $valueNode
* @param mixed[]|null $variables
*
* @return float|null
* @return float
*
* @throws Exception
*/
public function parseLiteral($valueNode, ?array $variables = null)
public function parseLiteral(Node $valueNode, ?array $variables = null)
{
if ($valueNode instanceof FloatValueNode || $valueNode instanceof IntValueNode) {
return (float) $valueNode->value;
}
// Intentionally without message, as all information already in wrapped Exception
throw new Exception();
throw new Error();
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
interface HasFieldsType
{
/**
* @throws InvariantViolation
*/
public function getField(string $name) : FieldDefinition;
public function hasField(string $name) : bool;
public function findField(string $name) : ?FieldDefinition;
/**
* @return array<string, FieldDefinition>
*
* @throws InvariantViolation
*/
public function getFields() : array;
/**
* @return array<int, string>
*
* @throws InvariantViolation
*/
public function getFieldNames() : array;
}
+11 -21
View File
@@ -12,7 +12,6 @@ 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;
@@ -38,17 +37,12 @@ When expected as an input type, any string (such as `"4"`) or integer
*/
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));
$canCast = is_string($value)
|| is_int($value)
|| (is_object($value) && method_exists($value, '__toString'));
if (! $canCast) {
throw new Error('ID cannot represent value: ' . Utils::printSafe($value));
}
return (string) $value;
@@ -57,34 +51,30 @@ When expected as an input type, any string (such as `"4"`) or integer
/**
* @param mixed $value
*
* @return string
*
* @throws Error
*/
public function parseValue($value)
public function parseValue($value) : string
{
if (is_string($value) || is_int($value)) {
return (string) $value;
}
throw new Error('Cannot represent value as ID: ' . Utils::printSafe($value));
throw new Error('ID cannot represent value: ' . Utils::printSafe($value));
}
/**
* @param Node $valueNode
* @param mixed[]|null $variables
*
* @return string|null
* @return string
*
* @throws Exception
*/
public function parseLiteral($valueNode, ?array $variables = null)
public function parseLiteral(Node $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();
throw new Error();
}
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Type\Definition;
/**
export type GraphQLImplementingType =
GraphQLObjectType |
GraphQLInterfaceType;
*/
interface ImplementingType
{
public function implementsInterface(InterfaceType $interfaceType) : bool;
/**
* @return array<int, InterfaceType>
*/
public function getInterfaces() : array;
}
@@ -6,8 +6,11 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Error\Warning;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use function array_key_exists;
use function sprintf;
class InputObjectField
@@ -21,8 +24,8 @@ class InputObjectField
/** @var string|null */
public $description;
/** @var mixed */
public $type;
/** @var Type&InputType */
private $type;
/** @var InputValueDefinitionNode|null */
public $astNode;
@@ -30,13 +33,6 @@ class InputObjectField
/** @var mixed[] */
public $config;
/**
* Helps to differentiate when `defaultValue` is `null` and when it was not even set initially
*
* @var bool
*/
private $defaultValueExists = false;
/**
* @param mixed[] $opts
*/
@@ -45,11 +41,13 @@ class InputObjectField
foreach ($opts as $k => $v) {
switch ($k) {
case 'defaultValue':
$this->defaultValue = $v;
$this->defaultValueExists = true;
$this->defaultValue = $v;
break;
case 'defaultValueExists':
break;
case 'type':
// do nothing; type is lazy loaded in getType
break;
default:
$this->{$k} = $v;
}
@@ -57,20 +55,84 @@ class InputObjectField
$this->config = $opts;
}
/**
* @return mixed
*/
public function getType()
public function __isset(string $name) : bool
{
return $this->type;
switch ($name) {
case 'type':
Warning::warnOnce(
"The public getter for 'type' on InputObjectField has been deprecated and will be removed" .
" in the next major version. Please update your code to use the 'getType' method.",
Warning::WARNING_CONFIG_DEPRECATION
);
return isset($this->type);
}
return isset($this->$name);
}
public function __get(string $name)
{
switch ($name) {
case 'type':
Warning::warnOnce(
"The public getter for 'type' on InputObjectField has been deprecated and will be removed" .
" in the next major version. Please update your code to use the 'getType' method.",
Warning::WARNING_CONFIG_DEPRECATION
);
return $this->getType();
default:
return $this->$name;
}
return null;
}
public function __set(string $name, $value)
{
switch ($name) {
case 'type':
Warning::warnOnce(
"The public setter for 'type' on InputObjectField has been deprecated and will be removed" .
' in the next major version.',
Warning::WARNING_CONFIG_DEPRECATION
);
$this->type = $value;
break;
default:
$this->$name = $value;
break;
}
}
/**
* @return bool
* @return Type&InputType
*/
public function defaultValueExists()
public function getType() : Type
{
return $this->defaultValueExists;
if (! isset($this->type)) {
/**
* TODO: replace this phpstan cast with native assert
*
* @var Type&InputType
*/
$type = Schema::resolveType($this->config['type']);
$this->type = $type;
}
return $this->type;
}
public function defaultValueExists() : bool
{
return array_key_exists('defaultValue', $this->config);
}
public function isRequired() : bool
{
return $this->getType() instanceof NonNull && ! $this->defaultValueExists();
}
/**
@@ -83,7 +145,7 @@ class InputObjectField
} catch (Error $e) {
throw new InvariantViolation(sprintf('%s.%s: %s', $parentType->name, $this->name, $e->getMessage()));
}
$type = $this->type;
$type = $this->getType();
if ($type instanceof WrappingType) {
$type = $type->getWrappedType(true);
}
@@ -97,9 +159,9 @@ class InputObjectField
)
);
Utils::invariant(
empty($this->config['resolve']),
! array_key_exists('resolve', $this->config),
sprintf(
'%s.%s field type has a resolve property, but Input Types cannot define resolvers.',
'%s.%s field has a resolve property, but Input Types cannot define resolvers.',
$parentType->name,
$this->name
)
@@ -4,12 +4,11 @@ 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 count;
use function is_array;
use function is_callable;
use function is_string;
@@ -20,7 +19,11 @@ class InputObjectType extends Type implements InputType, NullableType, NamedType
/** @var InputObjectTypeDefinitionNode|null */
public $astNode;
/** @var InputObjectField[] */
/**
* Lazily initialized.
*
* @var InputObjectField[]
*/
private $fields;
/** @var InputObjectTypeExtensionNode[] */
@@ -45,16 +48,12 @@ class InputObjectType extends Type implements InputType, NullableType, NamedType
}
/**
* @param string $name
*
* @return InputObjectField
*
* @throws Exception
* @throws InvariantViolation
*/
public function getField($name)
public function getField(string $name) : InputObjectField
{
if ($this->fields === null) {
$this->getFields();
if (! isset($this->fields)) {
$this->initializeFields();
}
Utils::invariant(isset($this->fields[$name]), "Field '%s' is not defined for type '%s'", $name, $this->name);
@@ -64,43 +63,50 @@ class InputObjectType extends Type implements InputType, NullableType, NamedType
/**
* @return InputObjectField[]
*/
public function getFields()
public function getFields() : array
{
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;
}
if (! isset($this->fields)) {
$this->initializeFields();
}
return $this->fields;
}
protected function initializeFields() : void
{
$this->fields = [];
$fields = $this->config['fields'] ?? [];
if (is_callable($fields)) {
$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 || is_callable($field)) {
$field = ['type' => $field];
}
$field = new InputObjectField($field + ['name' => $name]);
$this->fields[$field->name] = $field;
}
}
/**
* 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()
public function assertValid() : void
{
parent::assertValid();
Utils::invariant(
! empty($this->getFields()),
count($this->getFields()) > 0,
sprintf(
'%s fields must be an associative array with field names as keys or a callable which returns such an array.',
$this->name
+11 -12
View File
@@ -4,20 +4,19 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition;
/*
export type GraphQLInputType =
| GraphQLScalarType
| GraphQLEnumType
| GraphQLInputObjectType
| GraphQLList<GraphQLInputType>
| GraphQLNonNull<
| GraphQLScalarType
| GraphQLEnumType
| GraphQLInputObjectType
| GraphQLList<GraphQLInputType>,
/**
export type InputType =
| ScalarType
| EnumType
| InputObjectType
| ListOfType<InputType>
| NonNull<
| ScalarType
| EnumType
| InputObjectType
| ListOfType<InputType>,
>;
*/
interface InputType
{
}
+40 -34
View File
@@ -10,8 +10,11 @@ use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils;
use function floatval;
use function floor;
use function intval;
use function is_bool;
use function is_float;
use function is_int;
use function is_numeric;
class IntType extends ScalarType
@@ -41,63 +44,66 @@ values. Int can represent values between -(2^31) and 2^31 - 1. ';
*/
public function serialize($value)
{
return $this->coerceInt($value);
}
/**
* @param mixed $value
*
* @return int
*/
private function coerceInt($value)
{
if ($value === '') {
throw new Error(
'Int cannot represent non 32-bit signed integer value: (empty string)'
);
// Fast path for 90+% of cases:
if (is_int($value) && $value <= self::MAX_INT && $value >= self::MIN_INT) {
return $value;
}
$num = floatval($value);
if ((! is_numeric($value) && ! is_bool($value)) || $num > self::MAX_INT || $num < self::MIN_INT) {
throw new Error(
'Int cannot represent non 32-bit signed integer value: ' .
Utils::printSafe($value)
);
}
$int = intval($num);
// int cast with == used for performance reasons
// phpcs:ignore
if ($int != $num) {
$float = is_numeric($value) || is_bool($value)
? (float) $value
: null;
if ($float === null || floor($float) !== $float) {
throw new Error(
'Int cannot represent non-integer value: ' .
Utils::printSafe($value)
);
}
return $int;
if ($float > self::MAX_INT || $float < self::MIN_INT) {
throw new Error(
'Int cannot represent non 32-bit signed integer value: ' .
Utils::printSafe($value)
);
}
return (int) $float;
}
/**
* @param mixed $value
*
* @return int|null
*
* @throws Error
*/
public function parseValue($value)
public function parseValue($value) : int
{
return $this->coerceInt($value);
$isInt = is_int($value) || (is_float($value) && floor($value) === $value);
if (! $isInt) {
throw new Error(
'Int cannot represent non-integer value: ' .
Utils::printSafe($value)
);
}
if ($value > self::MAX_INT || $value < self::MIN_INT) {
throw new Error(
'Int cannot represent non 32-bit signed integer value: ' .
Utils::printSafe($value)
);
}
return (int) $value;
}
/**
* @param Node $valueNode
* @param mixed[]|null $variables
*
* @return int|null
* @return int
*
* @throws Exception
*/
public function parseLiteral($valueNode, ?array $variables = null)
public function parseLiteral(Node $valueNode, ?array $variables = null)
{
if ($valueNode instanceof IntValueNode) {
$val = (int) $valueNode->value;
@@ -107,6 +113,6 @@ values. Int can represent values between -(2^31) and 2^31 - 1. ';
}
// Intentionally without message, as all information already in wrapped Exception
throw new Exception();
throw new Error();
}
}
@@ -7,21 +7,35 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use function array_map;
use function is_array;
use function is_callable;
use function is_string;
use function sprintf;
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
class InterfaceType extends TypeWithFields implements AbstractType, OutputType, CompositeType, NullableType, NamedType, ImplementingType
{
/** @var InterfaceTypeDefinitionNode|null */
public $astNode;
/** @var InterfaceTypeExtensionNode[] */
/** @var array<int, InterfaceTypeExtensionNode> */
public $extensionASTNodes;
/** @var FieldDefinition[] */
private $fields;
/**
* Lazily initialized.
*
* @var array<int, InterfaceType>
*/
private $interfaces;
/**
* Lazily initialized.
*
* @var array<string, InterfaceType>
*/
private $interfaceMap;
/**
* @param mixed[] $config
@@ -44,9 +58,11 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
/**
* @param mixed $type
*
* @return self
* @return $this
*
* @throws InvariantViolation
*/
public static function assertInterfaceType($type)
public static function assertInterfaceType($type) : self
{
Utils::invariant(
$type instanceof self,
@@ -56,53 +72,53 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
return $type;
}
/**
* @param string $name
*
* @return FieldDefinition
*/
public function getField($name)
public function implementsInterface(InterfaceType $interfaceType) : bool
{
if ($this->fields === null) {
$this->getFields();
if (! isset($this->interfaceMap)) {
$this->interfaceMap = [];
foreach ($this->getInterfaces() as $interface) {
/** @var Type&InterfaceType $interface */
$interface = Schema::resolveType($interface);
$this->interfaceMap[$interface->name] = $interface;
}
}
Utils::invariant(isset($this->fields[$name]), 'Field "%s" is not defined for type "%s"', $name, $this->name);
return $this->fields[$name];
return isset($this->interfaceMap[$interfaceType->name]);
}
/**
* @param string $name
*
* @return bool
* @return array<int, InterfaceType>
*/
public function hasField($name)
public function getInterfaces() : array
{
if ($this->fields === null) {
$this->getFields();
if (! isset($this->interfaces)) {
$interfaces = $this->config['interfaces'] ?? [];
if (is_callable($interfaces)) {
$interfaces = $interfaces();
}
if ($interfaces !== null && ! is_array($interfaces)) {
throw new InvariantViolation(
sprintf('%s interfaces must be an Array or a callable which returns an Array.', $this->name)
);
}
/** @var array<int, InterfaceType> $interfaces */
$interfaces = $interfaces === null
? []
: array_map([Schema::class, 'resolveType'], $interfaces);
$this->interfaces = $interfaces;
}
return isset($this->fields[$name]);
}
/**
* @return FieldDefinition[]
*/
public function getFields()
{
if ($this->fields === null) {
$fields = $this->config['fields'] ?? [];
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
}
return $this->fields;
return $this->interfaces;
}
/**
* Resolves concrete ObjectType for given object value
*
* @param object $objectValue
* @param mixed[] $context
* @param object $objectValue
* @param mixed $context
*
* @return Type|null
*/
@@ -120,7 +136,7 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
/**
* @throws InvariantViolation
*/
public function assertValid()
public function assertValid() : void
{
parent::assertValid();
@@ -6,7 +6,12 @@ namespace GraphQL\Type\Definition;
use Exception;
use GraphQL\Error\Error;
use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\StringValueNode;
/*
export type GraphQLLeafType =
@@ -45,12 +50,12 @@ interface LeafType
*
* In the case of an invalid node or value this method must throw an Exception
*
* @param Node $valueNode
* @param mixed[]|null $variables
* @param IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|NullValueNode $valueNode
* @param mixed[]|null $variables
*
* @return mixed
*
* @throws Exception
*/
public function parseLiteral($valueNode, ?array $variables = null);
public function parseLiteral(Node $valueNode, ?array $variables = null);
}
+17 -12
View File
@@ -4,33 +4,38 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition;
use GraphQL\Type\Schema;
use function is_callable;
class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType
{
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */
/** @var callable():Type|Type */
public $ofType;
/**
* @param callable|Type $type
* @param callable():Type|Type $type
*/
public function __construct($type)
{
$this->ofType = Type::assertType($type);
$this->ofType = is_callable($type) ? $type : Type::assertType($type);
}
public function toString() : string
{
return '[' . $this->ofType->toString() . ']';
return '[' . $this->getOfType()->toString() . ']';
}
/**
* @param bool $recurse
*
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType
*/
public function getWrappedType($recurse = false)
public function getOfType()
{
$type = $this->ofType;
return Schema::resolveType($this->ofType);
}
return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) : $type;
public function getWrappedType(bool $recurse = false) : Type
{
$type = $this->getOfType();
return $recurse && $type instanceof WrappingType
? $type->getWrappedType($recurse)
: $type;
}
}
@@ -4,16 +4,15 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition;
/*
export type GraphQLNamedType =
| GraphQLScalarType
| GraphQLObjectType
| GraphQLInterfaceType
| GraphQLUnionType
| GraphQLEnumType
| GraphQLInputObjectType;
/**
export type NamedType =
| ScalarType
| ObjectType
| InterfaceType
| UnionType
| EnumType
| InputObjectType;
*/
interface NamedType
{
}
+20 -46
View File
@@ -4,68 +4,42 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition;
use GraphQL\Utils\Utils;
use GraphQL\Type\Schema;
class NonNull extends Type implements WrappingType, OutputType, InputType
{
/** @var NullableType */
/** @var callable():(NullableType&Type)|(NullableType&Type) */
private $ofType;
/**
* @param NullableType $type
* code sniffer doesn't understand this syntax. Pr with a fix here: waiting on https://github.com/squizlabs/PHP_CodeSniffer/pull/2919
* phpcs:disable Squiz.Commenting.FunctionComment.SpacingAfterParamType
* @param callable():(NullableType&Type)|(NullableType&Type) $type
*/
public function __construct($type)
{
$this->ofType = self::assertNullableType($type);
$this->ofType = $type;
}
/**
* @param mixed $type
*
* @return NullableType
*/
public static function assertNullableType($type)
{
Utils::invariant(
Type::isType($type) && ! $type instanceof self,
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL nullable type.'
);
return $type;
}
/**
* @param mixed $type
*
* @return self
*/
public static function assertNullType($type)
{
Utils::invariant(
$type instanceof self,
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL Non-Null type.'
);
return $type;
}
/**
* @return string
*/
public function toString()
public function toString() : string
{
return $this->getWrappedType()->toString() . '!';
}
/**
* @param bool $recurse
*
* @return Type
*/
public function getWrappedType($recurse = false)
public function getOfType()
{
$type = $this->ofType;
return Schema::resolveType($this->ofType);
}
return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) : $type;
/**
* @return (NullableType&Type)
*/
public function getWrappedType(bool $recurse = false) : Type
{
$type = $this->getOfType();
return $recurse && $type instanceof WrappingType
? $type->getWrappedType($recurse)
: $type;
}
}
+45 -87
View File
@@ -4,12 +4,13 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition;
use Exception;
use GraphQL\Deferred;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use function call_user_func;
use function array_map;
use function is_array;
use function is_callable;
use function is_string;
@@ -54,7 +55,7 @@ use function sprintf;
* }
* ]);
*/
class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType
class ObjectType extends TypeWithFields implements OutputType, CompositeType, NullableType, NamedType, ImplementingType
{
/** @var ObjectTypeDefinitionNode|null */
public $astNode;
@@ -62,16 +63,21 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
/** @var ObjectTypeExtensionNode[] */
public $extensionASTNodes;
/** @var callable */
/** @var ?callable */
public $resolveFieldFn;
/** @var FieldDefinition[] */
private $fields;
/** @var InterfaceType[] */
/**
* Lazily initialized.
*
* @var array<int, InterfaceType>
*/
private $interfaces;
/** @var InterfaceType[]|null */
/**
* Lazily initialized.
*
* @var array<string, InterfaceType>
*/
private $interfaceMap;
/**
@@ -96,9 +102,11 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
/**
* @param mixed $type
*
* @return self
* @return $this
*
* @throws InvariantViolation
*/
public static function assertObjectType($type)
public static function assertObjectType($type) : self
{
Utils::invariant(
$type instanceof self,
@@ -108,84 +116,30 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
return $type;
}
/**
* @param string $name
*
* @return FieldDefinition
*
* @throws Exception
*/
public function getField($name)
public function implementsInterface(InterfaceType $interfaceType) : bool
{
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];
}
/**
* @param string $name
*
* @return bool
*/
public function hasField($name)
{
if ($this->fields === null) {
$this->getFields();
}
return isset($this->fields[$name]);
}
/**
* @return FieldDefinition[]
*
* @throws InvariantViolation
*/
public function getFields()
{
if ($this->fields === null) {
$fields = $this->config['fields'] ?? [];
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
}
return $this->fields;
}
/**
* @param InterfaceType $iface
*
* @return bool
*/
public function implementsInterface($iface)
{
$map = $this->getInterfaceMap();
return isset($map[$iface->name]);
}
private function getInterfaceMap()
{
if (! $this->interfaceMap) {
if (! isset($this->interfaceMap)) {
$this->interfaceMap = [];
foreach ($this->getInterfaces() as $interface) {
/** @var Type&InterfaceType $interface */
$interface = Schema::resolveType($interface);
$this->interfaceMap[$interface->name] = $interface;
}
}
return $this->interfaceMap;
return isset($this->interfaceMap[$interfaceType->name]);
}
/**
* @return InterfaceType[]
* @return array<int, InterfaceType>
*/
public function getInterfaces()
public function getInterfaces() : array
{
if ($this->interfaces === null) {
if (! isset($this->interfaces)) {
$interfaces = $this->config['interfaces'] ?? [];
$interfaces = is_callable($interfaces) ? call_user_func($interfaces) : $interfaces;
if (is_callable($interfaces)) {
$interfaces = $interfaces();
}
if ($interfaces !== null && ! is_array($interfaces)) {
throw new InvariantViolation(
@@ -193,26 +147,30 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
);
}
$this->interfaces = $interfaces ?: [];
/** @var InterfaceType[] $interfaces */
$interfaces = array_map([Schema::class, 'resolveType'], $interfaces ?? []);
$this->interfaces = $interfaces;
}
return $this->interfaces;
}
/**
* @param mixed[] $value
* @param mixed[]|null $context
* @param mixed $value
* @param mixed $context
*
* @return bool|null
* @return bool|Deferred|null
*/
public function isTypeOf($value, $context, ResolveInfo $info)
{
return isset($this->config['isTypeOf']) ? call_user_func(
$this->config['isTypeOf'],
$value,
$context,
$info
) : null;
return isset($this->config['isTypeOf'])
? $this->config['isTypeOf'](
$value,
$context,
$info
)
: null;
}
/**
@@ -221,7 +179,7 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
*
* @throws InvariantViolation
*/
public function assertValid()
public function assertValid() : void
{
parent::assertValid();
+86 -32
View File
@@ -11,8 +11,11 @@ use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use function array_diff_key;
use function array_filter;
use function array_intersect_key;
use function array_key_exists;
use function array_keys;
use function array_merge;
@@ -32,7 +35,7 @@ class QueryPlan
/** @var Schema */
private $schema;
/** @var mixed[] */
/** @var array<string, mixed> */
private $queryPlan = [];
/** @var mixed[] */
@@ -41,16 +44,21 @@ class QueryPlan
/** @var FragmentDefinitionNode[] */
private $fragments;
/** @var bool */
private $groupImplementorFields;
/**
* @param FieldNode[] $fieldNodes
* @param mixed[] $variableValues
* @param FragmentDefinitionNode[] $fragments
* @param mixed[] $options
*/
public function __construct(ObjectType $parentType, Schema $schema, iterable $fieldNodes, array $variableValues, array $fragments)
public function __construct(ObjectType $parentType, Schema $schema, iterable $fieldNodes, array $variableValues, array $fragments, array $options = [])
{
$this->schema = $schema;
$this->variableValues = $variableValues;
$this->fragments = $fragments;
$this->schema = $schema;
$this->variableValues = $variableValues;
$this->fragments = $fragments;
$this->groupImplementorFields = in_array('group-implementor-fields', $options, true);
$this->analyzeQueryPlan($parentType, $fieldNodes);
}
@@ -72,7 +80,7 @@ class QueryPlan
public function hasType(string $type) : bool
{
return count(array_filter($this->getReferencedTypes(), static function (string $referencedType) use ($type) {
return count(array_filter($this->getReferencedTypes(), static function (string $referencedType) use ($type) : bool {
return $type === $referencedType;
})) > 0;
}
@@ -87,7 +95,7 @@ class QueryPlan
public function hasField(string $field) : bool
{
return count(array_filter($this->getReferencedFields(), static function (string $referencedField) use ($field) {
return count(array_filter($this->getReferencedFields(), static function (string $referencedField) use ($field) : bool {
return $field === $referencedField;
})) > 0;
}
@@ -109,7 +117,8 @@ class QueryPlan
*/
private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) : void
{
$queryPlan = [];
$queryPlan = [];
$implementors = [];
/** @var FieldNode $fieldNode */
foreach ($fieldNodes as $fieldNode) {
if (! $fieldNode->selectionSet) {
@@ -118,10 +127,10 @@ class QueryPlan
$type = $parentType->getField($fieldNode->name->value)->getType();
if ($type instanceof WrappingType) {
$type = $type->getWrappedType();
$type = $type->getWrappedType(true);
}
$subfields = $this->analyzeSelectionSet($fieldNode->selectionSet, $type);
$subfields = $this->analyzeSelectionSet($fieldNode->selectionSet, $type, $implementors);
$this->types[$type->name] = array_unique(array_merge(
array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [],
@@ -134,28 +143,44 @@ class QueryPlan
);
}
$this->queryPlan = $queryPlan;
if ($this->groupImplementorFields) {
$this->queryPlan = ['fields' => $queryPlan];
if ($implementors) {
$this->queryPlan['implementors'] = $implementors;
}
} else {
$this->queryPlan = $queryPlan;
}
}
/**
* @return mixed[]
* @param InterfaceType|ObjectType $parentType
* @param mixed[] $implementors
*
* $parentType InterfaceType|ObjectType.
* @return mixed[]
*
* @throws Error
*/
private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType) : array
private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType, array &$implementors) : array
{
$fields = [];
$fields = [];
$implementors = [];
foreach ($selectionSet->selections as $selectionNode) {
if ($selectionNode instanceof FieldNode) {
$fieldName = $selectionNode->name->value;
$fieldName = $selectionNode->name->value;
if ($fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
continue;
}
$type = $parentType->getField($fieldName);
$selectionType = $type->getType();
$subfields = [];
$subfields = [];
$subImplementors = [];
if ($selectionNode->selectionSet) {
$subfields = $this->analyzeSubFields($selectionType, $selectionNode->selectionSet);
$subfields = $this->analyzeSubFields($selectionType, $selectionNode->selectionSet, $subImplementors);
}
$fields[$fieldName] = [
@@ -163,26 +188,21 @@ class QueryPlan
'fields' => $subfields ?? [],
'args' => Values::getArgumentValues($type, $selectionNode, $this->variableValues),
];
if ($this->groupImplementorFields && $subImplementors) {
$fields[$fieldName]['implementors'] = $subImplementors;
}
} elseif ($selectionNode instanceof FragmentSpreadNode) {
$spreadName = $selectionNode->name->value;
if (isset($this->fragments[$spreadName])) {
$fragment = $this->fragments[$spreadName];
$type = $this->schema->getType($fragment->typeCondition->name->value);
$subfields = $this->analyzeSubFields($type, $fragment->selectionSet);
$fields = $this->arrayMergeDeep(
$subfields,
$fields
);
$fields = $this->mergeFields($parentType, $type, $fields, $subfields, $implementors);
}
} elseif ($selectionNode instanceof InlineFragmentNode) {
$type = $this->schema->getType($selectionNode->typeCondition->name->value);
$subfields = $this->analyzeSubFields($type, $selectionNode->selectionSet);
$fields = $this->arrayMergeDeep(
$subfields,
$fields
);
$fields = $this->mergeFields($parentType, $type, $fields, $subfields, $implementors);
}
}
@@ -190,17 +210,19 @@ class QueryPlan
}
/**
* @param mixed[] $implementors
*
* @return mixed[]
*/
private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet) : array
private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet, array &$implementors = []) : array
{
if ($type instanceof WrappingType) {
$type = $type->getWrappedType();
$type = $type->getWrappedType(true);
}
$subfields = [];
if ($type instanceof ObjectType) {
$subfields = $this->analyzeSelectionSet($selectionSet, $type);
if ($type instanceof ObjectType || $type instanceof AbstractType) {
$subfields = $this->analyzeSelectionSet($selectionSet, $type, $implementors);
$this->types[$type->name] = array_unique(array_merge(
array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [],
array_keys($subfields)
@@ -210,6 +232,38 @@ class QueryPlan
return $subfields;
}
/**
* @param mixed[] $fields
* @param mixed[] $subfields
* @param mixed[] $implementors
*
* @return mixed[]
*/
private function mergeFields(Type $parentType, Type $type, array $fields, array $subfields, array &$implementors) : array
{
if ($this->groupImplementorFields && $parentType instanceof AbstractType && ! $type instanceof AbstractType) {
$implementors[$type->name] = [
'type' => $type,
'fields' => $this->arrayMergeDeep(
$implementors[$type->name]['fields'] ?? [],
array_diff_key($subfields, $fields)
),
];
$fields = $this->arrayMergeDeep(
$fields,
array_intersect_key($subfields, $fields)
);
} else {
$fields = $this->arrayMergeDeep(
$subfields,
$fields
);
}
return $fields;
}
/**
* similar to array_merge_recursive this merges nested arrays, but handles non array values differently
* while array_merge_recursive tries to merge non array values, in this implementation they will be overwritten
@@ -20,6 +20,14 @@ use function array_merge_recursive;
*/
class ResolveInfo
{
/**
* The definition of the field being resolved.
*
* @api
* @var FieldDefinition
*/
public $fieldDefinition;
/**
* The name of the field being resolved.
*
@@ -28,6 +36,14 @@ class ResolveInfo
*/
public $fieldName;
/**
* Expected return type of the field being resolved.
*
* @api
* @var Type
*/
public $returnType;
/**
* AST of all nodes referencing this field in the query.
*
@@ -36,14 +52,6 @@ class ResolveInfo
*/
public $fieldNodes = [];
/**
* Expected return type of the field being resolved.
*
* @api
* @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull
*/
public $returnType;
/**
* Parent type of the field being resolved.
*
@@ -56,7 +64,7 @@ class ResolveInfo
* Path to this field from the very root value.
*
* @api
* @var string[][]
* @var string[]
*/
public $path;
@@ -100,21 +108,23 @@ class ResolveInfo
*/
public $variableValues = [];
/** @var QueryPlan */
/**
* Lazily initialized.
*
* @var QueryPlan
*/
private $queryPlan;
/**
* @param FieldNode[] $fieldNodes
* @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType
* @param string[][] $path
* @param FragmentDefinitionNode[] $fragments
* @param mixed|null $rootValue
* @param mixed[] $variableValues
* @param FieldNode[] $fieldNodes
* @param string[] $path
* @param FragmentDefinitionNode[] $fragments
* @param mixed|null $rootValue
* @param mixed[] $variableValues
*/
public function __construct(
string $fieldName,
FieldDefinition $fieldDefinition,
iterable $fieldNodes,
$returnType,
ObjectType $parentType,
array $path,
Schema $schema,
@@ -123,16 +133,17 @@ class ResolveInfo
?OperationDefinitionNode $operation,
array $variableValues
) {
$this->fieldName = $fieldName;
$this->fieldNodes = $fieldNodes;
$this->returnType = $returnType;
$this->parentType = $parentType;
$this->path = $path;
$this->schema = $schema;
$this->fragments = $fragments;
$this->rootValue = $rootValue;
$this->operation = $operation;
$this->variableValues = $variableValues;
$this->fieldDefinition = $fieldDefinition;
$this->fieldName = $fieldDefinition->name;
$this->returnType = $fieldDefinition->getType();
$this->fieldNodes = $fieldNodes;
$this->parentType = $parentType;
$this->path = $path;
$this->schema = $schema;
$this->fragments = $fragments;
$this->rootValue = $rootValue;
$this->operation = $operation;
$this->variableValues = $variableValues;
}
/**
@@ -168,7 +179,7 @@ class ResolveInfo
*
* @param int $depth How many levels to include in output
*
* @return bool[]
* @return array<string, mixed>
*
* @api
*/
@@ -191,15 +202,19 @@ class ResolveInfo
return $fields;
}
public function lookAhead() : QueryPlan
/**
* @param mixed[] $options
*/
public function lookAhead(array $options = []) : QueryPlan
{
if ($this->queryPlan === null) {
if (! isset($this->queryPlan)) {
$this->queryPlan = new QueryPlan(
$this->parentType,
$this->schema,
$this->fieldNodes,
$this->variableValues,
$this->fragments
$this->fragments,
$options
);
}
@@ -214,7 +229,7 @@ class ResolveInfo
$fields = [];
foreach ($selectionSet->selections as $selectionNode) {
if ($selectionNode instanceof FieldNode) {
$fields[$selectionNode->name->value] = $descend > 0 && ! empty($selectionNode->selectionSet)
$fields[$selectionNode->name->value] = $descend > 0 && $selectionNode->selectionSet !== null
? $this->foldSelectionSet($selectionNode->selectionSet, $descend - 1)
: true;
} elseif ($selectionNode instanceof FragmentSpreadNode) {
+16 -29
View File
@@ -9,9 +9,9 @@ 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 is_string;
use function method_exists;
class StringType extends ScalarType
@@ -34,31 +34,13 @@ represent free-form human-readable text.';
*/
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));
}
$canCast = is_scalar($value)
|| (is_object($value) && method_exists($value, '__toString'))
|| $value === null;
return $this->coerceString($value);
}
private function coerceString($value)
{
if (is_array($value)) {
if (! $canCast) {
throw new Error(
'String cannot represent an array value: ' .
Utils::printSafe($value)
'String cannot represent value: ' . Utils::printSafe($value)
);
}
@@ -74,24 +56,29 @@ represent free-form human-readable text.';
*/
public function parseValue($value)
{
return $this->coerceString($value);
if (! is_string($value)) {
throw new Error(
'String cannot represent a non string value: ' . Utils::printSafe($value)
);
}
return $value;
}
/**
* @param Node $valueNode
* @param mixed[]|null $variables
*
* @return string|null
* @return string
*
* @throws Exception
*/
public function parseLiteral($valueNode, ?array $variables = null)
public function parseLiteral(Node $valueNode, ?array $variables = null)
{
if ($valueNode instanceof StringValueNode) {
return $valueNode->value;
}
// Intentionally without message, as all information already in wrapped Exception
throw new Exception();
throw new Error();
}
}
+62 -119
View File
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition;
use Exception;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\TypeDefinitionNode;
use GraphQL\Language\AST\TypeExtensionNode;
@@ -12,9 +11,9 @@ use GraphQL\Type\Introspection;
use GraphQL\Utils\Utils;
use JsonSerializable;
use ReflectionClass;
use Throwable;
use function array_keys;
use function array_merge;
use function assert;
use function implode;
use function in_array;
use function preg_replace;
@@ -33,8 +32,8 @@ abstract class Type implements JsonSerializable
public const FLOAT = 'Float';
public const ID = 'ID';
/** @var Type[] */
private static $standardTypes;
/** @var array<string, ScalarType> */
protected static $standardTypes;
/** @var Type[] */
private static $builtInTypes;
@@ -55,105 +54,87 @@ abstract class Type implements JsonSerializable
public $extensionASTNodes;
/**
* @return IDType
*
* @api
*/
public static function id()
public static function id() : ScalarType
{
return self::getStandardType(self::ID);
}
/**
* @param string $name
*
* @return (IDType|StringType|FloatType|IntType|BooleanType)[]|IDType|StringType|FloatType|IntType|BooleanType
*/
private static function getStandardType($name = null)
{
if (self::$standardTypes === null) {
self::$standardTypes = [
self::ID => new IDType(),
self::STRING => new StringType(),
self::FLOAT => new FloatType(),
self::INT => new IntType(),
self::BOOLEAN => new BooleanType(),
];
if (! isset(static::$standardTypes[self::ID])) {
static::$standardTypes[self::ID] = new IDType();
}
return $name ? self::$standardTypes[$name] : self::$standardTypes;
return static::$standardTypes[self::ID];
}
/**
* @return StringType
*
* @api
*/
public static function string()
public static function string() : ScalarType
{
return self::getStandardType(self::STRING);
if (! isset(static::$standardTypes[self::STRING])) {
static::$standardTypes[self::STRING] = new StringType();
}
return static::$standardTypes[self::STRING];
}
/**
* @return BooleanType
*
* @api
*/
public static function boolean()
public static function boolean() : ScalarType
{
return self::getStandardType(self::BOOLEAN);
if (! isset(static::$standardTypes[self::BOOLEAN])) {
static::$standardTypes[self::BOOLEAN] = new BooleanType();
}
return static::$standardTypes[self::BOOLEAN];
}
/**
* @return IntType
*
* @api
*/
public static function int()
public static function int() : ScalarType
{
return self::getStandardType(self::INT);
if (! isset(static::$standardTypes[self::INT])) {
static::$standardTypes[self::INT] = new IntType();
}
return static::$standardTypes[self::INT];
}
/**
* @return FloatType
*
* @api
*/
public static function float()
public static function float() : ScalarType
{
return self::getStandardType(self::FLOAT);
if (! isset(static::$standardTypes[self::FLOAT])) {
static::$standardTypes[self::FLOAT] = new FloatType();
}
return static::$standardTypes[self::FLOAT];
}
/**
* @param Type|ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType|NonNull $wrappedType
*
* @return ListOfType
*
* @api
*/
public static function listOf($wrappedType)
public static function listOf(Type $wrappedType) : ListOfType
{
return new ListOfType($wrappedType);
}
/**
* @param NullableType $wrappedType
*
* @return NonNull
* @param callable|NullableType $wrappedType
*
* @api
*/
public static function nonNull($wrappedType)
public static function nonNull($wrappedType) : NonNull
{
return new NonNull($wrappedType);
}
/**
* Checks if the type is a builtin type
*
* @return bool
*/
public static function isBuiltInType(Type $type)
public static function isBuiltInType(Type $type) : bool
{
return in_array($type->name, array_keys(self::getAllBuiltInTypes()), true);
}
@@ -179,17 +160,25 @@ abstract class Type implements JsonSerializable
/**
* Returns all builtin scalar types
*
* @return Type[]
* @return ScalarType[]
*/
public static function getStandardTypes()
{
return self::getStandardType();
return [
self::ID => static::id(),
self::STRING => static::string(),
self::FLOAT => static::float(),
self::INT => static::int(),
self::BOOLEAN => static::boolean(),
];
}
/**
* @deprecated Use method getStandardTypes() instead
*
* @return Type[]
*
* @codeCoverageIgnore
*/
public static function getInternalTypes()
{
@@ -199,7 +188,7 @@ abstract class Type implements JsonSerializable
}
/**
* @param Type[] $types
* @param array<string, ScalarType> $types
*/
public static function overrideStandardTypes(array $types)
{
@@ -217,35 +206,26 @@ abstract class Type implements JsonSerializable
implode(', ', array_keys($standardTypes)),
Utils::printSafe($type->name ?? null)
);
$standardTypes[$type->name] = $type;
static::$standardTypes[$type->name] = $type;
}
self::$standardTypes = $standardTypes;
}
/**
* @param Type $type
*
* @return bool
*
* @api
*/
public static function isInputType($type)
public static function isInputType($type) : bool
{
return $type instanceof InputType &&
(
! $type instanceof WrappingType ||
self::getNamedType($type) instanceof InputType
);
return self::getNamedType($type) instanceof InputType;
}
/**
* @param Type $type
*
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType
*
* @api
*/
public static function getNamedType($type)
public static function getNamedType($type) : ?Type
{
if ($type === null) {
return null;
@@ -260,27 +240,19 @@ abstract class Type implements JsonSerializable
/**
* @param Type $type
*
* @return bool
*
* @api
*/
public static function isOutputType($type)
public static function isOutputType($type) : bool
{
return $type instanceof OutputType &&
(
! $type instanceof WrappingType ||
self::getNamedType($type) instanceof OutputType
);
return self::getNamedType($type) instanceof OutputType;
}
/**
* @param Type $type
*
* @return bool
*
* @api
*/
public static function isLeafType($type)
public static function isLeafType($type) : bool
{
return $type instanceof LeafType;
}
@@ -288,11 +260,9 @@ abstract class Type implements JsonSerializable
/**
* @param Type $type
*
* @return bool
*
* @api
*/
public static function isCompositeType($type)
public static function isCompositeType($type) : bool
{
return $type instanceof CompositeType;
}
@@ -300,52 +270,31 @@ abstract class Type implements JsonSerializable
/**
* @param Type $type
*
* @return bool
*
* @api
*/
public static function isAbstractType($type)
public static function isAbstractType($type) : bool
{
return $type instanceof AbstractType;
}
/**
* @param mixed $type
*
* @return mixed
*/
public static function assertType($type)
public static function assertType($type) : Type
{
Utils::invariant(
self::isType($type),
'Expected ' . Utils::printSafe($type) . ' to be a GraphQL type.'
);
assert($type instanceof Type, new InvariantViolation('Expected ' . Utils::printSafe($type) . ' to be a GraphQL type.'));
return $type;
}
/**
* @param Type $type
*
* @return bool
*
* @api
*/
public static function isType($type)
public static function getNullableType(Type $type) : Type
{
return $type instanceof Type;
}
/**
* @param Type $type
*
* @return NullableType
*
* @api
*/
public static function getNullableType($type)
{
return $type instanceof NonNull ? $type->getWrappedType() : $type;
return $type instanceof NonNull
? $type->getWrappedType()
: $type;
}
/**
@@ -377,13 +326,7 @@ abstract class Type implements JsonSerializable
*/
public function __toString()
{
try {
return $this->toString();
} catch (Exception $e) {
echo $e;
} catch (Throwable $e) {
echo $e;
}
return $this->toString();
}
/**
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace GraphQL\Type\Definition;
use GraphQL\Utils\Utils;
use function array_keys;
abstract class TypeWithFields extends Type implements HasFieldsType
{
/**
* Lazily initialized.
*
* @var array<string, FieldDefinition>
*/
private $fields;
private function initializeFields() : void
{
if (isset($this->fields)) {
return;
}
$fields = $this->config['fields'] ?? [];
$this->fields = FieldDefinition::defineFieldMap($this, $fields);
}
public function getField(string $name) : FieldDefinition
{
Utils::invariant($this->hasField($name), 'Field "%s" is not defined for type "%s"', $name, $this->name);
return $this->findField($name);
}
public function findField(string $name) : ?FieldDefinition
{
$this->initializeFields();
if (! isset($this->fields[$name])) {
return null;
}
if ($this->fields[$name] instanceof UnresolvedFieldDefinition) {
$this->fields[$name] = $this->fields[$name]->resolve();
}
return $this->fields[$name];
}
public function hasField(string $name) : bool
{
$this->initializeFields();
return isset($this->fields[$name]);
}
/** @inheritDoc */
public function getFields() : array
{
$this->initializeFields();
foreach ($this->fields as $name => $field) {
if (! ($field instanceof UnresolvedFieldDefinition)) {
continue;
}
$this->fields[$name] = $field->resolve();
}
return $this->fields;
}
/** @inheritDoc */
public function getFieldNames() : array
{
$this->initializeFields();
return array_keys($this->fields);
}
}

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