Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions docs/class-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1805,6 +1805,8 @@ function toArray(int $debug = 'GraphQL\\Error\\DebugFlag::NONE'): array

Provides a means for integration of async PHP platforms ([related docs](data-fetching.md#async-php)).

@template TAdopted = mixed

### GraphQL\Executor\Promise\PromiseAdapter Methods

```php
Expand All @@ -1824,6 +1826,8 @@ function isThenable($value): bool
*
* @param mixed $thenable
*
* @phpstan-return Promise<TAdopted>
*
* @api
*/
function convertThenable($thenable): GraphQL\Executor\Promise\Promise
Expand All @@ -1834,6 +1838,10 @@ 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.
*
* @phpstan-param Promise<covariant TAdopted> $promise
*
* @phpstan-return Promise<TAdopted>
*
* @api
*/
function then(
Expand All @@ -1849,6 +1857,8 @@ function then(
*
* @param callable(callable $resolve, callable $reject): void $resolver
*
* @phpstan-return Promise<TAdopted>
*
* @api
*/
function create(callable $resolver): GraphQL\Executor\Promise\Promise
Expand All @@ -1860,16 +1870,18 @@ function create(callable $resolver): GraphQL\Executor\Promise\Promise
*
* @param mixed $value
*
* @phpstan-return Promise<TAdopted>
*
* @api
*/
function createFulfilled($value = null): GraphQL\Executor\Promise\Promise
```

```php
/**
* Creates a rejected promise for a reason if the reason is not a promise.
* Return a promise rejected with the given reason.
*
* If the provided reason is a promise, then it is returned as-is.
* @phpstan-return Promise<TAdopted>
*
* @api
*/
Expand All @@ -1883,6 +1895,8 @@ function createRejected(Throwable $reason): GraphQL\Executor\Promise\Promise
*
* @param iterable<Promise|mixed> $promisesOrValues
*
* @phpstan-return Promise<TAdopted>
*
* @api
*/
function all(iterable $promisesOrValues): GraphQL\Executor\Promise\Promise
Expand Down
16 changes: 16 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ parameters:
- src/Executor/Promise
- examples/04-async-php

# The v2 AmpPromiseAdapter wraps concrete Amp\Success/Amp\Failure; against the
# invariant Amp\Promise<mixed> binding PHPStan reports a return mismatch (as
# Amp\Success<mixed> on a v2 install, or bare Amp\Success when v2 is absent on a
# v3 install). Tolerate either form / either state.
- message: "~Method GraphQL\\\\Executor\\\\Promise\\\\Adapter\\\\AmpPromiseAdapter::create(Fulfilled|Rejected)\\(\\) should return GraphQL\\\\Executor\\\\Promise\\\\Promise<Amp\\\\Promise<mixed>> but returns GraphQL\\\\Executor\\\\Promise\\\\Promise<(Amp\\\\Promise<mixed>\\|)?Amp\\\\(Success|Failure)(<mixed>)?(\\|Amp\\\\Promise<mixed>)?>~"
reportUnmatched: false
path: src/Executor/Promise/Adapter/AmpPromiseAdapter.php

# AmpFutureAdapter::createFulfilled()/createRejected() build a concrete Amp\Future
# whose generic arg PHPStan infers narrower than the invariant Amp\Future<mixed>
# binding. Only reported when amphp/amp v3 is installed (absent under v2), so
# tolerate either state.
- message: "~Method GraphQL\\\\Executor\\\\Promise\\\\Adapter\\\\AmpFutureAdapter::create(Fulfilled|Rejected)\\(\\) should return GraphQL\\\\Executor\\\\Promise\\\\Promise<Amp\\\\Future<mixed>> but returns GraphQL\\\\Executor\\\\Promise\\\\Promise<Amp\\\\Future(<[a-z]+>)?>~"
reportUnmatched: false
path: src/Executor/Promise/Adapter/AmpFutureAdapter.php

includes:
- phpstan-baseline.neon
- phpstan/include-by-php-version.php
Expand Down
25 changes: 21 additions & 4 deletions src/Executor/Promise/Adapter/AmpFutureAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* Allows integration with amphp/amp v3 (fiber-based futures).
*
* @see https://amphp.org/amp
*
* @implements PromiseAdapter<Future<mixed>>
*/
class AmpFutureAdapter implements PromiseAdapter
{
Expand All @@ -23,17 +25,26 @@ public function isThenable($value): bool
return $value instanceof Future;
}

/** @throws InvariantViolation */
/**
* @throws InvariantViolation
*
* @phpstan-return Promise<Future<mixed>>
*/
public function convertThenable($thenable): Promise
{
return new Promise($thenable, $this);
}

/** @throws InvariantViolation */
/**
* @phpstan-param Promise<covariant Future<mixed>> $promise
*
* @throws InvariantViolation
*
* @phpstan-return Promise<Future<mixed>>
*/
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise
{
$future = $promise->adoptedPromise;
assert($future instanceof Future);

$next = async(static function () use ($future, $onFulfilled, $onRejected) {
try {
Expand Down Expand Up @@ -80,6 +91,8 @@ static function (\Throwable $exception) use ($deferred): void {
/**
* @throws \Error
* @throws InvariantViolation
*
* @phpstan-return Promise<Future<mixed>>
*/
public function createFulfilled($value = null): Promise
{
Comment thread
simPod marked this conversation as resolved.
Expand All @@ -94,7 +107,11 @@ public function createFulfilled($value = null): Promise
return new Promise(Future::complete($value), $this);
}

/** @throws InvariantViolation */
/**
* @throws InvariantViolation
*
* @phpstan-return Promise<Future<mixed>>
*/
public function createRejected(\Throwable $reason): Promise
{
return new Promise(Future::error($reason), $this);
Expand Down
20 changes: 17 additions & 3 deletions src/Executor/Promise/Adapter/AmpPromiseAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

use function Amp\Promise\all;

/**
* @implements PromiseAdapter<AmpPromise<mixed>>
*/
class AmpPromiseAdapter implements PromiseAdapter
{
public function isThenable($value): bool
Expand All @@ -25,7 +28,13 @@ public function convertThenable($thenable): Promise
return new Promise($thenable, $this);
}

/** @throws InvariantViolation */
/**
* @phpstan-param Promise<covariant AmpPromise<mixed>> $promise
*
* @throws InvariantViolation
*
* @phpstan-return Promise<AmpPromise<mixed>>
*/
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise
{
$deferred = new Deferred();
Expand All @@ -42,7 +51,6 @@ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable
};

$ampPromise = $promise->adoptedPromise;
assert($ampPromise instanceof AmpPromise);
$ampPromise->onResolve($onResolve);

return new Promise($deferred->promise(), $this);
Comment thread
spawnia marked this conversation as resolved.
Expand All @@ -68,6 +76,8 @@ static function (\Throwable $exception) use ($deferred): void {
/**
* @throws \Error
* @throws InvariantViolation
*
* @phpstan-return Promise<AmpPromise<mixed>>
*/
public function createFulfilled($value = null): Promise
{
Expand All @@ -76,7 +86,11 @@ public function createFulfilled($value = null): Promise
return new Promise($promise, $this);
}

/** @throws InvariantViolation */
/**
* @throws InvariantViolation
*
* @phpstan-return Promise<AmpPromise<mixed>>
*/
public function createRejected(\Throwable $reason): Promise
{
$promise = new Failure($reason);
Expand Down
38 changes: 32 additions & 6 deletions src/Executor/Promise/Adapter/ReactPromiseAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
use function React\Promise\reject;
use function React\Promise\resolve;

/**
* @implements PromiseAdapter<ReactPromiseInterface<mixed>>
*/
class ReactPromiseAdapter implements PromiseAdapter
{
public function isThenable($value): bool
Expand All @@ -25,40 +28,62 @@ public function convertThenable($thenable): Promise
return new Promise($thenable, $this);
}

/** @throws InvariantViolation */
/**
* @phpstan-param Promise<covariant ReactPromiseInterface<mixed>> $promise
*
* @throws InvariantViolation
*
* @phpstan-return Promise<ReactPromiseInterface<mixed>>
*/
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise
{
$reactPromise = $promise->adoptedPromise;
assert($reactPromise instanceof ReactPromiseInterface);

return new Promise($reactPromise->then($onFulfilled, $onRejected), $this);
Comment thread
spawnia marked this conversation as resolved.
}

/** @throws InvariantViolation */
/**
* @throws InvariantViolation
*
* @phpstan-return Promise<ReactPromiseInterface<mixed>>
*/
public function create(callable $resolver): Promise
{
$reactPromise = new ReactPromise($resolver);

return new Promise($reactPromise, $this);
}

/** @throws InvariantViolation */
/**
* @throws InvariantViolation
*
* @phpstan-return Promise<ReactPromiseInterface<mixed>>
*/
public function createFulfilled($value = null): Promise
{
$reactPromise = resolve($value);

return new Promise($reactPromise, $this);
}

/** @throws InvariantViolation */
/**
* @throws InvariantViolation
*
* @phpstan-return Promise<ReactPromiseInterface<mixed>>
*/
public function createRejected(\Throwable $reason): Promise
{
/** @var ReactPromiseInterface<mixed> $reactPromise */
$reactPromise = reject($reason);

return new Promise($reactPromise, $this);
}

/** @throws InvariantViolation */
/**
* @throws InvariantViolation
*
* @phpstan-return Promise<ReactPromiseInterface<mixed>>
*/
public function all(iterable $promisesOrValues): Promise
{
foreach ($promisesOrValues as &$promiseOrValue) {
Expand All @@ -70,6 +95,7 @@ public function all(iterable $promisesOrValues): Promise
$promisesOrValuesArray = is_array($promisesOrValues)
? $promisesOrValues
: iterator_to_array($promisesOrValues);
/** @var ReactPromiseInterface<mixed> $reactPromise */
$reactPromise = all($promisesOrValuesArray)->then(static fn (array $values): array => array_map(
static fn ($key) => $values[$key],
array_keys($promisesOrValuesArray),
Expand Down
11 changes: 9 additions & 2 deletions src/Executor/Promise/Adapter/SyncPromiseAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
/**
* Allows changing order of field resolution even in sync environments
* (by leveraging queue of deferreds and promises).
*
* @implements PromiseAdapter<SyncPromise>
*/
class SyncPromiseAdapter implements PromiseAdapter
{
Expand All @@ -32,11 +34,16 @@ public function convertThenable($thenable): Promise
return new Promise($thenable, $this);
}

/** @throws InvariantViolation */
/**
* @phpstan-param Promise<covariant SyncPromise> $promise
*
* @throws InvariantViolation
*
* @phpstan-return Promise<SyncPromise>
*/
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise
{
$syncPromise = $promise->adoptedPromise;
assert($syncPromise instanceof SyncPromise);

return new Promise($syncPromise->then($onFulfilled, $onRejected), $this);
Comment thread
spawnia marked this conversation as resolved.
}
Expand Down
23 changes: 17 additions & 6 deletions src/Executor/Promise/Promise.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,34 @@

namespace GraphQL\Executor\Promise;

use Amp\Future as AmpFuture;
use Amp\Promise as AmpPromise;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use React\Promise\PromiseInterface as ReactPromise;

/**
* Convenience wrapper for promises represented by Promise Adapter.
*
* The adopted promise is whatever the configured adapter produces (e.g.
* {@see \GraphQL\Executor\Promise\Adapter\SyncPromise}, a ReactPHP promise or an
* amphp Future). It is kept as a generic so the concrete platform type never has
* to be named in this class, which would otherwise require importing a class
* that may not exist for the installed platform.
*
* @template TAdopted = mixed
*/
class Promise
{
/** @var SyncPromise|ReactPromise<mixed>|AmpFuture<mixed>|AmpPromise<mixed> */
/**
* @phpstan-var TAdopted
*
* @readonly
Comment thread
simPod marked this conversation as resolved.
*/
public $adoptedPromise;

/** @phpstan-var PromiseAdapter<TAdopted> */
private PromiseAdapter $adapter;

/**
* @param mixed $adoptedPromise
* @phpstan-param TAdopted $adoptedPromise
* @phpstan-param PromiseAdapter<TAdopted> $adapter
*
* @throws InvariantViolation
*/
Comment thread
spawnia marked this conversation as resolved.
Expand All @@ -34,6 +44,7 @@ public function __construct($adoptedPromise, PromiseAdapter $adapter)
$this->adapter = $adapter;
}

/** @phpstan-return Promise<TAdopted> */
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): Promise
{
return $this->adapter->then($this, $onFulfilled, $onRejected);
Expand Down
Loading
Loading