new Deps
This commit is contained in:
+110
-147
@@ -3,14 +3,13 @@
|
||||
namespace Nuwave\Lighthouse\Defer;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
use Illuminate\Support\Arr;
|
||||
use Nuwave\Lighthouse\Events\StartExecution;
|
||||
use Nuwave\Lighthouse\GraphQL;
|
||||
use Nuwave\Lighthouse\Events\ManipulateAST;
|
||||
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Nuwave\Lighthouse\Schema\AST\PartialParser;
|
||||
use Nuwave\Lighthouse\Support\Contracts\CreatesResponse;
|
||||
use Nuwave\Lighthouse\Support\Contracts\CanStreamResponse;
|
||||
use Nuwave\Lighthouse\Support\Contracts\CreatesResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Defer implements CreatesResponse
|
||||
{
|
||||
@@ -25,161 +24,134 @@ class Defer implements CreatesResponse
|
||||
protected $graphQL;
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
* @var \Nuwave\Lighthouse\Events\StartExecution
|
||||
*/
|
||||
protected $result = [];
|
||||
protected $startExecution;
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
* A map from paths to deferred resolvers.
|
||||
*
|
||||
* @var array<string, \Closure(): mixed>
|
||||
*/
|
||||
protected $deferred = [];
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
* Paths resolved during the current nesting of defers.
|
||||
*
|
||||
* @var array<int, mixed>
|
||||
*/
|
||||
protected $resolved = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* The entire result of resolving the query up until the current nesting.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $acceptFurtherDeferring = true;
|
||||
protected $result = [];
|
||||
|
||||
/**
|
||||
* Should further deferring happen?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $shouldDeferFurther = true;
|
||||
|
||||
/**
|
||||
* Are we currently streaming deferred results?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isStreaming = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @var float|int
|
||||
*/
|
||||
protected $maxExecutionTime = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $maxNestedFields = 0;
|
||||
|
||||
/**
|
||||
* @param \Nuwave\Lighthouse\Support\Contracts\CanStreamResponse $stream
|
||||
* @param \Nuwave\Lighthouse\GraphQL $graphQL
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(CanStreamResponse $stream, GraphQL $graphQL)
|
||||
public function __construct(CanStreamResponse $stream, GraphQL $graphQL, ConfigRepository $config)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
$this->graphQL = $graphQL;
|
||||
$this->maxNestedFields = config('lighthouse.defer.max_nested_fields', 0);
|
||||
|
||||
$executionTime = $config->get('lighthouse.defer.max_execution_ms', 0);
|
||||
if ($executionTime > 0) {
|
||||
$this->maxExecutionTime = microtime(true) + $executionTime * 1000;
|
||||
}
|
||||
|
||||
$this->maxNestedFields = $config->get('lighthouse.defer.max_nested_fields', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tracing directive on all fields of the query to enable tracing them.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Events\ManipulateAST $manipulateAST
|
||||
* @return void
|
||||
*/
|
||||
public function handleManipulateAST(ManipulateAST $manipulateAST): void
|
||||
public function handleStartExecution(StartExecution $startExecution): void
|
||||
{
|
||||
ASTHelper::attachDirectiveToObjectTypeFields(
|
||||
$manipulateAST->documentAST,
|
||||
PartialParser::directive('@deferrable')
|
||||
);
|
||||
|
||||
$manipulateAST->documentAST->setDirectiveDefinition(
|
||||
PartialParser::directiveDefinition('
|
||||
"""
|
||||
Use this directive on expensive or slow fields to resolve them asynchronously.
|
||||
Must not be placed upon:
|
||||
- Non-Nullable fields
|
||||
- Mutation root fields
|
||||
"""
|
||||
directive @defer(if: Boolean = true) on FIELD
|
||||
')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isStreaming(): bool
|
||||
{
|
||||
return $this->isStreaming;
|
||||
$this->startExecution = $startExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register deferred field.
|
||||
*
|
||||
* @param \Closure $resolver
|
||||
* @param string $path
|
||||
* @return mixed
|
||||
* @param \Closure(): mixed $resolver
|
||||
* @return mixed The data if it is already available.
|
||||
*/
|
||||
public function defer(Closure $resolver, string $path)
|
||||
{
|
||||
if ($data = Arr::get($this->result, "data.{$path}")) {
|
||||
$data = $this->getData($path);
|
||||
if ($data !== null) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ($this->isDeferred($path) || ! $this->acceptFurtherDeferring) {
|
||||
// If we have been here before, now is the time to resolve this field
|
||||
$deferredResolver = $this->deferred[$path] ?? null;
|
||||
if ($deferredResolver) {
|
||||
return $this->resolve($deferredResolver, $path);
|
||||
}
|
||||
|
||||
if (! $this->shouldDeferFurther) {
|
||||
return $this->resolve($resolver, $path);
|
||||
}
|
||||
|
||||
$this->deferred[$path] = $resolver;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Closure $originalResolver
|
||||
* @param string $path
|
||||
* @return mixed
|
||||
* @return mixed The data at the path
|
||||
*/
|
||||
public function findOrResolve(Closure $originalResolver, string $path)
|
||||
protected function getData(string $path)
|
||||
{
|
||||
if (! $this->hasData($path)) {
|
||||
if (isset($this->deferred[$path])) {
|
||||
unset($this->deferred[$path]);
|
||||
}
|
||||
|
||||
return $this->resolve($originalResolver, $path);
|
||||
}
|
||||
|
||||
return Arr::get($this->result, "data.{$path}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve field with data or resolver.
|
||||
*
|
||||
* @param \Closure $originalResolver
|
||||
* @param string $path
|
||||
* @return mixed
|
||||
* @param \Closure(): mixed $resolver
|
||||
* @return mixed The loaded data
|
||||
*/
|
||||
public function resolve(Closure $originalResolver, string $path)
|
||||
protected function resolve(Closure $resolver, string $path)
|
||||
{
|
||||
$isDeferred = $this->isDeferred($path);
|
||||
$resolver = $isDeferred
|
||||
? $this->deferred[$path]
|
||||
: $originalResolver;
|
||||
|
||||
if ($isDeferred) {
|
||||
$this->resolved[] = $path;
|
||||
|
||||
unset($this->deferred[$path]);
|
||||
}
|
||||
unset($this->deferred[$path]);
|
||||
$this->resolved [] = $path;
|
||||
|
||||
return $resolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return bool
|
||||
* @param \Closure(): mixed $originalResolver
|
||||
* @return mixed The loaded data
|
||||
*/
|
||||
public function isDeferred(string $path): bool
|
||||
public function findOrResolve(Closure $originalResolver, string $path)
|
||||
{
|
||||
return isset($this->deferred[$path]);
|
||||
if ($this->hasData($path)) {
|
||||
return $this->getData($path);
|
||||
}
|
||||
|
||||
return $originalResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function hasData(string $path): bool
|
||||
protected function hasData(string $path): bool
|
||||
{
|
||||
return Arr::has($this->result, "data.{$path}");
|
||||
}
|
||||
@@ -187,30 +159,26 @@ directive @defer(if: Boolean = true) on FIELD
|
||||
/**
|
||||
* Return either a final response or a stream of responses.
|
||||
*
|
||||
* @param mixed[] $result
|
||||
* @param array<string, mixed> $result
|
||||
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\StreamedResponse
|
||||
*/
|
||||
public function createResponse(array $result): Response
|
||||
{
|
||||
if (empty($this->deferred)) {
|
||||
if (! $this->hasRemainingDeferred()) {
|
||||
return response($result);
|
||||
}
|
||||
|
||||
$this->result = $result;
|
||||
$this->isStreaming = true;
|
||||
|
||||
return response()->stream(
|
||||
function () use ($result): void {
|
||||
function (): void {
|
||||
$this->stream();
|
||||
|
||||
$nested = 1;
|
||||
$this->result = $result;
|
||||
$this->isStreaming = true;
|
||||
$this->stream->stream($result, [], empty($this->deferred));
|
||||
|
||||
if ($executionTime = config('lighthouse.defer.max_execution_ms', 0)) {
|
||||
$this->maxExecutionTime = microtime(true) + ($executionTime * 1000);
|
||||
}
|
||||
|
||||
// TODO: Allow nested_levels to be set in config to break out of loop early.
|
||||
while (
|
||||
count($this->deferred)
|
||||
&& ! $this->executionTimeExpired()
|
||||
$this->hasRemainingDeferred()
|
||||
&& ! $this->maxExecutionTimeReached()
|
||||
&& ! $this->maxNestedFieldsResolved($nested)
|
||||
) {
|
||||
$nested++;
|
||||
@@ -218,48 +186,40 @@ directive @defer(if: Boolean = true) on FIELD
|
||||
}
|
||||
|
||||
// We've hit the max execution time or max nested levels of deferred fields.
|
||||
$this->shouldDeferFurther = false;
|
||||
|
||||
// We process remaining deferred fields, but are no longer allowing additional
|
||||
// fields to be deferred.
|
||||
if (count($this->deferred)) {
|
||||
$this->acceptFurtherDeferring = false;
|
||||
if ($this->hasRemainingDeferred()) {
|
||||
$this->executeDeferred();
|
||||
}
|
||||
},
|
||||
200,
|
||||
[
|
||||
// TODO: Allow headers to be set in config
|
||||
'X-Accel-Buffering' => 'no',
|
||||
'Content-Type' => 'multipart/mixed; boundary="-"',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $time
|
||||
* @return void
|
||||
*/
|
||||
public function setMaxExecutionTime(int $time): void
|
||||
protected function hasRemainingDeferred(): bool
|
||||
{
|
||||
$this->maxExecutionTime = $time;
|
||||
return count($this->deferred) > 0;
|
||||
}
|
||||
|
||||
protected function stream(): void
|
||||
{
|
||||
$this->stream->stream(
|
||||
$this->result,
|
||||
$this->resolved,
|
||||
! $this->hasRemainingDeferred()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override max nested fields.
|
||||
*
|
||||
* @param int $max
|
||||
* @return void
|
||||
* Check if we reached the maximum execution time.
|
||||
*/
|
||||
public function setMaxNestedFields(int $max): void
|
||||
{
|
||||
$this->maxNestedFields = $max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the maximum execution time has expired.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function executionTimeExpired(): bool
|
||||
protected function maxExecutionTimeReached(): bool
|
||||
{
|
||||
if ($this->maxExecutionTime === 0) {
|
||||
return false;
|
||||
@@ -270,9 +230,6 @@ directive @defer(if: Boolean = true) on FIELD
|
||||
|
||||
/**
|
||||
* Check if the maximum number of nested field has been resolved.
|
||||
*
|
||||
* @param int $nested
|
||||
* @return bool
|
||||
*/
|
||||
protected function maxNestedFieldsResolved(int $nested): bool
|
||||
{
|
||||
@@ -280,26 +237,32 @@ directive @defer(if: Boolean = true) on FIELD
|
||||
return false;
|
||||
}
|
||||
|
||||
return $nested >= $this->maxNestedFields;
|
||||
return $this->maxNestedFields <= $nested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute deferred fields.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function executeDeferred(): void
|
||||
{
|
||||
$this->result = app()->call(
|
||||
[$this->graphQL, 'executeRequest']
|
||||
$executionResult = $this->graphQL->executeQuery(
|
||||
$this->startExecution->query,
|
||||
$this->startExecution->context,
|
||||
$this->startExecution->variables,
|
||||
null,
|
||||
$this->startExecution->operationName
|
||||
);
|
||||
|
||||
$this->stream->stream(
|
||||
$this->result,
|
||||
$this->resolved,
|
||||
empty($this->deferred)
|
||||
);
|
||||
$this->result = $this->graphQL->serializable($executionResult);
|
||||
$this->stream();
|
||||
|
||||
$this->resolved = [];
|
||||
}
|
||||
|
||||
public function setMaxExecutionTime(float $time): void
|
||||
{
|
||||
$this->maxExecutionTime = $time;
|
||||
}
|
||||
|
||||
public function setMaxNestedFields(int $max): void
|
||||
{
|
||||
$this->maxNestedFields = $max;
|
||||
}
|
||||
}
|
||||
|
||||
+42
-20
@@ -2,43 +2,65 @@
|
||||
|
||||
namespace Nuwave\Lighthouse\Defer;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use GraphQL\Language\Parser;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Nuwave\Lighthouse\Events\ManipulateAST;
|
||||
use Nuwave\Lighthouse\Schema\Factories\DirectiveFactory;
|
||||
use Nuwave\Lighthouse\Events\RegisterDirectiveNamespaces;
|
||||
use Nuwave\Lighthouse\Events\StartExecution;
|
||||
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
|
||||
use Nuwave\Lighthouse\Support\Contracts\CreatesResponse;
|
||||
|
||||
class DeferServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Schema\Factories\DirectiveFactory $directiveFactory
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
|
||||
* @return void
|
||||
*/
|
||||
public function boot(DirectiveFactory $directiveFactory, Dispatcher $dispatcher): void
|
||||
public function register(): void
|
||||
{
|
||||
$directiveFactory->addResolved(
|
||||
DeferrableDirective::NAME,
|
||||
DeferrableDirective::class
|
||||
$this->app->singleton(Defer::class);
|
||||
$this->app->singleton(CreatesResponse::class, Defer::class);
|
||||
}
|
||||
|
||||
public function boot(Dispatcher $dispatcher): void
|
||||
{
|
||||
$dispatcher->listen(
|
||||
RegisterDirectiveNamespaces::class,
|
||||
static function (): string {
|
||||
return __NAMESPACE__;
|
||||
}
|
||||
);
|
||||
|
||||
$dispatcher->listen(
|
||||
ManipulateAST::class,
|
||||
Defer::class.'@handleManipulateAST'
|
||||
function (ManipulateAST $manipulateAST): void {
|
||||
$this->handleManipulateAST($manipulateAST);
|
||||
}
|
||||
);
|
||||
|
||||
$dispatcher->listen(
|
||||
StartExecution::class,
|
||||
Defer::class.'@handleStartExecution'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
* Set the tracing directive on all fields of the query to enable tracing them.
|
||||
*/
|
||||
public function register(): void
|
||||
public function handleManipulateAST(ManipulateAST $manipulateAST): void
|
||||
{
|
||||
$this->app->singleton(Defer::class);
|
||||
ASTHelper::attachDirectiveToObjectTypeFields(
|
||||
$manipulateAST->documentAST,
|
||||
Parser::constDirective(/** @lang GraphQL */ '@deferrable')
|
||||
);
|
||||
|
||||
$this->app->singleton(CreatesResponse::class, Defer::class);
|
||||
$manipulateAST->documentAST->setDirectiveDefinition(
|
||||
Parser::directiveDefinition(/** @lang GraphQL */ '
|
||||
"""
|
||||
Use this directive on expensive or slow fields to resolve them asynchronously.
|
||||
Must not be placed upon:
|
||||
- Non-Nullable fields
|
||||
- Mutation root fields
|
||||
"""
|
||||
directive @defer(if: Boolean = true) on FIELD
|
||||
')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+50
-65
@@ -4,31 +4,32 @@ namespace Nuwave\Lighthouse\Defer;
|
||||
|
||||
use Closure;
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\TypeNode;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Language\AST\NonNullTypeNode;
|
||||
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
|
||||
use Nuwave\Lighthouse\Schema\Values\FieldValue;
|
||||
use GraphQL\Language\AST\TypeNode;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use Nuwave\Lighthouse\ClientDirectives\ClientDirective;
|
||||
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
|
||||
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
|
||||
use Nuwave\Lighthouse\Schema\RootType;
|
||||
use Nuwave\Lighthouse\Schema\Values\FieldValue;
|
||||
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
|
||||
use Nuwave\Lighthouse\Support\Contracts\DefinedDirective;
|
||||
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
|
||||
|
||||
class DeferrableDirective extends BaseDirective implements DefinedDirective, FieldMiddleware
|
||||
class DeferrableDirective extends BaseDirective implements FieldMiddleware
|
||||
{
|
||||
const NAME = 'deferrable';
|
||||
const THE_DEFER_DIRECTIVE_CANNOT_BE_USED_ON_A_ROOT_MUTATION_FIELD = 'The @defer directive cannot be used on a root mutation field.';
|
||||
const THE_DEFER_DIRECTIVE_CANNOT_BE_USED_ON_A_NON_NULLABLE_FIELD = 'The @defer directive cannot be used on a Non-Nullable field.';
|
||||
public const THE_DEFER_DIRECTIVE_CANNOT_BE_USED_ON_A_ROOT_MUTATION_FIELD = 'The @defer directive cannot be used on a root mutation field.';
|
||||
public const THE_DEFER_DIRECTIVE_CANNOT_BE_USED_ON_A_NON_NULLABLE_FIELD = 'The @defer directive cannot be used on a Non-Nullable field.';
|
||||
public const DEFER_DIRECTIVE_NAME = 'defer';
|
||||
|
||||
public static function definition(): string
|
||||
{
|
||||
return /* @lang GraphQL */ <<<'SDL'
|
||||
return /** @lang GraphQL */ <<<'GRAPHQL'
|
||||
"""
|
||||
Do not use this directive directly, it is automatically added to the schema
|
||||
when using the defer extension.
|
||||
"""
|
||||
directive @deferrable on FIELD_DEFINITION
|
||||
SDL;
|
||||
GRAPHQL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,32 +37,11 @@ SDL;
|
||||
*/
|
||||
protected $defer;
|
||||
|
||||
/**
|
||||
* @param \Nuwave\Lighthouse\Defer\Defer $defer
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Defer $defer)
|
||||
{
|
||||
$this->defer = $defer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the directive.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the field directive.
|
||||
*
|
||||
* @param \Nuwave\Lighthouse\Schema\Values\FieldValue $fieldValue
|
||||
* @param \Closure $next
|
||||
* @return \Nuwave\Lighthouse\Schema\Values\FieldValue
|
||||
*/
|
||||
public function handleField(FieldValue $fieldValue, Closure $next): FieldValue
|
||||
{
|
||||
$previousResolver = $fieldValue->getResolver();
|
||||
@@ -78,9 +58,7 @@ SDL;
|
||||
return $this->defer->defer($wrappedResolver, $path);
|
||||
}
|
||||
|
||||
return $this->defer->isStreaming()
|
||||
? $this->defer->findOrResolve($wrappedResolver, $path)
|
||||
: $previousResolver($root, $args, $context, $resolveInfo);
|
||||
return $this->defer->findOrResolve($wrappedResolver, $path);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -90,49 +68,56 @@ SDL;
|
||||
/**
|
||||
* Determine if field should be deferred.
|
||||
*
|
||||
* @param \GraphQL\Language\AST\TypeNode $fieldType
|
||||
* @param \GraphQL\Type\Definition\ResolveInfo $resolveInfo
|
||||
* @return bool
|
||||
*
|
||||
* @throws \GraphQL\Error\Error
|
||||
*/
|
||||
protected function shouldDefer(TypeNode $fieldType, ResolveInfo $resolveInfo): bool
|
||||
{
|
||||
foreach ($resolveInfo->fieldNodes as $fieldNode) {
|
||||
$deferDirective = ASTHelper::directiveDefinition($fieldNode, 'defer');
|
||||
if (! $deferDirective) {
|
||||
return false;
|
||||
}
|
||||
$defers = (new ClientDirective(self::DEFER_DIRECTIVE_NAME))->forField($resolveInfo);
|
||||
|
||||
if ($resolveInfo->parentType->name === 'Mutation') {
|
||||
if ($this->anyFieldHasDefer($defers)) {
|
||||
if ($resolveInfo->parentType->name === RootType::MUTATION) {
|
||||
throw new Error(self::THE_DEFER_DIRECTIVE_CANNOT_BE_USED_ON_A_ROOT_MUTATION_FIELD);
|
||||
}
|
||||
|
||||
if (! ASTHelper::directiveArgValue($deferDirective, 'if', true)) {
|
||||
return false;
|
||||
if ($fieldType instanceof NonNullTypeNode) {
|
||||
throw new Error(self::THE_DEFER_DIRECTIVE_CANNOT_BE_USED_ON_A_NON_NULLABLE_FIELD);
|
||||
}
|
||||
}
|
||||
|
||||
$skipDirective = ASTHelper::directiveDefinition($fieldNode, 'skip');
|
||||
if (
|
||||
$skipDirective
|
||||
&& ASTHelper::directiveArgValue($skipDirective, 'if') === true
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$includeDirective = ASTHelper::directiveDefinition($fieldNode, 'include');
|
||||
if (
|
||||
$includeDirective
|
||||
&& ASTHelper::directiveArgValue($includeDirective, 'if') === false
|
||||
) {
|
||||
// Following the semantics of Apollo:
|
||||
// All declarations of a field have to contain @defer for the field to be deferred
|
||||
foreach ($defers as $defer) {
|
||||
if ($defer === null || $defer === [Directive::IF_ARGUMENT_NAME => false]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fieldType instanceof NonNullTypeNode) {
|
||||
throw new Error(self::THE_DEFER_DIRECTIVE_CANNOT_BE_USED_ON_A_NON_NULLABLE_FIELD);
|
||||
$skips = (new ClientDirective(Directive::SKIP_NAME))->forField($resolveInfo);
|
||||
foreach ($skips as $skip) {
|
||||
if ($skip === [Directive::IF_ARGUMENT_NAME => true]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
$includes = (new ClientDirective(Directive::INCLUDE_NAME))->forField($resolveInfo);
|
||||
|
||||
return ! in_array(
|
||||
[Directive::IF_ARGUMENT_NAME => false],
|
||||
$includes,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array<string, mixed>|null> $defers
|
||||
*/
|
||||
protected function anyFieldHasDefer(array $defers): bool
|
||||
{
|
||||
foreach ($defers as $defer) {
|
||||
if ($defer !== null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user