new Deps
This commit is contained in:
+11
-50
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+147
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-12
@@ -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);
|
||||
}
|
||||
|
||||
+289
-347
File diff suppressed because it is too large
Load Diff
+140
-84
@@ -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 */
|
||||
|
||||
+52
-30
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
+2
-2
@@ -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 */
|
||||
|
||||
+2
-2
@@ -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 */
|
||||
|
||||
+5
-2
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
+332
-338
File diff suppressed because it is too large
Load Diff
+73
-54
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
+48
-33
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user