|
4 | 4 |
|
5 | 5 | namespace TheCodingMachine\GraphQLite; |
6 | 6 |
|
7 | | -use GraphQL\Deferred; |
| 7 | +use Closure; |
8 | 8 | use GraphQL\Error\ClientAware; |
9 | 9 | use GraphQL\Executor\Promise\Adapter\SyncPromise; |
10 | | -use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter; |
11 | | -use GraphQL\Executor\Promise\Promise; |
12 | 10 | use GraphQL\Language\AST\FieldDefinitionNode; |
13 | 11 | use GraphQL\Type\Definition\FieldDefinition; |
14 | 12 | use GraphQL\Type\Definition\ListOfType; |
|
22 | 20 | use TheCodingMachine\GraphQLite\Parameters\ParameterInterface; |
23 | 21 | use TheCodingMachine\GraphQLite\Parameters\SourceParameter; |
24 | 22 |
|
25 | | -use function array_filter; |
26 | | -use function array_map; |
27 | | - |
28 | 23 | /** |
29 | 24 | * A GraphQL field that maps to a PHP method automatically. |
30 | 25 | * |
@@ -79,42 +74,39 @@ public function __construct( |
79 | 74 | $callResolver = function (...$args) use ($originalResolver, $source, $resolver) { |
80 | 75 | $result = $resolver($source, ...$args); |
81 | 76 |
|
82 | | - try { |
83 | | - $this->assertReturnType($result); |
84 | | - } catch (TypeMismatchRuntimeException $e) { |
85 | | - $e->addInfo($this->name, $originalResolver->toString()); |
86 | | - |
87 | | - throw $e; |
88 | | - } |
89 | | - |
90 | | - return $result; |
| 77 | + return $this->resolveWithPromise($result, $originalResolver); |
91 | 78 | }; |
92 | 79 |
|
93 | | - $deferred = (bool) array_filter($toPassArgs, static fn (mixed $value) => $value instanceof SyncPromise); |
94 | | - |
95 | 80 | // GraphQL allows deferring resolving the field's value using promises, i.e. they call the resolve |
96 | 81 | // function ahead of time for all of the fields (allowing us to gather all calls and do something |
97 | 82 | // in batch, like prefetch) and then resolve the promises as needed. To support that for prefetch, |
98 | 83 | // we're checking if any of the resolved parameters returned a promise. If they did, we know |
99 | 84 | // that the value should also be resolved using a promise, so we're wrapping it in one. |
100 | | - return $deferred ? new Deferred(static function () use ($toPassArgs, $callResolver) { |
101 | | - $syncPromiseAdapter = new SyncPromiseAdapter(); |
102 | | - |
103 | | - // Wait for every deferred parameter. |
104 | | - $toPassArgs = array_map( |
105 | | - static fn (mixed $value) => $value instanceof SyncPromise ? $syncPromiseAdapter->wait(new Promise($value, $syncPromiseAdapter)) : $value, |
106 | | - $toPassArgs, |
107 | | - ); |
108 | | - |
109 | | - return $callResolver(...$toPassArgs); |
110 | | - }) : $callResolver(...$toPassArgs); |
| 85 | + return $this->deferred($toPassArgs, $callResolver); |
111 | 86 | }; |
112 | 87 |
|
113 | 88 | $config += $additionalConfig; |
114 | 89 |
|
115 | 90 | parent::__construct($config); |
116 | 91 | } |
117 | 92 |
|
| 93 | + private function resolveWithPromise(mixed $result, ResolverInterface $originalResolver): mixed |
| 94 | + { |
| 95 | + if ($result instanceof SyncPromise) { |
| 96 | + return $result->then(fn ($resolvedValue) => $this->resolveWithPromise($resolvedValue, $originalResolver)); |
| 97 | + } |
| 98 | + |
| 99 | + try { |
| 100 | + $this->assertReturnType($result); |
| 101 | + } catch (TypeMismatchRuntimeException $e) { |
| 102 | + $e->addInfo($this->name, $originalResolver->toString()); |
| 103 | + |
| 104 | + throw $e; |
| 105 | + } |
| 106 | + |
| 107 | + return $result; |
| 108 | + } |
| 109 | + |
118 | 110 | /** |
119 | 111 | * This method checks the returned value of the resolver to be sure it matches the documented return type. |
120 | 112 | * We are sure the returned value is of the correct type... except if the return type is type-hinted as an array. |
@@ -204,4 +196,24 @@ public static function paramsToArguments(string $name, array $parameters, object |
204 | 196 |
|
205 | 197 | return $toPassArgs; |
206 | 198 | } |
| 199 | + |
| 200 | + /** |
| 201 | + * @param array<int, mixed> $toPassArgs |
| 202 | + * Create Deferred if any of arguments contain promise |
| 203 | + */ |
| 204 | + public static function deferred(array $toPassArgs, Closure $callResolver): mixed |
| 205 | + { |
| 206 | + $deferredArgument = null; |
| 207 | + foreach ($toPassArgs as $position => $toPassArg) { |
| 208 | + if ($toPassArg instanceof SyncPromise) { |
| 209 | + $deferredArgument = $toPassArg->then(static function ($resolvedValue) use ($toPassArgs, $position, $callResolver) { |
| 210 | + $toPassArgs[$position] = $resolvedValue; |
| 211 | + return self::deferred($toPassArgs, $callResolver); |
| 212 | + }); |
| 213 | + break; |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + return $deferredArgument ?? $callResolver(...$toPassArgs); |
| 218 | + } |
207 | 219 | } |
0 commit comments