defer = $defer; } public function handleField(FieldValue $fieldValue, Closure $next): FieldValue { $previousResolver = $fieldValue->getResolver(); $fieldType = $fieldValue->getField()->type; $fieldValue->setResolver( function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($previousResolver, $fieldType) { $wrappedResolver = function () use ($previousResolver, $root, $args, $context, $resolveInfo) { return $previousResolver($root, $args, $context, $resolveInfo); }; $path = implode('.', $resolveInfo->path); if ($this->shouldDefer($fieldType, $resolveInfo)) { return $this->defer->defer($wrappedResolver, $path); } return $this->defer->findOrResolve($wrappedResolver, $path); } ); return $next($fieldValue); } /** * Determine if field should be deferred. * * @throws \GraphQL\Error\Error */ protected function shouldDefer(TypeNode $fieldType, ResolveInfo $resolveInfo): bool { $defers = (new ClientDirective(self::DEFER_DIRECTIVE_NAME))->forField($resolveInfo); 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 ($fieldType instanceof NonNullTypeNode) { throw new Error(self::THE_DEFER_DIRECTIVE_CANNOT_BE_USED_ON_A_NON_NULLABLE_FIELD); } } // 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; } } $skips = (new ClientDirective(Directive::SKIP_NAME))->forField($resolveInfo); foreach ($skips as $skip) { if ($skip === [Directive::IF_ARGUMENT_NAME => true]) { return false; } } $includes = (new ClientDirective(Directive::INCLUDE_NAME))->forField($resolveInfo); return ! in_array( [Directive::IF_ARGUMENT_NAME => false], $includes, true ); } /** * @param array|null> $defers */ protected function anyFieldHasDefer(array $defers): bool { foreach ($defers as $defer) { if ($defer !== null) { return true; } } return false; } }