-
-
Notifications
You must be signed in to change notification settings - Fork 469
Expand file tree
/
Copy pathDeferrableDirective.php
More file actions
100 lines (80 loc) · 3.62 KB
/
Copy pathDeferrableDirective.php
File metadata and controls
100 lines (80 loc) · 3.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?php declare(strict_types=1);
namespace Nuwave\Lighthouse\Defer;
use GraphQL\Error\Error;
use GraphQL\Language\AST\NonNullTypeNode;
use GraphQL\Language\AST\TypeNode;
use GraphQL\Type\Definition\Directive;
use Nuwave\Lighthouse\ClientDirectives\ClientDirective;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\RootType;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
class DeferrableDirective extends BaseDirective implements FieldMiddleware
{
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 */ <<<'GRAPHQL'
"""
Do not use this directive directly, it is automatically added to the schema
when using the defer extension.
"""
directive @deferrable on FIELD_DEFINITION
GRAPHQL;
}
public function __construct(
protected Defer $defer,
) {}
public function handleField(FieldValue $fieldValue): void
{
$fieldType = $fieldValue->getField()->type;
$fieldValue->wrapResolver(fn (callable $resolver): \Closure => function (mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver, $fieldType) {
$wrappedResolver = static fn (): mixed => $resolver($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);
});
}
/** Determine if the field should be deferred. */
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);
if (in_array([Directive::IF_ARGUMENT_NAME => true], $skips, strict: true)) {
return false;
}
$includes = (new ClientDirective(Directive::INCLUDE_NAME))->forField($resolveInfo);
return ! in_array([Directive::IF_ARGUMENT_NAME => false], $includes, strict: 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;
}
}