From 32b4f1d728a55d76970971826d872bc8f7987598 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Tue, 29 Nov 2022 17:52:45 +0200 Subject: [PATCH 01/36] Add generics --- src/Executor/Promise/Promise.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Executor/Promise/Promise.php b/src/Executor/Promise/Promise.php index b0dbb0c7a..f5026a4ae 100644 --- a/src/Executor/Promise/Promise.php +++ b/src/Executor/Promise/Promise.php @@ -9,6 +9,7 @@ /** * Convenience wrapper for promises represented by Promise Adapter. + * @template T */ class Promise { @@ -30,6 +31,17 @@ public function __construct($adoptedPromise, PromiseAdapter $adapter) $this->adapter = $adapter; } + /** + * @template TFulfilled of mixed + * @template TRejected of mixed + * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled + * @param (callable(mixed): (Promise|TRejected))|null $onRejected + * @return Promise<( + * $onFulfilled is not null + * ? ($onRejected is not null ? TFulfilled|TRejected : TFulfilled) + * : ($onRejected is not null ? TRejected : T) + * )> + */ public function then(?callable $onFulfilled = null, ?callable $onRejected = null): Promise { return $this->adapter->then($this, $onFulfilled, $onRejected); From 0b65301a2508ad2b4a0f4a9a0758679280f48644 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Tue, 29 Nov 2022 15:54:12 +0000 Subject: [PATCH 02/36] Apply php-cs-fixer changes --- src/Executor/Promise/Promise.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Executor/Promise/Promise.php b/src/Executor/Promise/Promise.php index f5026a4ae..25ce16507 100644 --- a/src/Executor/Promise/Promise.php +++ b/src/Executor/Promise/Promise.php @@ -9,6 +9,7 @@ /** * Convenience wrapper for promises represented by Promise Adapter. + * * @template T */ class Promise @@ -34,8 +35,10 @@ public function __construct($adoptedPromise, PromiseAdapter $adapter) /** * @template TFulfilled of mixed * @template TRejected of mixed + * * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled * @param (callable(mixed): (Promise|TRejected))|null $onRejected + * * @return Promise<( * $onFulfilled is not null * ? ($onRejected is not null ? TFulfilled|TRejected : TFulfilled) From d15eea57de7a01f05e28d407638d88eab86f9e45 Mon Sep 17 00:00:00 2001 From: Yup Date: Tue, 29 Nov 2022 18:32:12 +0200 Subject: [PATCH 03/36] Update src/Executor/Promise/Promise.php Co-authored-by: Benedikt Franke --- src/Executor/Promise/Promise.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Executor/Promise/Promise.php b/src/Executor/Promise/Promise.php index 25ce16507..fa1701fa8 100644 --- a/src/Executor/Promise/Promise.php +++ b/src/Executor/Promise/Promise.php @@ -33,8 +33,8 @@ public function __construct($adoptedPromise, PromiseAdapter $adapter) } /** - * @template TFulfilled of mixed - * @template TRejected of mixed + * @template TFulfilled + * @template TRejected * * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled * @param (callable(mixed): (Promise|TRejected))|null $onRejected From d5fa6442eb589241b3fa7b47898a48780068bf9f Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 30 Nov 2022 20:55:22 +0200 Subject: [PATCH 04/36] Add generics --- src/Executor/Executor.php | 2 + src/Executor/ExecutorImplementation.php | 1 + .../Promise/Adapter/SyncPromiseAdapter.php | 37 ++++++++++++++++++- src/Executor/Promise/PromiseAdapter.php | 20 +++++++++- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 88c132d2a..c652006b8 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -132,6 +132,8 @@ public static function execute( * @param mixed $contextValue * @param array|null $variableValues * + * @return Promise + * * @phpstan-param FieldResolver|null $fieldResolver * * @api diff --git a/src/Executor/ExecutorImplementation.php b/src/Executor/ExecutorImplementation.php index b614d8b73..92a4d6c7d 100644 --- a/src/Executor/ExecutorImplementation.php +++ b/src/Executor/ExecutorImplementation.php @@ -8,6 +8,7 @@ interface ExecutorImplementation { /** * Returns promise of {@link ExecutionResult}. Promise should always resolve, never reject. + * @return Promise */ public function doExecute(): Promise; } diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index 78286e50f..091b3a267 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -31,6 +31,19 @@ public function convertThenable($thenable): Promise return new Promise($thenable, $this); } + /** + * @template V + * @template TFulfilled of mixed + * @template TRejected of mixed + * @param Promise $promise + * @param (callable(V): (Promise|TFulfilled))|null $onFulfilled + * @param (callable(mixed): (Promise|TRejected))|null $onRejected + * @return Promise<( + * $onFulfilled is not null + * ? ($onRejected is not null ? TFulfilled|TRejected : TFulfilled) + * : ($onRejected is not null ? TRejected : T) + * )> + */ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise { $adoptedPromise = $promise->adoptedPromise; @@ -55,6 +68,11 @@ public function create(callable $resolver): Promise return new Promise($promise, $this); } + /** + * @template V + * @param V $value + * @return Promise + */ public function createFulfilled($value = null): Promise { $promise = new SyncPromise(); @@ -62,6 +80,11 @@ public function createFulfilled($value = null): Promise return new Promise($promise->resolve($value), $this); } + /** + * @template V of \Throwable + * @param V $reason + * @return Promise + */ public function createRejected(\Throwable $reason): Promise { $promise = new SyncPromise(); @@ -69,6 +92,11 @@ public function createRejected(\Throwable $reason): Promise return new Promise($promise->reject($reason), $this); } + /** + * @template V + * @param iterable|V> $promisesOrValues + * @return Promise + */ public function all(iterable $promisesOrValues): Promise { \assert( @@ -113,7 +141,10 @@ static function ($value) use ($index, &$count, $total, &$result, $all): void { /** * Synchronously wait when promise completes. * - * @return mixed + * @template V + * @param Promise $promise + * @return V + * @throws InvariantViolation */ public function wait(Promise $promise) { @@ -144,6 +175,8 @@ public function wait(Promise $promise) /** * Execute just before starting to run promise completion. + * @template V + * @param Promise $promise */ protected function beforeWait(Promise $promise): void { @@ -151,6 +184,8 @@ protected function beforeWait(Promise $promise): void /** * Execute while running promise completion. + * @template V + * @param Promise $promise */ protected function onWait(Promise $promise): void { diff --git a/src/Executor/Promise/PromiseAdapter.php b/src/Executor/Promise/PromiseAdapter.php index a91ee243d..b9e489852 100644 --- a/src/Executor/Promise/PromiseAdapter.php +++ b/src/Executor/Promise/PromiseAdapter.php @@ -29,6 +29,17 @@ public function convertThenable($thenable): Promise; * Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described * in Promises/A+ specs. Then returns new wrapped instance of GraphQL\Executor\Promise\Promise. * + * @template T + * @template TFulfilled of mixed + * @template TRejected of mixed + * @param Promise $promise + * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled + * @param (callable(mixed): (Promise|TRejected))|null $onRejected + * @return Promise<( + * $onFulfilled is not null + * ? ($onRejected is not null ? TFulfilled|TRejected : TFulfilled) + * : ($onRejected is not null ? TRejected : T) + * )> * @api */ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise; @@ -36,6 +47,7 @@ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable /** * Creates a Promise from the given resolver callable. * + * @template V * @param callable(callable $resolve, callable $reject): void $resolver * * @api @@ -45,7 +57,9 @@ public function create(callable $resolver): Promise; /** * Creates a fulfilled Promise for a value if the value is not a promise. * - * @param mixed $value + * @template V + * @param V $value + * @return Promise * * @api */ @@ -64,7 +78,9 @@ public function createRejected(\Throwable $reason): Promise; * Given an iterable of promises (or values), returns a promise that is fulfilled when all the * items in the iterable are fulfilled. * - * @param iterable $promisesOrValues + * @template V + * @param iterable|V> $promisesOrValues + * @return Promise * * @api */ From 77b7e84cf6db16642150ddbdb0a54d2186114cc0 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 30 Nov 2022 18:56:17 +0000 Subject: [PATCH 05/36] Prettify docs --- docs/class-reference.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/class-reference.md b/docs/class-reference.md index 8e9232b55..4b2bad15c 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -1305,6 +1305,8 @@ static function execute( * @param mixed $contextValue * @param array|null $variableValues * + * @return Promise + * * @phpstan-param FieldResolver|null $fieldResolver * * @api @@ -1471,6 +1473,17 @@ function convertThenable($thenable): GraphQL\Executor\Promise\Promise * Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described * in Promises/A+ specs. Then returns new wrapped instance of GraphQL\Executor\Promise\Promise. * + * @template T + * @template TFulfilled of mixed + * @template TRejected of mixed + * @param Promise $promise + * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled + * @param (callable(mixed): (Promise|TRejected))|null $onRejected + * @return Promise<( + * $onFulfilled is not null + * ? ($onRejected is not null ? TFulfilled|TRejected : TFulfilled) + * : ($onRejected is not null ? TRejected : T) + * )> * @api */ function then( @@ -1484,6 +1497,7 @@ function then( /** * Creates a Promise from the given resolver callable. * + * @template V * @param callable(callable $resolve, callable $reject): void $resolver * * @api @@ -1495,7 +1509,9 @@ function create(callable $resolver): GraphQL\Executor\Promise\Promise /** * Creates a fulfilled Promise for a value if the value is not a promise. * - * @param mixed $value + * @template V + * @param V $value + * @return Promise * * @api */ @@ -1518,7 +1534,9 @@ function createRejected(Throwable $reason): GraphQL\Executor\Promise\Promise * Given an iterable of promises (or values), returns a promise that is fulfilled when all the * items in the iterable are fulfilled. * - * @param iterable $promisesOrValues + * @template V + * @param iterable|V> $promisesOrValues + * @return Promise * * @api */ From 2b6c89881a32d6cb9540c25fa409946b870f5350 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 30 Nov 2022 20:56:51 +0200 Subject: [PATCH 06/36] Add generics --- tests/Executor/SyncTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Executor/SyncTest.php b/tests/Executor/SyncTest.php index 1e245aa62..4e2a5769f 100644 --- a/tests/Executor/SyncTest.php +++ b/tests/Executor/SyncTest.php @@ -77,6 +77,7 @@ public function testDoesNotReturnAPromiseForInitialErrors(): void /** * @param mixed $rootValue + * @return Promise */ private function execute(Schema $schema, DocumentNode $doc, $rootValue = null): Promise { @@ -85,6 +86,7 @@ private function execute(Schema $schema, DocumentNode $doc, $rootValue = null): /** * @param array $expectedFinalArray + * @param Promise $actualResult */ private static function assertSync(array $expectedFinalArray, Promise $actualResult): void { @@ -145,6 +147,7 @@ public function testReturnsAPromiseIfAnyFieldIsAsynchronous(): void /** * @param array $expectedFinalArray + * @param Promise $actualResult */ private function assertAsync(array $expectedFinalArray, Promise $actualResult): void { @@ -183,6 +186,7 @@ public function testDoesNotReturnAPromiseForSyntaxErrors(): void /** * @param mixed $rootValue + * @return Promise */ private function graphqlSync(Schema $schema, string $doc, $rootValue = null): Promise { From 1121827535f46a115268890851288c1a22ec8cc2 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 30 Nov 2022 18:58:42 +0000 Subject: [PATCH 07/36] Apply php-cs-fixer changes --- src/Executor/ExecutorImplementation.php | 1 + .../Promise/Adapter/SyncPromiseAdapter.php | 17 ++++++++++++++++- src/Executor/Promise/PromiseAdapter.php | 8 ++++++++ tests/Executor/SyncTest.php | 2 ++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Executor/ExecutorImplementation.php b/src/Executor/ExecutorImplementation.php index 92a4d6c7d..b0e4726c2 100644 --- a/src/Executor/ExecutorImplementation.php +++ b/src/Executor/ExecutorImplementation.php @@ -8,6 +8,7 @@ interface ExecutorImplementation { /** * Returns promise of {@link ExecutionResult}. Promise should always resolve, never reject. + * * @return Promise */ public function doExecute(): Promise; diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index 091b3a267..e0394675a 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -35,9 +35,11 @@ public function convertThenable($thenable): Promise * @template V * @template TFulfilled of mixed * @template TRejected of mixed + * * @param Promise $promise * @param (callable(V): (Promise|TFulfilled))|null $onFulfilled * @param (callable(mixed): (Promise|TRejected))|null $onRejected + * * @return Promise<( * $onFulfilled is not null * ? ($onRejected is not null ? TFulfilled|TRejected : TFulfilled) @@ -70,7 +72,9 @@ public function create(callable $resolver): Promise /** * @template V + * * @param V $value + * * @return Promise */ public function createFulfilled($value = null): Promise @@ -82,7 +86,9 @@ public function createFulfilled($value = null): Promise /** * @template V of \Throwable + * * @param V $reason + * * @return Promise */ public function createRejected(\Throwable $reason): Promise @@ -94,7 +100,9 @@ public function createRejected(\Throwable $reason): Promise /** * @template V + * * @param iterable|V> $promisesOrValues + * * @return Promise */ public function all(iterable $promisesOrValues): Promise @@ -142,9 +150,12 @@ static function ($value) use ($index, &$count, $total, &$result, $all): void { * Synchronously wait when promise completes. * * @template V + * * @param Promise $promise - * @return V + * * @throws InvariantViolation + * + * @return V */ public function wait(Promise $promise) { @@ -175,7 +186,9 @@ public function wait(Promise $promise) /** * Execute just before starting to run promise completion. + * * @template V + * * @param Promise $promise */ protected function beforeWait(Promise $promise): void @@ -184,7 +197,9 @@ protected function beforeWait(Promise $promise): void /** * Execute while running promise completion. + * * @template V + * * @param Promise $promise */ protected function onWait(Promise $promise): void diff --git a/src/Executor/Promise/PromiseAdapter.php b/src/Executor/Promise/PromiseAdapter.php index b9e489852..ff1826312 100644 --- a/src/Executor/Promise/PromiseAdapter.php +++ b/src/Executor/Promise/PromiseAdapter.php @@ -32,14 +32,17 @@ public function convertThenable($thenable): Promise; * @template T * @template TFulfilled of mixed * @template TRejected of mixed + * * @param Promise $promise * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled * @param (callable(mixed): (Promise|TRejected))|null $onRejected + * * @return Promise<( * $onFulfilled is not null * ? ($onRejected is not null ? TFulfilled|TRejected : TFulfilled) * : ($onRejected is not null ? TRejected : T) * )> + * * @api */ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise; @@ -48,6 +51,7 @@ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable * Creates a Promise from the given resolver callable. * * @template V + * * @param callable(callable $resolve, callable $reject): void $resolver * * @api @@ -58,7 +62,9 @@ public function create(callable $resolver): Promise; * Creates a fulfilled Promise for a value if the value is not a promise. * * @template V + * * @param V $value + * * @return Promise * * @api @@ -79,7 +85,9 @@ public function createRejected(\Throwable $reason): Promise; * items in the iterable are fulfilled. * * @template V + * * @param iterable|V> $promisesOrValues + * * @return Promise * * @api diff --git a/tests/Executor/SyncTest.php b/tests/Executor/SyncTest.php index 4e2a5769f..00bc73601 100644 --- a/tests/Executor/SyncTest.php +++ b/tests/Executor/SyncTest.php @@ -77,6 +77,7 @@ public function testDoesNotReturnAPromiseForInitialErrors(): void /** * @param mixed $rootValue + * * @return Promise */ private function execute(Schema $schema, DocumentNode $doc, $rootValue = null): Promise @@ -186,6 +187,7 @@ public function testDoesNotReturnAPromiseForSyntaxErrors(): void /** * @param mixed $rootValue + * * @return Promise */ private function graphqlSync(Schema $schema, string $doc, $rootValue = null): Promise From abd57fea29ac52ce733593e14562ec1bcdccd7bb Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 30 Nov 2022 21:06:59 +0200 Subject: [PATCH 08/36] Add generics --- src/Executor/ReferenceExecutor.php | 33 +++++++++++++++++++----------- src/GraphQL.php | 1 + src/Server/Helper.php | 8 ++++---- src/Server/StandardServer.php | 6 +++--- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index b6464af41..2d5b5badb 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -107,8 +107,14 @@ public static function create( if (\is_array($exeContext)) { return new class($promiseAdapter->createFulfilled(new ExecutionResult(null, $exeContext))) implements ExecutorImplementation { + /** + * @var Promise $result + */ private Promise $result; + /** + * @param Promise $result + */ public function __construct(Promise $result) { $this->result = $result; @@ -250,7 +256,7 @@ public function doExecute(): Promise /** * @param mixed $data * - * @return ExecutionResult|Promise + * @return ExecutionResult|Promise */ protected function buildResponse($data) { @@ -276,7 +282,7 @@ protected function buildResponse($data) * * @param mixed $rootValue * - * @return array|Promise|\stdClass|null + * @return Promise|\stdClass|null>|array|\stdClass|null */ protected function executeOperation(OperationDefinitionNode $operation, $rootValue) { @@ -308,6 +314,7 @@ protected function executeOperation(OperationDefinitionNode $operation, $rootVal /** * @param mixed $error + * @return Promise */ public function onError($error): ?Promise { @@ -516,7 +523,7 @@ protected function doesFragmentConditionMatch(Node $fragment, ObjectType $type): * * @phpstan-param Fields $fields * - * @return array|Promise|\stdClass + * @return Promise|\stdClass>|array|\stdClass */ protected function executeFieldsSerially(ObjectType $parentType, $rootValue, array $path, \ArrayObject $fields) { @@ -709,7 +716,7 @@ protected function resolveFieldValueOrError( * * @param mixed $result * - * @return array|Promise|\stdClass|null + * @return Promise|\stdClass|null>|array|\stdClass|null */ protected function completeValueCatchingError( Type $returnType, @@ -884,7 +891,9 @@ protected function isPromise($value): bool * Only returns the value if it acts like a Promise, i.e. has a "then" function, * otherwise returns null. * - * @param mixed $value + * @template T + * @param T $value + * @return Promise|null */ protected function getPromise($value): ?Promise { @@ -938,7 +947,7 @@ function ($previous, $value) use ($callback) { * * @throws \Exception * - * @return array|Promise|\stdClass + * @return Promise|\stdClass>|array|\stdClass */ protected function completeListValue( ListOfType $returnType, @@ -1005,7 +1014,7 @@ protected function completeLeafValue(LeafType $returnType, &$result) * * @throws Error * - * @return array|Promise|\stdClass + * @return Promise|\stdClass>|array|\stdClass */ protected function completeAbstractValue( AbstractType $returnType, @@ -1069,7 +1078,7 @@ protected function completeAbstractValue( * @param mixed|null $contextValue * @param AbstractType&Type $abstractType * - * @return Promise|Type|string|null + * @return Promise|Type|string|null */ protected function defaultTypeResolver($value, $contextValue, ResolveInfo $info, AbstractType $abstractType) { @@ -1128,7 +1137,7 @@ protected function defaultTypeResolver($value, $contextValue, ResolveInfo $info, * * @throws Error * - * @return array|Promise|\stdClass + * @return Promise|\stdClass>|array|\stdClass */ protected function completeObjectValue( ObjectType $returnType, @@ -1201,7 +1210,7 @@ protected function invalidReturnTypeError( * * @throws Error * - * @return array|Promise|\stdClass + * @return Promise|\stdClass>|array|\stdClass */ protected function collectAndExecuteSubfields( ObjectType $returnType, @@ -1256,7 +1265,7 @@ protected function collectSubFields(ObjectType $returnType, \ArrayObject $fieldN * * @phpstan-param Fields $fields * - * @return Promise|\stdClass|array + * @return Promise<\stdClass|array>|\stdClass|array */ protected function executeFields(ObjectType $parentType, $rootValue, array $path, \ArrayObject $fields) { @@ -1310,7 +1319,7 @@ protected static function fixResultsIfEmptyArray($results) * Transform an associative array with Promises to a Promise which resolves to an * associative array where all Promises were resolved. * - * @param array $assoc + * @param array|mixed> $assoc */ protected function promiseForAssocArray(array $assoc): Promise { diff --git a/src/GraphQL.php b/src/GraphQL.php index bded57793..929872384 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -108,6 +108,7 @@ public static function executeQuery( * @param mixed $context * @param array|null $variableValues * @param array|null $validationRules + * @return Promise * * @api */ diff --git a/src/Server/Helper.php b/src/Server/Helper.php index 1cc99bf0c..81958c67a 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -175,7 +175,7 @@ public function validateOperationParams(OperationParams $params): array * Executes GraphQL operation with given server configuration and returns execution result * (or promise when promise adapter is different from SyncPromiseAdapter). * - * @return ExecutionResult|Promise + * @return ExecutionResult|Promise * * @api */ @@ -392,7 +392,7 @@ protected function resolveContextValue( /** * Send response using standard PHP `header()` and `echo`. * - * @param Promise|ExecutionResult|array $result + * @param Promise>|ExecutionResult|array $result * * @api */ @@ -516,9 +516,9 @@ protected function assertJsonObjectOrArray($bodyParams): void /** * Converts query execution result to PSR-7 response. * - * @param Promise|ExecutionResult|array $result + * @param Promise>|ExecutionResult|array $result * - * @return Promise|ResponseInterface + * @return Promise|ResponseInterface * * @api */ diff --git a/src/Server/StandardServer.php b/src/Server/StandardServer.php index ede83407c..a9780497c 100644 --- a/src/Server/StandardServer.php +++ b/src/Server/StandardServer.php @@ -90,7 +90,7 @@ public function handleRequest($parsedBody = null): void * * @param OperationParams|array $parsedBody * - * @return ExecutionResult|array|Promise + * @return ExecutionResult|array|Promise> * * @api */ @@ -113,7 +113,7 @@ public function executeRequest($parsedBody = null) * See `executePsrRequest()` if you prefer to create response yourself * (e.g. using specific JsonResponse instance of some framework). * - * @return ResponseInterface|Promise + * @return ResponseInterface|Promise * * @api */ @@ -131,7 +131,7 @@ public function processPsrRequest( * Executes GraphQL operation and returns execution result * (or promise when promise adapter is different from SyncPromiseAdapter). * - * @return ExecutionResult|array|Promise + * @return ExecutionResult|array|Promise> * * @api */ From 032ed9fd82fa70b70e78e51c5a8b32b11bdb8582 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 30 Nov 2022 19:08:01 +0000 Subject: [PATCH 09/36] Prettify docs --- docs/class-reference.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/class-reference.md b/docs/class-reference.md index 4b2bad15c..04a452253 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -75,6 +75,7 @@ static function executeQuery( * @param mixed $context * @param array|null $variableValues * @param array|null $validationRules + * @return Promise * * @api */ @@ -1476,14 +1477,17 @@ function convertThenable($thenable): GraphQL\Executor\Promise\Promise * @template T * @template TFulfilled of mixed * @template TRejected of mixed + * * @param Promise $promise * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled * @param (callable(mixed): (Promise|TRejected))|null $onRejected + * * @return Promise<( * $onFulfilled is not null * ? ($onRejected is not null ? TFulfilled|TRejected : TFulfilled) * : ($onRejected is not null ? TRejected : T) * )> + * * @api */ function then( @@ -1498,6 +1502,7 @@ function then( * Creates a Promise from the given resolver callable. * * @template V + * * @param callable(callable $resolve, callable $reject): void $resolver * * @api @@ -1510,7 +1515,9 @@ function create(callable $resolver): GraphQL\Executor\Promise\Promise * Creates a fulfilled Promise for a value if the value is not a promise. * * @template V + * * @param V $value + * * @return Promise * * @api @@ -1535,7 +1542,9 @@ function createRejected(Throwable $reason): GraphQL\Executor\Promise\Promise * items in the iterable are fulfilled. * * @template V + * * @param iterable|V> $promisesOrValues + * * @return Promise * * @api @@ -1894,7 +1903,7 @@ function handleRequest($parsedBody = null): void * * @param OperationParams|array $parsedBody * - * @return ExecutionResult|array|Promise + * @return ExecutionResult|array|Promise> * * @api */ @@ -1908,7 +1917,7 @@ function executeRequest($parsedBody = null) * See `executePsrRequest()` if you prefer to create response yourself * (e.g. using specific JsonResponse instance of some framework). * - * @return ResponseInterface|Promise + * @return ResponseInterface|Promise * * @api */ @@ -1924,7 +1933,7 @@ function processPsrRequest( * Executes GraphQL operation and returns execution result * (or promise when promise adapter is different from SyncPromiseAdapter). * - * @return ExecutionResult|array|Promise + * @return ExecutionResult|array|Promise> * * @api */ @@ -2136,7 +2145,7 @@ function validateOperationParams(GraphQL\Server\OperationParams $params): array * Executes GraphQL operation with given server configuration and returns execution result * (or promise when promise adapter is different from SyncPromiseAdapter). * - * @return ExecutionResult|Promise + * @return ExecutionResult|Promise * * @api */ @@ -2161,7 +2170,7 @@ function executeBatch(GraphQL\Server\ServerConfig $config, array $operations) /** * Send response using standard PHP `header()` and `echo`. * - * @param Promise|ExecutionResult|array $result + * @param Promise>|ExecutionResult|array $result * * @api */ @@ -2185,9 +2194,9 @@ function parsePsrRequest(Psr\Http\Message\RequestInterface $request) /** * Converts query execution result to PSR-7 response. * - * @param Promise|ExecutionResult|array $result + * @param Promise>|ExecutionResult|array $result * - * @return Promise|ResponseInterface + * @return Promise|ResponseInterface * * @api */ From 9c9323b06e15d19123d45eb3ea14740fd67e7429 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 30 Nov 2022 22:24:43 +0200 Subject: [PATCH 10/36] Add generics --- .../Promise/Adapter/SyncPromiseAdapter.php | 2 +- src/Executor/Promise/PromiseAdapter.php | 6 +++--- src/Executor/ReferenceExecutor.php | 19 ++++++++++--------- src/Server/Helper.php | 11 +++++++---- src/Server/StandardServer.php | 6 +++--- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index e0394675a..ed5da1cb3 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -103,7 +103,7 @@ public function createRejected(\Throwable $reason): Promise * * @param iterable|V> $promisesOrValues * - * @return Promise + * @return Promise */ public function all(iterable $promisesOrValues): Promise { diff --git a/src/Executor/Promise/PromiseAdapter.php b/src/Executor/Promise/PromiseAdapter.php index ff1826312..a3995b029 100644 --- a/src/Executor/Promise/PromiseAdapter.php +++ b/src/Executor/Promise/PromiseAdapter.php @@ -51,8 +51,8 @@ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable * Creates a Promise from the given resolver callable. * * @template V - * - * @param callable(callable $resolve, callable $reject): void $resolver + * @param callable(callable(V): void $resolve, callable(\Throwable): void $reject): void $resolver + * @return Promise * * @api */ @@ -88,7 +88,7 @@ public function createRejected(\Throwable $reason): Promise; * * @param iterable|V> $promisesOrValues * - * @return Promise + * @return Promise * * @api */ diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 2d5b5badb..1d1c3b24d 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -282,7 +282,7 @@ protected function buildResponse($data) * * @param mixed $rootValue * - * @return Promise|\stdClass|null>|array|\stdClass|null + * @return Promise>|Promise<\stdClass>|Promise|array|\stdClass|null */ protected function executeOperation(OperationDefinitionNode $operation, $rootValue) { @@ -523,7 +523,7 @@ protected function doesFragmentConditionMatch(Node $fragment, ObjectType $type): * * @phpstan-param Fields $fields * - * @return Promise|\stdClass>|array|\stdClass + * @return Promise>|Promise<\stdClass>|array|\stdClass */ protected function executeFieldsSerially(ObjectType $parentType, $rootValue, array $path, \ArrayObject $fields) { @@ -716,7 +716,7 @@ protected function resolveFieldValueOrError( * * @param mixed $result * - * @return Promise|\stdClass|null>|array|\stdClass|null + * @return Promise>|Promise<\stdClass>|Promise|array|\stdClass|null */ protected function completeValueCatchingError( Type $returnType, @@ -947,7 +947,7 @@ function ($previous, $value) use ($callback) { * * @throws \Exception * - * @return Promise|\stdClass>|array|\stdClass + * @return Promise>|Promise<\stdClass>|array|\stdClass */ protected function completeListValue( ListOfType $returnType, @@ -1014,7 +1014,7 @@ protected function completeLeafValue(LeafType $returnType, &$result) * * @throws Error * - * @return Promise|\stdClass>|array|\stdClass + * @return Promise>|Promise<\stdClass>|array|\stdClass */ protected function completeAbstractValue( AbstractType $returnType, @@ -1078,7 +1078,7 @@ protected function completeAbstractValue( * @param mixed|null $contextValue * @param AbstractType&Type $abstractType * - * @return Promise|Type|string|null + * @return Promise|Promise|Promise|Type|string|null */ protected function defaultTypeResolver($value, $contextValue, ResolveInfo $info, AbstractType $abstractType) { @@ -1137,7 +1137,7 @@ protected function defaultTypeResolver($value, $contextValue, ResolveInfo $info, * * @throws Error * - * @return Promise|\stdClass>|array|\stdClass + * @return Promise>|Promise<\stdClass>|array|\stdClass */ protected function completeObjectValue( ObjectType $returnType, @@ -1210,7 +1210,7 @@ protected function invalidReturnTypeError( * * @throws Error * - * @return Promise|\stdClass>|array|\stdClass + * @return Promise>|Promise<\stdClass>|array|\stdClass */ protected function collectAndExecuteSubfields( ObjectType $returnType, @@ -1265,7 +1265,7 @@ protected function collectSubFields(ObjectType $returnType, \ArrayObject $fieldN * * @phpstan-param Fields $fields * - * @return Promise<\stdClass|array>|\stdClass|array + * @return Promise>|Promise<\stdClass>|\stdClass|array */ protected function executeFields(ObjectType $parentType, $rootValue, array $path, \ArrayObject $fields) { @@ -1320,6 +1320,7 @@ protected static function fixResultsIfEmptyArray($results) * associative array where all Promises were resolved. * * @param array|mixed> $assoc + * @return Promise> */ protected function promiseForAssocArray(array $assoc): Promise { diff --git a/src/Server/Helper.php b/src/Server/Helper.php index 81958c67a..9e97a0443 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -197,7 +197,7 @@ public function executeOperation(ServerConfig $config, OperationParams $op) * * @param array $operations * - * @return array|Promise + * @return array|Promise> * * @api */ @@ -220,6 +220,9 @@ public function executeBatch(ServerConfig $config, array $operations) return $result; } + /** + * @return Promise + */ protected function promiseToExecuteOperation( PromiseAdapter $promiseAdapter, ServerConfig $config, @@ -392,7 +395,7 @@ protected function resolveContextValue( /** * Send response using standard PHP `header()` and `echo`. * - * @param Promise>|ExecutionResult|array $result + * @param Promise|Promise>|ExecutionResult|array $result * * @api */ @@ -516,9 +519,9 @@ protected function assertJsonObjectOrArray($bodyParams): void /** * Converts query execution result to PSR-7 response. * - * @param Promise>|ExecutionResult|array $result + * @param Promise|Promise>|ExecutionResult|array $result * - * @return Promise|ResponseInterface + * @return Promise|ResponseInterface * * @api */ diff --git a/src/Server/StandardServer.php b/src/Server/StandardServer.php index a9780497c..ea1f8c22e 100644 --- a/src/Server/StandardServer.php +++ b/src/Server/StandardServer.php @@ -90,7 +90,7 @@ public function handleRequest($parsedBody = null): void * * @param OperationParams|array $parsedBody * - * @return ExecutionResult|array|Promise> + * @return ExecutionResult|array|Promise|Promise> * * @api */ @@ -113,7 +113,7 @@ public function executeRequest($parsedBody = null) * See `executePsrRequest()` if you prefer to create response yourself * (e.g. using specific JsonResponse instance of some framework). * - * @return ResponseInterface|Promise + * @return ResponseInterface|Promise * * @api */ @@ -131,7 +131,7 @@ public function processPsrRequest( * Executes GraphQL operation and returns execution result * (or promise when promise adapter is different from SyncPromiseAdapter). * - * @return ExecutionResult|array|Promise> + * @return ExecutionResult|array|Promise|Promise> * * @api */ From 9e26ed2833253a6b13f004eaf822a81b44502ddf Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 30 Nov 2022 20:25:32 +0000 Subject: [PATCH 11/36] Prettify docs --- docs/class-reference.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/class-reference.md b/docs/class-reference.md index 04a452253..be7ff2fda 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -1502,8 +1502,8 @@ function then( * Creates a Promise from the given resolver callable. * * @template V - * - * @param callable(callable $resolve, callable $reject): void $resolver + * @param callable(callable(V): void $resolve, callable(\Throwable): void $reject): void $resolver + * @return Promise * * @api */ @@ -1545,7 +1545,7 @@ function createRejected(Throwable $reason): GraphQL\Executor\Promise\Promise * * @param iterable|V> $promisesOrValues * - * @return Promise + * @return Promise * * @api */ @@ -1903,7 +1903,7 @@ function handleRequest($parsedBody = null): void * * @param OperationParams|array $parsedBody * - * @return ExecutionResult|array|Promise> + * @return ExecutionResult|array|Promise|Promise> * * @api */ @@ -1917,7 +1917,7 @@ function executeRequest($parsedBody = null) * See `executePsrRequest()` if you prefer to create response yourself * (e.g. using specific JsonResponse instance of some framework). * - * @return ResponseInterface|Promise + * @return ResponseInterface|Promise * * @api */ @@ -1933,7 +1933,7 @@ function processPsrRequest( * Executes GraphQL operation and returns execution result * (or promise when promise adapter is different from SyncPromiseAdapter). * - * @return ExecutionResult|array|Promise> + * @return ExecutionResult|array|Promise|Promise> * * @api */ @@ -2159,7 +2159,7 @@ function executeOperation(GraphQL\Server\ServerConfig $config, GraphQL\Server\Op * * @param array $operations * - * @return array|Promise + * @return array|Promise> * * @api */ @@ -2170,7 +2170,7 @@ function executeBatch(GraphQL\Server\ServerConfig $config, array $operations) /** * Send response using standard PHP `header()` and `echo`. * - * @param Promise>|ExecutionResult|array $result + * @param Promise|Promise>|ExecutionResult|array $result * * @api */ @@ -2194,9 +2194,9 @@ function parsePsrRequest(Psr\Http\Message\RequestInterface $request) /** * Converts query execution result to PSR-7 response. * - * @param Promise>|ExecutionResult|array $result + * @param Promise|Promise>|ExecutionResult|array $result * - * @return Promise|ResponseInterface + * @return Promise|ResponseInterface * * @api */ From ca0812a48fee070d0a4f3d59e4050048d5283576 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Fri, 2 Dec 2022 09:52:43 +0200 Subject: [PATCH 12/36] Add generics --- src/Executor/Promise/Adapter/SyncPromiseAdapter.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index ed5da1cb3..36c48fa1a 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -32,12 +32,12 @@ public function convertThenable($thenable): Promise } /** - * @template V + * @template T * @template TFulfilled of mixed * @template TRejected of mixed * - * @param Promise $promise - * @param (callable(V): (Promise|TFulfilled))|null $onFulfilled + * @param Promise $promise + * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled * @param (callable(mixed): (Promise|TRejected))|null $onRejected * * @return Promise<( From 78a442f2353f03d38b647b382974cd5277e5204c Mon Sep 17 00:00:00 2001 From: Warxcell Date: Fri, 2 Dec 2022 07:53:54 +0000 Subject: [PATCH 13/36] Apply php-cs-fixer changes --- src/Executor/Promise/PromiseAdapter.php | 2 ++ src/Executor/ReferenceExecutor.php | 6 +++++- src/GraphQL.php | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Executor/Promise/PromiseAdapter.php b/src/Executor/Promise/PromiseAdapter.php index a3995b029..78096be55 100644 --- a/src/Executor/Promise/PromiseAdapter.php +++ b/src/Executor/Promise/PromiseAdapter.php @@ -51,7 +51,9 @@ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable * Creates a Promise from the given resolver callable. * * @template V + * * @param callable(callable(V): void $resolve, callable(\Throwable): void $reject): void $resolver + * * @return Promise * * @api diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 1d1c3b24d..8906b3c13 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -108,7 +108,7 @@ public static function create( if (\is_array($exeContext)) { return new class($promiseAdapter->createFulfilled(new ExecutionResult(null, $exeContext))) implements ExecutorImplementation { /** - * @var Promise $result + * @var Promise */ private Promise $result; @@ -314,6 +314,7 @@ protected function executeOperation(OperationDefinitionNode $operation, $rootVal /** * @param mixed $error + * * @return Promise */ public function onError($error): ?Promise @@ -892,7 +893,9 @@ protected function isPromise($value): bool * otherwise returns null. * * @template T + * * @param T $value + * * @return Promise|null */ protected function getPromise($value): ?Promise @@ -1320,6 +1323,7 @@ protected static function fixResultsIfEmptyArray($results) * associative array where all Promises were resolved. * * @param array|mixed> $assoc + * * @return Promise> */ protected function promiseForAssocArray(array $assoc): Promise diff --git a/src/GraphQL.php b/src/GraphQL.php index 929872384..a2e42e038 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -108,6 +108,7 @@ public static function executeQuery( * @param mixed $context * @param array|null $variableValues * @param array|null $validationRules + * * @return Promise * * @api From f0f32c57c1cc5a1e0065053fd3b4567f4544eb80 Mon Sep 17 00:00:00 2001 From: Yup Date: Wed, 14 Dec 2022 16:30:29 +0200 Subject: [PATCH 14/36] Update src/Executor/Promise/PromiseAdapter.php Co-authored-by: Benedikt Franke --- src/Executor/Promise/PromiseAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Executor/Promise/PromiseAdapter.php b/src/Executor/Promise/PromiseAdapter.php index 78096be55..a4272e916 100644 --- a/src/Executor/Promise/PromiseAdapter.php +++ b/src/Executor/Promise/PromiseAdapter.php @@ -90,7 +90,7 @@ public function createRejected(\Throwable $reason): Promise; * * @param iterable|V> $promisesOrValues * - * @return Promise + * @return Promise> * * @api */ From 7aa10413be5da0c3fdd712a44a75b4dcbbf15a5c Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 14 Dec 2022 14:31:10 +0000 Subject: [PATCH 15/36] Prettify docs --- docs/class-reference.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/class-reference.md b/docs/class-reference.md index be7ff2fda..0fc65ca1e 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -75,6 +75,7 @@ static function executeQuery( * @param mixed $context * @param array|null $variableValues * @param array|null $validationRules + * * @return Promise * * @api @@ -1502,7 +1503,9 @@ function then( * Creates a Promise from the given resolver callable. * * @template V + * * @param callable(callable(V): void $resolve, callable(\Throwable): void $reject): void $resolver + * * @return Promise * * @api @@ -1545,7 +1548,7 @@ function createRejected(Throwable $reason): GraphQL\Executor\Promise\Promise * * @param iterable|V> $promisesOrValues * - * @return Promise + * @return Promise> * * @api */ From 923bfc81e88484e31b0b8b7cf3a9351f48740e25 Mon Sep 17 00:00:00 2001 From: Yup Date: Wed, 14 Dec 2022 16:44:47 +0200 Subject: [PATCH 16/36] Update src/Executor/Promise/Adapter/SyncPromiseAdapter.php Co-authored-by: Benedikt Franke --- src/Executor/Promise/Adapter/SyncPromiseAdapter.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index 36c48fa1a..d9d45f9b3 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -187,9 +187,7 @@ public function wait(Promise $promise) /** * Execute just before starting to run promise completion. * - * @template V - * - * @param Promise $promise + * @param Promise $promise */ protected function beforeWait(Promise $promise): void { From 85e0377ffe5164b719909031ecdecb498679952b Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 14 Dec 2022 17:53:35 +0200 Subject: [PATCH 17/36] Add generics --- .../Promise/Adapter/AmpPromiseAdapter.php | 7 +---- .../Promise/Adapter/ReactPromiseAdapter.php | 7 +---- .../Promise/Adapter/SyncPromiseAdapter.php | 21 +-------------- src/Executor/Promise/PromiseAdapter.php | 18 ++++++++----- src/Executor/ReferenceExecutor.php | 27 +++++++------------ .../Promise/SyncPromiseAdapterTest.php | 2 ++ tests/Executor/SyncTest.php | 1 - 7 files changed, 26 insertions(+), 57 deletions(-) diff --git a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php index 86718ead4..fa6805d4e 100644 --- a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php @@ -77,13 +77,8 @@ public function createRejected(\Throwable $reason): Promise return new Promise($promise, $this); } - public function all(iterable $promisesOrValues): Promise + public function all(array $promisesOrValues): Promise { - \assert( - \is_array($promisesOrValues), - 'AmpPromiseAdapter::all(): Argument #1 ($promisesOrValues) must be of type array' - ); - /** @var array> $promises */ $promises = []; foreach ($promisesOrValues as $key => $item) { diff --git a/src/Executor/Promise/Adapter/ReactPromiseAdapter.php b/src/Executor/Promise/Adapter/ReactPromiseAdapter.php index b16ac25d4..fac76a259 100644 --- a/src/Executor/Promise/Adapter/ReactPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/ReactPromiseAdapter.php @@ -54,13 +54,8 @@ public function createRejected(\Throwable $reason): Promise return new Promise($promise, $this); } - public function all(iterable $promisesOrValues): Promise + public function all(array $promisesOrValues): Promise { - assert( - is_array($promisesOrValues), - 'ReactPromiseAdapter::all(): Argument #1 ($promisesOrValues) must be of type array' - ); - foreach ($promisesOrValues as &$promiseOrValue) { if ($promiseOrValue instanceof Promise) { $promiseOrValue = $promiseOrValue->adoptedPromise; diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index d9d45f9b3..38a7341d7 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -84,13 +84,6 @@ public function createFulfilled($value = null): Promise return new Promise($promise->resolve($value), $this); } - /** - * @template V of \Throwable - * - * @param V $reason - * - * @return Promise - */ public function createRejected(\Throwable $reason): Promise { $promise = new SyncPromise(); @@ -98,20 +91,8 @@ public function createRejected(\Throwable $reason): Promise return new Promise($promise->reject($reason), $this); } - /** - * @template V - * - * @param iterable|V> $promisesOrValues - * - * @return Promise - */ - public function all(iterable $promisesOrValues): Promise + public function all(array $promisesOrValues): Promise { - \assert( - \is_array($promisesOrValues), - 'SyncPromiseAdapter::all(): Argument #1 ($promisesOrValues) must be of type array' - ); - $all = new SyncPromise(); $total = \count($promisesOrValues); diff --git a/src/Executor/Promise/PromiseAdapter.php b/src/Executor/Promise/PromiseAdapter.php index a4272e916..25edb8a07 100644 --- a/src/Executor/Promise/PromiseAdapter.php +++ b/src/Executor/Promise/PromiseAdapter.php @@ -2,6 +2,8 @@ namespace GraphQL\Executor\Promise; +use Throwable; + /** * Provides a means for integration of async PHP platforms ([related docs](data-fetching.md#async-php)). */ @@ -19,7 +21,9 @@ public function isThenable($value): bool; /** * Converts thenable of the underlying platform into GraphQL\Executor\Promise\Promise instance. * - * @param mixed $thenable + * @template T + * @param T $thenable + * @return Promise * * @api */ @@ -35,7 +39,7 @@ public function convertThenable($thenable): Promise; * * @param Promise $promise * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled - * @param (callable(mixed): (Promise|TRejected))|null $onRejected + * @param (callable(Throwable): (Promise|TRejected))|null $onRejected * * @return Promise<( * $onFulfilled is not null @@ -52,7 +56,7 @@ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable * * @template V * - * @param callable(callable(V): void $resolve, callable(\Throwable): void $reject): void $resolver + * @param callable(callable(V): void $resolve, callable(Throwable): void $reject): void $resolver * * @return Promise * @@ -78,9 +82,11 @@ public function createFulfilled($value = null): Promise; * * If the provided reason is a promise, then it is returned as-is. * + * @return Promise + * * @api */ - public function createRejected(\Throwable $reason): Promise; + public function createRejected(Throwable $reason): Promise; /** * Given an iterable of promises (or values), returns a promise that is fulfilled when all the @@ -88,11 +94,11 @@ public function createRejected(\Throwable $reason): Promise; * * @template V * - * @param iterable|V> $promisesOrValues + * @param array $promisesOrValues * * @return Promise> * * @api */ - public function all(iterable $promisesOrValues): Promise; + public function all(array $promisesOrValues): Promise; } diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 8906b3c13..573fa3610 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -240,36 +240,27 @@ public function doExecute(): Promise // be executed. An execution which encounters errors will still result in a // resolved Promise. $data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue); - $result = $this->buildResponse($data); + + if ($data instanceof Promise) { + $data = $data->then(fn ($resolved) => $this->buildResponse($resolved)); + } // Note: we deviate here from the reference implementation a bit by always returning promise // But for the "sync" case it is always fulfilled - $promise = $this->getPromise($result); + $promise = $this->getPromise($data); if ($promise !== null) { - return $promise; + return $promise->then(fn ($resolved) => $this->buildResponse($resolved)); } - return $this->exeContext->promiseAdapter->createFulfilled($result); + return $this->exeContext->promiseAdapter->createFulfilled($data); } /** * @param mixed $data - * - * @return ExecutionResult|Promise */ - protected function buildResponse($data) + protected function buildResponse($data): ExecutionResult { - if ($data instanceof Promise) { - return $data->then(fn ($resolved) => $this->buildResponse($resolved)); - } - - $promiseAdapter = $this->exeContext->promiseAdapter; - if ($promiseAdapter->isThenable($data)) { - return $promiseAdapter->convertThenable($data) - ->then(fn ($resolved) => $this->buildResponse($resolved)); - } - if ($data !== null) { $data = (array) $data; } @@ -1081,7 +1072,7 @@ protected function completeAbstractValue( * @param mixed|null $contextValue * @param AbstractType&Type $abstractType * - * @return Promise|Promise|Promise|Type|string|null + * @return Promise|Type|string|null */ protected function defaultTypeResolver($value, $contextValue, ResolveInfo $info, AbstractType $abstractType) { diff --git a/tests/Executor/Promise/SyncPromiseAdapterTest.php b/tests/Executor/Promise/SyncPromiseAdapterTest.php index b61860f6d..3060ac4ab 100644 --- a/tests/Executor/Promise/SyncPromiseAdapterTest.php +++ b/tests/Executor/Promise/SyncPromiseAdapterTest.php @@ -74,6 +74,8 @@ public function testCreatePromise(): void } /** + * @template T + * @param Promise $promise * @param mixed $expectedNextValue */ private static function assertValidPromise(Promise $promise, ?string $expectedNextReason, $expectedNextValue, string $expectedNextState): void diff --git a/tests/Executor/SyncTest.php b/tests/Executor/SyncTest.php index 00bc73601..0ce8f6211 100644 --- a/tests/Executor/SyncTest.php +++ b/tests/Executor/SyncTest.php @@ -159,7 +159,6 @@ private function assertAsync(array $expectedFinalArray, Promise $actualResult): self::assertEquals(SyncPromise::PENDING, $adoptedPromise->state, $message); $resolvedResult = $this->promiseAdapter->wait($actualResult); - self::assertInstanceOf(ExecutionResult::class, $resolvedResult); self::assertArraySubset($expectedFinalArray, $resolvedResult->toArray()); } From dddbb7fa1d73a29769f14dccf3463188dcab42f9 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 14 Dec 2022 15:54:20 +0000 Subject: [PATCH 18/36] Prettify docs --- docs/class-reference.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/class-reference.md b/docs/class-reference.md index 0fc65ca1e..b2124a231 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -1463,7 +1463,9 @@ function isThenable($value): bool /** * Converts thenable of the underlying platform into GraphQL\Executor\Promise\Promise instance. * - * @param mixed $thenable + * @template T + * @param T $thenable + * @return Promise * * @api */ @@ -1481,7 +1483,7 @@ function convertThenable($thenable): GraphQL\Executor\Promise\Promise * * @param Promise $promise * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled - * @param (callable(mixed): (Promise|TRejected))|null $onRejected + * @param (callable(Throwable): (Promise|TRejected))|null $onRejected * * @return Promise<( * $onFulfilled is not null @@ -1504,7 +1506,7 @@ function then( * * @template V * - * @param callable(callable(V): void $resolve, callable(\Throwable): void $reject): void $resolver + * @param callable(callable(V): void $resolve, callable(Throwable): void $reject): void $resolver * * @return Promise * @@ -1534,6 +1536,8 @@ function createFulfilled($value = null): GraphQL\Executor\Promise\Promise * * If the provided reason is a promise, then it is returned as-is. * + * @return Promise + * * @api */ function createRejected(Throwable $reason): GraphQL\Executor\Promise\Promise @@ -1546,13 +1550,13 @@ function createRejected(Throwable $reason): GraphQL\Executor\Promise\Promise * * @template V * - * @param iterable|V> $promisesOrValues + * @param array $promisesOrValues * * @return Promise> * * @api */ -function all(iterable $promisesOrValues): GraphQL\Executor\Promise\Promise +function all(array $promisesOrValues): GraphQL\Executor\Promise\Promise ``` ## GraphQL\Validator\DocumentValidator From af104631d35162efb89fc2d283a47657e27d7428 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 14 Dec 2022 17:59:08 +0200 Subject: [PATCH 19/36] Add generics --- .../Promise/Adapter/SyncPromiseAdapter.php | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index 38a7341d7..ab6143a0f 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -31,21 +31,6 @@ public function convertThenable($thenable): Promise return new Promise($thenable, $this); } - /** - * @template T - * @template TFulfilled of mixed - * @template TRejected of mixed - * - * @param Promise $promise - * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled - * @param (callable(mixed): (Promise|TRejected))|null $onRejected - * - * @return Promise<( - * $onFulfilled is not null - * ? ($onRejected is not null ? TFulfilled|TRejected : TFulfilled) - * : ($onRejected is not null ? TRejected : T) - * )> - */ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise { $adoptedPromise = $promise->adoptedPromise; @@ -70,13 +55,6 @@ public function create(callable $resolver): Promise return new Promise($promise, $this); } - /** - * @template V - * - * @param V $value - * - * @return Promise - */ public function createFulfilled($value = null): Promise { $promise = new SyncPromise(); From 1b31ae6f412473b7463e8192e6e75d30277821d0 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 14 Dec 2022 16:00:22 +0000 Subject: [PATCH 20/36] Apply php-cs-fixer changes --- src/Executor/Promise/PromiseAdapter.php | 4 +++- tests/Executor/Promise/SyncPromiseAdapterTest.php | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Executor/Promise/PromiseAdapter.php b/src/Executor/Promise/PromiseAdapter.php index 25edb8a07..92579ccf4 100644 --- a/src/Executor/Promise/PromiseAdapter.php +++ b/src/Executor/Promise/PromiseAdapter.php @@ -22,7 +22,9 @@ public function isThenable($value): bool; * Converts thenable of the underlying platform into GraphQL\Executor\Promise\Promise instance. * * @template T + * * @param T $thenable + * * @return Promise * * @api @@ -86,7 +88,7 @@ public function createFulfilled($value = null): Promise; * * @api */ - public function createRejected(Throwable $reason): Promise; + public function createRejected(\Throwable $reason): Promise; /** * Given an iterable of promises (or values), returns a promise that is fulfilled when all the diff --git a/tests/Executor/Promise/SyncPromiseAdapterTest.php b/tests/Executor/Promise/SyncPromiseAdapterTest.php index 3060ac4ab..12bf7fb64 100644 --- a/tests/Executor/Promise/SyncPromiseAdapterTest.php +++ b/tests/Executor/Promise/SyncPromiseAdapterTest.php @@ -75,6 +75,7 @@ public function testCreatePromise(): void /** * @template T + * * @param Promise $promise * @param mixed $expectedNextValue */ From b5602818656da08de724f2e8703c206d8c47acf8 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 14 Dec 2022 18:43:21 +0200 Subject: [PATCH 21/36] Add generics --- src/Deferred.php | 15 +-- src/Executor/Promise/Adapter/SyncPromise.php | 110 +++++++++++-------- src/Executor/Promise/Promise.php | 2 +- src/Executor/ReferenceExecutor.php | 6 +- src/Type/Definition/AbstractType.php | 4 +- src/Type/Definition/ObjectType.php | 4 +- tests/Executor/Promise/SyncPromiseTest.php | 2 + tests/Executor/TestClasses/Root.php | 6 + 8 files changed, 83 insertions(+), 66 deletions(-) diff --git a/src/Deferred.php b/src/Deferred.php index d0f99ec13..a57fdf35c 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -5,23 +5,18 @@ use GraphQL\Executor\Promise\Adapter\SyncPromise; /** - * @phpstan-import-type Executor from SyncPromise + * @template V + * @extends SyncPromise */ class Deferred extends SyncPromise { /** - * @param Executor $executor + * @template T + * @param callable(): T $executor + * @return self */ public static function create(callable $executor): self { return new self($executor); } - - /** - * @param Executor $executor - */ - public function __construct(callable $executor) - { - parent::__construct($executor); - } } diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index c0b08ea80..46c48e587 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -17,7 +17,8 @@ * 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. * - * @phpstan-type Executor callable(): mixed + * @template V + * @phpstan-type Executor callable(): V */ class SyncPromise { @@ -36,7 +37,7 @@ class SyncPromise * @var array< * int, * array{ - * self, + * self, * (callable(mixed): mixed)|null, * (callable(Throwable): mixed)|null * } @@ -44,15 +45,6 @@ class SyncPromise */ private array $waiting = []; - public static function runQueue(): void - { - $q = self::getQueue(); - while (! $q->isEmpty()) { - $task = $q->dequeue(); - $task(); - } - } - /** * @param Executor|null $executor */ @@ -73,6 +65,7 @@ public function __construct(?callable $executor = null) /** * @param mixed $value + * @return self */ public function resolve($value): self { @@ -112,25 +105,35 @@ function ($reason): void { return $this; } - public function reject(\Throwable $reason): self + /** + * @template VFulfilled + * @template VRejected + * @param (callable(V): VFulfilled)|null $onFulfilled + * @param (callable(Throwable): VRejected)|null $onRejected + * @return self<( + * $onFulfilled is not null + * ? ($onRejected is not null ? VFulfilled|VRejected : VFulfilled) + * : ($onRejected is not null ? VRejected : V) + * )> + */ + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): self { - switch ($this->state) { - case self::PENDING: - $this->state = self::REJECTED; - $this->result = $reason; - $this->enqueueWaitingPromises(); - break; - case self::REJECTED: - if ($reason !== $this->result) { - throw new \Exception('Cannot change rejection reason'); - } + if ($this->state === self::REJECTED && $onRejected === null) { + return $this; + } - break; - case self::FULFILLED: - throw new \Exception('Cannot reject fulfilled promise'); + if ($this->state === self::FULFILLED && $onFulfilled === null) { + return $this; } - return $this; + $tmp = new self(); + $this->waiting[] = [$tmp, $onFulfilled, $onRejected]; + + if ($this->state !== self::PENDING) { + $this->enqueueWaitingPromises(); + } + + return $tmp; } private function enqueueWaitingPromises(): void @@ -167,41 +170,52 @@ private function enqueueWaitingPromises(): void } /** - * @return \SplQueue + * @return self + * @throws \Exception */ - public static function getQueue(): \SplQueue + public function reject(\Throwable $reason): self { - static $queue; + switch ($this->state) { + case self::PENDING: + $this->state = self::REJECTED; + $this->result = $reason; + $this->enqueueWaitingPromises(); + break; + case self::REJECTED: + if ($reason !== $this->result) { + throw new \Exception('Cannot change rejection reason'); + } - return $queue ??= new \SplQueue(); + break; + case self::FULFILLED: + throw new \Exception('Cannot reject fulfilled promise'); + } + + return $this; } - /** - * @param (callable(mixed): mixed)|null $onFulfilled - * @param (callable(Throwable): mixed)|null $onRejected - */ - public function then(?callable $onFulfilled = null, ?callable $onRejected = null): self + public static function runQueue(): void { - if ($this->state === self::REJECTED && $onRejected === null) { - return $this; - } - - if ($this->state === self::FULFILLED && $onFulfilled === null) { - return $this; + $q = self::getQueue(); + while (!$q->isEmpty()) { + $task = $q->dequeue(); + $task(); } + } - $tmp = new self(); - $this->waiting[] = [$tmp, $onFulfilled, $onRejected]; - - if ($this->state !== self::PENDING) { - $this->enqueueWaitingPromises(); - } + /** + * @return \SplQueue + */ + public static function getQueue(): \SplQueue + { + static $queue; - return $tmp; + return $queue ??= new \SplQueue(); } /** * @param callable(\Throwable): mixed $onRejected + * @return self */ public function catch(callable $onRejected): self { diff --git a/src/Executor/Promise/Promise.php b/src/Executor/Promise/Promise.php index fa1701fa8..2f4f52172 100644 --- a/src/Executor/Promise/Promise.php +++ b/src/Executor/Promise/Promise.php @@ -14,7 +14,7 @@ */ class Promise { - /** @var SyncPromise|ReactPromise|AmpPromise */ + /** @var SyncPromise|ReactPromise|AmpPromise */ public $adoptedPromise; private PromiseAdapter $adapter; diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 573fa3610..c38a1c623 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -1131,7 +1131,7 @@ protected function defaultTypeResolver($value, $contextValue, ResolveInfo $info, * * @throws Error * - * @return Promise>|Promise<\stdClass>|array|\stdClass + * @return Promise>|array|\stdClass */ protected function completeObjectValue( ObjectType $returnType, @@ -1204,7 +1204,7 @@ protected function invalidReturnTypeError( * * @throws Error * - * @return Promise>|Promise<\stdClass>|array|\stdClass + * @return Promise>|array|\stdClass */ protected function collectAndExecuteSubfields( ObjectType $returnType, @@ -1259,7 +1259,7 @@ protected function collectSubFields(ObjectType $returnType, \ArrayObject $fieldN * * @phpstan-param Fields $fields * - * @return Promise>|Promise<\stdClass>|\stdClass|array + * @return Promise>|\stdClass|array */ protected function executeFields(ObjectType $parentType, $rootValue, array $path, \ArrayObject $fields) { diff --git a/src/Type/Definition/AbstractType.php b/src/Type/Definition/AbstractType.php index c646b8805..17f9c132f 100644 --- a/src/Type/Definition/AbstractType.php +++ b/src/Type/Definition/AbstractType.php @@ -5,7 +5,7 @@ use GraphQL\Deferred; /** - * @phpstan-type ResolveTypeReturn ObjectType|string|callable(): (ObjectType|string|null)|Deferred|null + * @phpstan-type ResolveTypeReturn ObjectType|string|callable(): ObjectType|string|null|Deferred * @phpstan-type ResolveType callable(mixed $objectValue, mixed $context, ResolveInfo $resolveInfo): ResolveTypeReturn */ interface AbstractType @@ -16,7 +16,7 @@ interface AbstractType * @param mixed $objectValue The resolved value for the object type * @param mixed $context The context that was passed to GraphQL::execute() * - * @return ObjectType|string|callable|Deferred|null + * @return ObjectType|string|callable|null|Deferred * * @phpstan-return ResolveTypeReturn */ diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index bcce4204b..eb0b748c1 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -57,7 +57,7 @@ * resolveField?: FieldResolver|null, * fields: (callable(): iterable)|iterable, * interfaces?: iterable|callable(): iterable, - * isTypeOf?: (callable(mixed $objectValue, mixed $context, ResolveInfo $resolveInfo): (bool|Deferred|null))|null, + * isTypeOf?: (callable(mixed $objectValue, mixed $context, ResolveInfo $resolveInfo): (bool|null|Deferred))|null, * astNode?: ObjectTypeDefinitionNode|null, * extensionASTNodes?: array|null * } @@ -116,7 +116,7 @@ public static function assertObjectType($type): self * @param mixed $objectValue The resolved value for the object type * @param mixed $context The context that was passed to GraphQL::execute() * - * @return bool|Deferred|null + * @return bool|Deferred|null */ public function isTypeOf($objectValue, $context, ResolveInfo $info) { diff --git a/tests/Executor/Promise/SyncPromiseTest.php b/tests/Executor/Promise/SyncPromiseTest.php index 82804fd80..6a4d13963 100644 --- a/tests/Executor/Promise/SyncPromiseTest.php +++ b/tests/Executor/Promise/SyncPromiseTest.php @@ -138,6 +138,8 @@ static function () use (&$onRejectedCalled): void { } /** + * @template V + * @param SyncPromise $promise * @param mixed $expectedNextValue */ private static function assertValidPromise( diff --git a/tests/Executor/TestClasses/Root.php b/tests/Executor/TestClasses/Root.php index 9d192b127..e7ceed200 100644 --- a/tests/Executor/TestClasses/Root.php +++ b/tests/Executor/TestClasses/Root.php @@ -13,6 +13,9 @@ public function __construct(float $originalNumber) $this->numberHolder = new NumberHolder($originalNumber); } + /** + * @return Deferred + */ public function promiseToChangeTheNumber(float $newNumber): Deferred { return new Deferred(fn (): NumberHolder => $this->immediatelyChangeTheNumber($newNumber)); @@ -30,6 +33,9 @@ public function failToChangeTheNumber(): void throw new \Exception('Cannot change the number'); } + /** + * @return Deferred + */ public function promiseAndFailToChangeTheNumber(): Deferred { return new Deferred(function (): void { From 4e03770a731badae947019abf4162a225d8a8e53 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 14 Dec 2022 16:44:27 +0000 Subject: [PATCH 22/36] Prettify docs --- docs/class-reference.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/class-reference.md b/docs/class-reference.md index b2124a231..60aec2b1f 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -1464,7 +1464,9 @@ function isThenable($value): bool * Converts thenable of the underlying platform into GraphQL\Executor\Promise\Promise instance. * * @template T + * * @param T $thenable + * * @return Promise * * @api From 9e2f95445ff2a149329d7fdd06d9d939942d81c0 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 14 Dec 2022 19:00:32 +0200 Subject: [PATCH 23/36] Add generics --- src/Executor/ReferenceExecutor.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index c38a1c623..6edaa8ac2 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -273,7 +273,7 @@ protected function buildResponse($data): ExecutionResult * * @param mixed $rootValue * - * @return Promise>|Promise<\stdClass>|Promise|array|\stdClass|null + * @return Promise>|Promise|array|\stdClass|null */ protected function executeOperation(OperationDefinitionNode $operation, $rootValue) { @@ -708,7 +708,7 @@ protected function resolveFieldValueOrError( * * @param mixed $result * - * @return Promise>|Promise<\stdClass>|Promise|array|\stdClass|null + * @return Promise>|Promise|array|\stdClass|null */ protected function completeValueCatchingError( Type $returnType, @@ -887,7 +887,7 @@ protected function isPromise($value): bool * * @param T $value * - * @return Promise|null + * @return (T === null || T instanceof Promise ? T : (Promise|null)) */ protected function getPromise($value): ?Promise { @@ -941,7 +941,7 @@ function ($previous, $value) use ($callback) { * * @throws \Exception * - * @return Promise>|Promise<\stdClass>|array|\stdClass + * @return Promise>|array|\stdClass */ protected function completeListValue( ListOfType $returnType, @@ -1008,7 +1008,7 @@ protected function completeLeafValue(LeafType $returnType, &$result) * * @throws Error * - * @return Promise>|Promise<\stdClass>|array|\stdClass + * @return Promise>|array|\stdClass */ protected function completeAbstractValue( AbstractType $returnType, From d8f58309e24552eb8f5d01efdb9143fdbecb4d10 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 14 Dec 2022 17:01:33 +0000 Subject: [PATCH 24/36] Apply php-cs-fixer changes --- src/Deferred.php | 3 +++ src/Executor/Promise/Adapter/SyncPromise.php | 10 ++++++++-- src/Type/Definition/AbstractType.php | 2 +- tests/Executor/Promise/SyncPromiseTest.php | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Deferred.php b/src/Deferred.php index a57fdf35c..73aa47fac 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -6,13 +6,16 @@ /** * @template V + * * @extends SyncPromise */ class Deferred extends SyncPromise { /** * @template T + * * @param callable(): T $executor + * * @return self */ public static function create(callable $executor): self diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index 46c48e587..a02d77bd5 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -18,6 +18,7 @@ * at least one $executor to start the promise chain. * * @template V + * * @phpstan-type Executor callable(): V */ class SyncPromise @@ -65,6 +66,7 @@ public function __construct(?callable $executor = null) /** * @param mixed $value + * * @return self */ public function resolve($value): self @@ -108,8 +110,10 @@ function ($reason): void { /** * @template VFulfilled * @template VRejected + * * @param (callable(V): VFulfilled)|null $onFulfilled * @param (callable(Throwable): VRejected)|null $onRejected + * * @return self<( * $onFulfilled is not null * ? ($onRejected is not null ? VFulfilled|VRejected : VFulfilled) @@ -170,8 +174,9 @@ private function enqueueWaitingPromises(): void } /** - * @return self * @throws \Exception + * + * @return self */ public function reject(\Throwable $reason): self { @@ -197,7 +202,7 @@ public function reject(\Throwable $reason): self public static function runQueue(): void { $q = self::getQueue(); - while (!$q->isEmpty()) { + while (! $q->isEmpty()) { $task = $q->dequeue(); $task(); } @@ -215,6 +220,7 @@ public static function getQueue(): \SplQueue /** * @param callable(\Throwable): mixed $onRejected + * * @return self */ public function catch(callable $onRejected): self diff --git a/src/Type/Definition/AbstractType.php b/src/Type/Definition/AbstractType.php index 17f9c132f..754b501ed 100644 --- a/src/Type/Definition/AbstractType.php +++ b/src/Type/Definition/AbstractType.php @@ -16,7 +16,7 @@ interface AbstractType * @param mixed $objectValue The resolved value for the object type * @param mixed $context The context that was passed to GraphQL::execute() * - * @return ObjectType|string|callable|null|Deferred + * @return ObjectType|string|callable|Deferred|null * * @phpstan-return ResolveTypeReturn */ diff --git a/tests/Executor/Promise/SyncPromiseTest.php b/tests/Executor/Promise/SyncPromiseTest.php index 6a4d13963..bf01221e0 100644 --- a/tests/Executor/Promise/SyncPromiseTest.php +++ b/tests/Executor/Promise/SyncPromiseTest.php @@ -139,6 +139,7 @@ static function () use (&$onRejectedCalled): void { /** * @template V + * * @param SyncPromise $promise * @param mixed $expectedNextValue */ From 61561a38cb341d0658cb3de2c7cddcfa125371c7 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 19 Dec 2022 11:47:47 +0100 Subject: [PATCH 25/36] restore iterable interface --- .../Promise/Adapter/AmpPromiseAdapter.php | 15 +++++++----- .../Promise/Adapter/ReactPromiseAdapter.php | 9 ++++--- .../Promise/Adapter/SyncPromiseAdapter.php | 24 ++++++++++--------- src/Executor/Promise/PromiseAdapter.php | 4 ++-- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php index fa6805d4e..dda1380ff 100644 --- a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php @@ -77,7 +77,7 @@ public function createRejected(\Throwable $reason): Promise return new Promise($promise, $this); } - public function all(array $promisesOrValues): Promise + public function all(iterable $promisesOrValues): Promise { /** @var array> $promises */ $promises = []; @@ -93,18 +93,21 @@ public function all(array $promisesOrValues): Promise $deferred = new Deferred(); - $onResolve = static function (?\Throwable $reason, ?array $values) use ($promisesOrValues, $deferred): void { + all($promises)->onResolve(static function (?\Throwable $reason, ?array $values) use ($promisesOrValues, $deferred): void { if ($reason === null) { \assert(\is_array($values), 'Either $reason or $values must be passed'); - $deferred->resolve(\array_replace($promisesOrValues, $values)); + + $promisesOrValuesArray = is_array($promisesOrValues) + ? $promisesOrValues + : iterator_to_array($promisesOrValues); + $resolvedValues = \array_replace($promisesOrValuesArray, $values); + $deferred->resolve($resolvedValues); return; } $deferred->fail($reason); - }; - - all($promises)->onResolve($onResolve); + }); return new Promise($deferred->promise(), $this); } diff --git a/src/Executor/Promise/Adapter/ReactPromiseAdapter.php b/src/Executor/Promise/Adapter/ReactPromiseAdapter.php index fac76a259..2604db399 100644 --- a/src/Executor/Promise/Adapter/ReactPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/ReactPromiseAdapter.php @@ -54,7 +54,7 @@ public function createRejected(\Throwable $reason): Promise return new Promise($promise, $this); } - public function all(array $promisesOrValues): Promise + public function all(iterable $promisesOrValues): Promise { foreach ($promisesOrValues as &$promiseOrValue) { if ($promiseOrValue instanceof Promise) { @@ -62,9 +62,12 @@ public function all(array $promisesOrValues): Promise } } - $promise = all($promisesOrValues)->then(static fn ($values): array => array_map( + $promisesOrValuesArray = is_array($promisesOrValues) + ? $promisesOrValues + : iterator_to_array($promisesOrValues); + $promise = all($promisesOrValuesArray)->then(static fn ($values): array => array_map( static fn ($key) => $values[$key], - array_keys($promisesOrValues), + array_keys($promisesOrValuesArray), )); return new Promise($promise, $this); diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index ab6143a0f..f628f3684 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -69,26 +69,30 @@ public function createRejected(\Throwable $reason): Promise return new Promise($promise->reject($reason), $this); } - public function all(array $promisesOrValues): Promise + public function all(iterable $promisesOrValues): Promise { $all = new SyncPromise(); - $total = \count($promisesOrValues); + $total = \is_array($promisesOrValues) + ? \count($promisesOrValues) + : \iterator_count($promisesOrValues); $count = 0; $result = []; + $resolveAllWhenFinished = function () use ($count, $total, $all, $result): void { + if ($count === $total) { + $all->resolve($result); + } + }; + foreach ($promisesOrValues as $index => $promiseOrValue) { if ($promiseOrValue instanceof Promise) { $result[$index] = null; $promiseOrValue->then( - static function ($value) use ($index, &$count, $total, &$result, $all): void { + static function ($value) use (&$result, $index, &$count, &$resolveAllWhenFinished): void { $result[$index] = $value; ++$count; - if ($count < $total) { - return; - } - - $all->resolve($result); + $resolveAllWhenFinished(); }, [$all, 'reject'] ); @@ -98,9 +102,7 @@ static function ($value) use ($index, &$count, $total, &$result, $all): void { } } - if ($count === $total) { - $all->resolve($result); - } + $resolveAllWhenFinished(); return new Promise($all, $this); } diff --git a/src/Executor/Promise/PromiseAdapter.php b/src/Executor/Promise/PromiseAdapter.php index 92579ccf4..cf10caf12 100644 --- a/src/Executor/Promise/PromiseAdapter.php +++ b/src/Executor/Promise/PromiseAdapter.php @@ -96,11 +96,11 @@ public function createRejected(\Throwable $reason): Promise; * * @template V * - * @param array $promisesOrValues + * @param iterable $promisesOrValues * * @return Promise> * * @api */ - public function all(array $promisesOrValues): Promise; + public function all(iterable $promisesOrValues): Promise; } From 820d73c037ffb67039a3b69b6052d135ba17f390 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 19 Dec 2022 11:48:39 +0100 Subject: [PATCH 26/36] improve ReactPromiseAdapterTest.php --- .../Promise/ReactPromiseAdapterTest.php | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/Executor/Promise/ReactPromiseAdapterTest.php b/tests/Executor/Promise/ReactPromiseAdapterTest.php index a54098f66..0369d7494 100644 --- a/tests/Executor/Promise/ReactPromiseAdapterTest.php +++ b/tests/Executor/Promise/ReactPromiseAdapterTest.php @@ -18,18 +18,16 @@ /** * @group ReactPromise */ -class ReactPromiseAdapterTest extends TestCase +final class ReactPromiseAdapterTest extends TestCase { public function testIsThenableReturnsTrueWhenAReactPromiseIsGiven(): void { $reactAdapter = new ReactPromiseAdapter(); - self::assertTrue( - $reactAdapter->isThenable(new ReactPromise(static function (): void { - })) - ); + self::assertTrue($reactAdapter->isThenable(new ReactPromise(static fn () => null))); self::assertTrue($reactAdapter->isThenable(resolve())); self::assertTrue($reactAdapter->isThenable(reject())); + self::assertFalse($reactAdapter->isThenable(static fn () => null)); self::assertFalse($reactAdapter->isThenable(false)); self::assertFalse($reactAdapter->isThenable(true)); self::assertFalse($reactAdapter->isThenable(1)); @@ -57,7 +55,6 @@ public function testThen(): void $promise = $reactAdapter->convertThenable($reactPromise); $result = null; - $resultPromise = $reactAdapter->then( $promise, static function ($value) use (&$result): void { @@ -79,7 +76,6 @@ public function testCreate(): void self::assertInstanceOf(Promise::class, $resolvedPromise->adoptedPromise); $result = null; - $resolvedPromise->then(static function ($value) use (&$result): void { $result = $value; }); @@ -95,7 +91,6 @@ public function testCreateFulfilled(): void self::assertInstanceOf(FulfilledPromise::class, $fulfilledPromise->adoptedPromise); $result = null; - $fulfilledPromise->then(static function ($value) use (&$result): void { $result = $value; }); @@ -111,7 +106,6 @@ public function testCreateRejected(): void self::assertInstanceOf(RejectedPromise::class, $rejectedPromise->adoptedPromise); $exception = null; - $rejectedPromise->then( null, static function ($error) use (&$exception): void { @@ -119,7 +113,7 @@ static function ($error) use (&$exception): void { } ); - self::assertInstanceOf('\Exception', $exception); + self::assertInstanceOf(\Exception::class, $exception); self::assertEquals('I am a bad promise', $exception->getMessage()); } @@ -133,7 +127,6 @@ public function testAll(): void self::assertInstanceOf(FulfilledPromise::class, $allPromise->adoptedPromise); $result = null; - $allPromise->then(static function ($values) use (&$result): void { $result = $values; }); @@ -146,8 +139,8 @@ public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromise $reactAdapter = new ReactPromiseAdapter(); $deferred = new Deferred(); $promises = [resolve(1), $deferred->promise(), resolve(3)]; - $result = null; + $result = null; $reactAdapter->all($promises)->then(static function ($values) use (&$result): void { $result = $values; }); From 506834baf0c38f3e3e387b9fd2883a03539760d3 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 19 Dec 2022 11:49:01 +0100 Subject: [PATCH 27/36] minimize diff in SyncPromise --- src/Executor/Promise/Adapter/SyncPromise.php | 121 +++++++++---------- 1 file changed, 59 insertions(+), 62 deletions(-) diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index a02d77bd5..d27f61c0e 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -9,11 +9,10 @@ * 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. + * Instead, they should use @see \GraphQL\Deferred which enforces `$executor` callback in the constructor. * - * Root SyncPromise without explicit $executor will never resolve (actually throw while trying). + * 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. * @@ -44,7 +43,16 @@ class SyncPromise * } * > */ - private array $waiting = []; + protected array $waiting = []; + + public static function runQueue(): void + { + $q = self::getQueue(); + while (! $q->isEmpty()) { + $task = $q->dequeue(); + $task(); + } + } /** * @param Executor|null $executor @@ -108,36 +116,27 @@ function ($reason): void { } /** - * @template VFulfilled - * @template VRejected - * - * @param (callable(V): VFulfilled)|null $onFulfilled - * @param (callable(Throwable): VRejected)|null $onRejected - * - * @return self<( - * $onFulfilled is not null - * ? ($onRejected is not null ? VFulfilled|VRejected : VFulfilled) - * : ($onRejected is not null ? VRejected : V) - * )> + * @return self */ - public function then(?callable $onFulfilled = null, ?callable $onRejected = null): self + public function reject(\Throwable $reason): self { - if ($this->state === self::REJECTED && $onRejected === null) { - return $this; - } - - if ($this->state === self::FULFILLED && $onFulfilled === null) { - return $this; - } - - $tmp = new self(); - $this->waiting[] = [$tmp, $onFulfilled, $onRejected]; + switch ($this->state) { + case self::PENDING: + $this->state = self::REJECTED; + $this->result = $reason; + $this->enqueueWaitingPromises(); + break; + case self::REJECTED: + if ($reason !== $this->result) { + throw new \Exception('Cannot change rejection reason'); + } - if ($this->state !== self::PENDING) { - $this->enqueueWaitingPromises(); + break; + case self::FULFILLED: + throw new \Exception('Cannot reject fulfilled promise'); } - return $tmp; + return $this; } private function enqueueWaitingPromises(): void @@ -174,48 +173,46 @@ private function enqueueWaitingPromises(): void } /** - * @throws \Exception - * - * @return self + * @return \SplQueue */ - public function reject(\Throwable $reason): self + public static function getQueue(): \SplQueue { - switch ($this->state) { - case self::PENDING: - $this->state = self::REJECTED; - $this->result = $reason; - $this->enqueueWaitingPromises(); - break; - case self::REJECTED: - if ($reason !== $this->result) { - throw new \Exception('Cannot change rejection reason'); - } - - break; - case self::FULFILLED: - throw new \Exception('Cannot reject fulfilled promise'); - } - - return $this; - } + static $queue; - public static function runQueue(): void - { - $q = self::getQueue(); - while (! $q->isEmpty()) { - $task = $q->dequeue(); - $task(); - } + return $queue ??= new \SplQueue(); } /** - * @return \SplQueue + * @template VFulfilled + * @template VRejected + * + * @param (callable(V): VFulfilled)|null $onFulfilled + * @param (callable(Throwable): VRejected)|null $onRejected + * + * @return self<( + * $onFulfilled is not null + * ? ($onRejected is not null ? VFulfilled|VRejected : VFulfilled) + * : ($onRejected is not null ? VRejected : V) + * )> */ - public static function getQueue(): \SplQueue + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): self { - static $queue; + if ($this->state === self::REJECTED && $onRejected === null) { + return $this; + } - return $queue ??= new \SplQueue(); + if ($this->state === self::FULFILLED && $onFulfilled === null) { + return $this; + } + + $tmp = new self(); + $this->waiting[] = [$tmp, $onFulfilled, $onRejected]; + + if ($this->state !== self::PENDING) { + $this->enqueueWaitingPromises(); + } + + return $tmp; } /** From 0f34cf312272c150505e5ec01c717d33b227f9b8 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 19 Dec 2022 11:49:22 +0100 Subject: [PATCH 28/36] improve phpdoc in PromiseAdapter.php --- src/Executor/Promise/PromiseAdapter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Executor/Promise/PromiseAdapter.php b/src/Executor/Promise/PromiseAdapter.php index cf10caf12..dfe7f9115 100644 --- a/src/Executor/Promise/PromiseAdapter.php +++ b/src/Executor/Promise/PromiseAdapter.php @@ -32,8 +32,8 @@ public function isThenable($value): bool; public function convertThenable($thenable): Promise; /** - * Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described - * in Promises/A+ specs. Then returns new wrapped instance of GraphQL\Executor\Promise\Promise. + * Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described in Promises/A+ specs. + * Then returns new wrapped instance of @see \GraphQL\Executor\Promise\Promise. * * @template T * @template TFulfilled of mixed From 50a6a8fa87a5309980e6ce22aa975691fe796421 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 19 Dec 2022 11:49:47 +0100 Subject: [PATCH 29/36] improve whitespace in Helper.php --- src/Server/Helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Server/Helper.php b/src/Server/Helper.php index 9e97a0443..298c4d18f 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -204,8 +204,8 @@ public function executeOperation(ServerConfig $config, OperationParams $op) public function executeBatch(ServerConfig $config, array $operations) { $promiseAdapter = $config->getPromiseAdapter() ?? Executor::getPromiseAdapter(); - $result = []; + $result = []; foreach ($operations as $operation) { $result[] = $this->promiseToExecuteOperation($promiseAdapter, $config, $operation, true); } From 2a1e3651456c82508d08e0a72846050f6b95c8eb Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 19 Dec 2022 12:11:29 +0100 Subject: [PATCH 30/36] fully qualified throwable --- src/Executor/ExecutionResult.php | 5 +---- src/Executor/Promise/Adapter/SyncPromise.php | 5 ++--- src/Executor/Promise/PromiseAdapter.php | 6 ++---- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Executor/ExecutionResult.php b/src/Executor/ExecutionResult.php index 40a8fdb03..469c9538a 100644 --- a/src/Executor/ExecutionResult.php +++ b/src/Executor/ExecutionResult.php @@ -7,7 +7,6 @@ use GraphQL\Error\DebugFlag; use GraphQL\Error\Error; use GraphQL\Error\FormattedError; -use Throwable; /** * Returned after [query execution](executing-queries.md). @@ -17,8 +16,6 @@ * Could be converted to [spec-compliant](https://facebook.github.io/graphql/#sec-Response-Format) * serializable array using `toArray()`. * - * @see Throwable - * * @phpstan-type SerializableError array{ * message: string, * locations?: array, @@ -31,7 +28,7 @@ * errors?: SerializableErrors, * extensions?: array * } - * @phpstan-type ErrorFormatter callable(Throwable): SerializableError + * @phpstan-type ErrorFormatter callable(\Throwable): SerializableError * @phpstan-type ErrorsHandler callable(array $errors, ErrorFormatter $formatter): SerializableErrors */ class ExecutionResult implements \JsonSerializable diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index d27f61c0e..3f6abc334 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -3,7 +3,6 @@ namespace GraphQL\Executor\Promise\Adapter; use GraphQL\Error\InvariantViolation; -use Throwable; /** * Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode @@ -39,7 +38,7 @@ class SyncPromise * array{ * self, * (callable(mixed): mixed)|null, - * (callable(Throwable): mixed)|null + * (callable(\Throwable): mixed)|null * } * > */ @@ -187,7 +186,7 @@ public static function getQueue(): \SplQueue * @template VRejected * * @param (callable(V): VFulfilled)|null $onFulfilled - * @param (callable(Throwable): VRejected)|null $onRejected + * @param (callable(\Throwable): VRejected)|null $onRejected * * @return self<( * $onFulfilled is not null diff --git a/src/Executor/Promise/PromiseAdapter.php b/src/Executor/Promise/PromiseAdapter.php index dfe7f9115..51cf1f9e5 100644 --- a/src/Executor/Promise/PromiseAdapter.php +++ b/src/Executor/Promise/PromiseAdapter.php @@ -2,8 +2,6 @@ namespace GraphQL\Executor\Promise; -use Throwable; - /** * Provides a means for integration of async PHP platforms ([related docs](data-fetching.md#async-php)). */ @@ -41,7 +39,7 @@ public function convertThenable($thenable): Promise; * * @param Promise $promise * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled - * @param (callable(Throwable): (Promise|TRejected))|null $onRejected + * @param (callable(\Throwable): (Promise|TRejected))|null $onRejected * * @return Promise<( * $onFulfilled is not null @@ -58,7 +56,7 @@ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable * * @template V * - * @param callable(callable(V): void $resolve, callable(Throwable): void $reject): void $resolver + * @param callable(callable(V): void $resolve, callable(\Throwable): void $reject): void $resolver * * @return Promise * From d796e4a7e102518db19996a9f99c324d463b92a0 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 19 Dec 2022 11:12:26 +0000 Subject: [PATCH 31/36] Prettify docs --- docs/class-reference.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/class-reference.md b/docs/class-reference.md index 60aec2b1f..38f6990be 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -1334,8 +1334,6 @@ Represents both - result of successful execution and of a failed one Could be converted to [spec-compliant](https://facebook.github.io/graphql/#sec-Response-Format) serializable array using `toArray()`. -@see Throwable - @phpstan-type SerializableError array{ message: string, locations?: array, @@ -1348,7 +1346,7 @@ data?: array, errors?: SerializableErrors, extensions?: array } -@phpstan-type ErrorFormatter callable(Throwable): SerializableError +@phpstan-type ErrorFormatter callable(\Throwable): SerializableError @phpstan-type ErrorsHandler callable(array $errors, ErrorFormatter $formatter): SerializableErrors ### GraphQL\Executor\ExecutionResult Props @@ -1476,8 +1474,8 @@ function convertThenable($thenable): GraphQL\Executor\Promise\Promise ```php /** - * Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described - * in Promises/A+ specs. Then returns new wrapped instance of GraphQL\Executor\Promise\Promise. + * Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described in Promises/A+ specs. + * Then returns new wrapped instance of @see \GraphQL\Executor\Promise\Promise. * * @template T * @template TFulfilled of mixed @@ -1485,7 +1483,7 @@ function convertThenable($thenable): GraphQL\Executor\Promise\Promise * * @param Promise $promise * @param (callable(T): (Promise|TFulfilled))|null $onFulfilled - * @param (callable(Throwable): (Promise|TRejected))|null $onRejected + * @param (callable(\Throwable): (Promise|TRejected))|null $onRejected * * @return Promise<( * $onFulfilled is not null @@ -1508,7 +1506,7 @@ function then( * * @template V * - * @param callable(callable(V): void $resolve, callable(Throwable): void $reject): void $resolver + * @param callable(callable(V): void $resolve, callable(\Throwable): void $reject): void $resolver * * @return Promise * @@ -1552,13 +1550,13 @@ function createRejected(Throwable $reason): GraphQL\Executor\Promise\Promise * * @template V * - * @param array $promisesOrValues + * @param iterable $promisesOrValues * * @return Promise> * * @api */ -function all(array $promisesOrValues): GraphQL\Executor\Promise\Promise +function all(iterable $promisesOrValues): GraphQL\Executor\Promise\Promise ``` ## GraphQL\Validator\DocumentValidator From c7fdf5c13b2021fb6ac94c9972f759fad1a6bdd6 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 19 Dec 2022 12:14:31 +0100 Subject: [PATCH 32/36] remove unnecessary generic --- src/Executor/Promise/Adapter/SyncPromiseAdapter.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index c20093ffd..d47b44b1a 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -157,9 +157,7 @@ protected function beforeWait(Promise $promise): void /** * Execute while running promise completion. * - * @template V - * - * @param Promise $promise + * @param Promise $promise */ protected function onWait(Promise $promise): void { From a6212de2e10463650ba8e7f0f64a3a09397ef21e Mon Sep 17 00:00:00 2001 From: Warxcell Date: Tue, 20 Dec 2022 10:21:45 +0200 Subject: [PATCH 33/36] Add generics --- src/Executor/ReferenceExecutor.php | 54 +++++++++++++++++------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 10459342f..f77dd5d84 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -233,27 +233,36 @@ public function doExecute(): Promise // be executed. An execution which encounters errors will still result in a // resolved Promise. $data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue); - - if ($data instanceof Promise) { - $data = $data->then(fn ($resolved) => $this->buildResponse($resolved)); - } + $result = $this->buildResponse($data); // Note: we deviate here from the reference implementation a bit by always returning promise // But for the "sync" case it is always fulfilled - $promise = $this->getPromise($data); + $promise = $this->getPromise($result); if ($promise !== null) { - return $promise->then(fn ($resolved) => $this->buildResponse($resolved)); + return $promise; } - return $this->exeContext->promiseAdapter->createFulfilled($data); + return $this->exeContext->promiseAdapter->createFulfilled($result); } /** * @param mixed $data + * + * @return ExecutionResult|Promise */ - protected function buildResponse($data): ExecutionResult + protected function buildResponse($data) { + if ($data instanceof Promise) { + return $data->then(fn ($resolved) => $this->buildResponse($resolved)); + } + + $promiseAdapter = $this->exeContext->promiseAdapter; + if ($promiseAdapter->isThenable($data)) { + return $promiseAdapter->convertThenable($data) + ->then(fn ($resolved) => $this->buildResponse($resolved)); + } + if ($data !== null) { $data = (array) $data; } @@ -266,7 +275,7 @@ protected function buildResponse($data): ExecutionResult * * @param mixed $rootValue * - * @return Promise>|Promise|array|\stdClass|null + * @return array|Promise|\stdClass|null */ protected function executeOperation(OperationDefinitionNode $operation, $rootValue) { @@ -299,7 +308,7 @@ protected function executeOperation(OperationDefinitionNode $operation, $rootVal /** * @param mixed $error * - * @return Promise + * @return Promise|null */ public function onError($error): ?Promise { @@ -508,7 +517,7 @@ protected function doesFragmentConditionMatch(Node $fragment, ObjectType $type): * * @phpstan-param Fields $fields * - * @return Promise>|Promise<\stdClass>|array|\stdClass + * @return array|Promise>|\stdClass */ protected function executeFieldsSerially(ObjectType $parentType, $rootValue, array $path, \ArrayObject $fields) { @@ -671,7 +680,7 @@ protected function resolveFieldValueOrError( FieldDefinition $fieldDef, FieldNode $fieldNode, callable $resolveFn, - $rootValue, + $rootValue, ResolveInfo $info ) { try { @@ -701,7 +710,7 @@ protected function resolveFieldValueOrError( * * @param mixed $result * - * @return Promise>|Promise|array|\stdClass|null + * @return array|Promise>|\stdClass|null */ protected function completeValueCatchingError( Type $returnType, @@ -791,7 +800,7 @@ protected function handleFieldError($rawError, \ArrayObject $fieldNodes, array $ * @throws Error * @throws \Throwable * - * @return array|mixed|Promise|null + * @return array|mixed|Promise>|null */ protected function completeValue( Type $returnType, @@ -876,11 +885,8 @@ protected function isPromise($value): bool * Only returns the value if it acts like a Promise, i.e. has a "then" function, * otherwise returns null. * - * @template T - * - * @param T $value - * - * @return (T === null || T instanceof Promise ? T : (Promise|null)) + * @param mixed $value + * @return Promise */ protected function getPromise($value): ?Promise { @@ -934,7 +940,7 @@ function ($previous, $value) use ($callback) { * * @throws \Exception * - * @return Promise>|array|\stdClass + * @return array|Promise>|\stdClass */ protected function completeListValue( ListOfType $returnType, @@ -1001,7 +1007,7 @@ protected function completeLeafValue(LeafType $returnType, &$result) * * @throws Error * - * @return Promise>|array|\stdClass + * @return array|Promise>|\stdClass */ protected function completeAbstractValue( AbstractType $returnType, @@ -1124,7 +1130,7 @@ protected function defaultTypeResolver($value, $contextValue, ResolveInfo $info, * * @throws Error * - * @return Promise>|array|\stdClass + * @return array|Promise>|\stdClass */ protected function completeObjectValue( ObjectType $returnType, @@ -1179,7 +1185,7 @@ protected function completeObjectValue( */ protected function invalidReturnTypeError( ObjectType $returnType, - $result, + $result, \ArrayObject $fieldNodes ): Error { $safeResult = Utils::printSafe($result); @@ -1197,7 +1203,7 @@ protected function invalidReturnTypeError( * * @throws Error * - * @return Promise>|array|\stdClass + * @return array|Promise>|\stdClass */ protected function collectAndExecuteSubfields( ObjectType $returnType, From fecb03e7f588c26079225679c127cf5aec9139c6 Mon Sep 17 00:00:00 2001 From: Warxcell Date: Tue, 20 Dec 2022 08:22:35 +0000 Subject: [PATCH 34/36] Apply php-cs-fixer changes --- src/Executor/ReferenceExecutor.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index f77dd5d84..a863017de 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -275,7 +275,7 @@ protected function buildResponse($data) * * @param mixed $rootValue * - * @return array|Promise|\stdClass|null + * @return array|Promise|\stdClass|null */ protected function executeOperation(OperationDefinitionNode $operation, $rootValue) { @@ -680,7 +680,7 @@ protected function resolveFieldValueOrError( FieldDefinition $fieldDef, FieldNode $fieldNode, callable $resolveFn, - $rootValue, + $rootValue, ResolveInfo $info ) { try { @@ -886,6 +886,7 @@ protected function isPromise($value): bool * otherwise returns null. * * @param mixed $value + * * @return Promise */ protected function getPromise($value): ?Promise @@ -1185,7 +1186,7 @@ protected function completeObjectValue( */ protected function invalidReturnTypeError( ObjectType $returnType, - $result, + $result, \ArrayObject $fieldNodes ): Error { $safeResult = Utils::printSafe($result); From 531e03b808d11dee0a1d4c65ea193109a57b281f Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 4 Jan 2023 10:56:48 +0200 Subject: [PATCH 35/36] ... --- src/Executor/Promise/Adapter/SyncPromise.php | 4 +--- src/Executor/Promise/Promise.php | 9 ++------- src/Executor/ReferenceExecutor.php | 5 +++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index 3f6abc334..9bd048c7d 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -16,8 +16,6 @@ * at least one $executor to start the promise chain. * * @template V - * - * @phpstan-type Executor callable(): V */ class SyncPromise { @@ -54,7 +52,7 @@ public static function runQueue(): void } /** - * @param Executor|null $executor + * @param (callable(): V)|null $executor */ public function __construct(?callable $executor = null) { diff --git a/src/Executor/Promise/Promise.php b/src/Executor/Promise/Promise.php index 2f4f52172..ec6224192 100644 --- a/src/Executor/Promise/Promise.php +++ b/src/Executor/Promise/Promise.php @@ -3,7 +3,6 @@ namespace GraphQL\Executor\Promise; use Amp\Promise as AmpPromise; -use GraphQL\Error\InvariantViolation; use GraphQL\Executor\Promise\Adapter\SyncPromise; use React\Promise\PromiseInterface as ReactPromise; @@ -14,20 +13,16 @@ */ class Promise { - /** @var SyncPromise|ReactPromise|AmpPromise */ + /** @var SyncPromise|ReactPromise|AmpPromise */ public $adoptedPromise; private PromiseAdapter $adapter; /** - * @param mixed $adoptedPromise + * @param SyncPromise|ReactPromise|AmpPromise $adoptedPromise */ public function __construct($adoptedPromise, PromiseAdapter $adapter) { - if ($adoptedPromise instanceof self) { - throw new InvariantViolation('Expecting promise from adapted system, got ' . self::class); - } - $this->adoptedPromise = $adoptedPromise; $this->adapter = $adapter; } diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index a863017de..6652d80ef 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -885,9 +885,10 @@ protected function isPromise($value): bool * Only returns the value if it acts like a Promise, i.e. has a "then" function, * otherwise returns null. * - * @param mixed $value + * @template V + * @param V $value * - * @return Promise + * @return Promise|null */ protected function getPromise($value): ?Promise { From 56796ea52e1682bcbebd17d6e8cc777e8df0c70f Mon Sep 17 00:00:00 2001 From: Warxcell Date: Wed, 4 Jan 2023 08:57:43 +0000 Subject: [PATCH 36/36] Apply php-cs-fixer changes --- src/Executor/ReferenceExecutor.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 6652d80ef..e83a4462b 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -886,6 +886,7 @@ protected function isPromise($value): bool * otherwise returns null. * * @template V + * * @param V $value * * @return Promise|null