diff --git a/Executor/Executor.php b/Executor/Executor.php index 4cd95a12e..54ffdf200 100644 --- a/Executor/Executor.php +++ b/Executor/Executor.php @@ -13,8 +13,8 @@ use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\Promise\Promise; +use GraphQL\Executor\Promise\PromiseAdapter; use GraphQL\Schema; -use Overblog\GraphQLBundle\Executor\Promise\PromiseAdapterInterface; class Executor implements ExecutorInterface { @@ -34,9 +34,9 @@ public function execute(Schema $schema, $requestString, $rootValue = null, $cont } /** - * @param PromiseAdapterInterface|null $promiseAdapter + * @param PromiseAdapter|null $promiseAdapter */ - public function setPromiseAdapter(PromiseAdapterInterface $promiseAdapter = null) + public function setPromiseAdapter(PromiseAdapter $promiseAdapter = null) { call_user_func_array('GraphQL\GraphQL::setPromiseAdapter', func_get_args()); } diff --git a/Executor/ExecutorInterface.php b/Executor/ExecutorInterface.php index 166faf533..e51056ecb 100644 --- a/Executor/ExecutorInterface.php +++ b/Executor/ExecutorInterface.php @@ -13,8 +13,8 @@ use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\Promise\Promise; +use GraphQL\Executor\Promise\PromiseAdapter; use GraphQL\Schema; -use Overblog\GraphQLBundle\Executor\Promise\PromiseAdapterInterface; interface ExecutorInterface { @@ -31,7 +31,7 @@ interface ExecutorInterface public function execute(Schema $schema, $requestString, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null); /** - * @param PromiseAdapterInterface|null $promiseAdapter + * @param PromiseAdapter|null $promiseAdapter */ - public function setPromiseAdapter(PromiseAdapterInterface $promiseAdapter = null); + public function setPromiseAdapter(PromiseAdapter $promiseAdapter = null); } diff --git a/Executor/Promise/Adapter/GraphQLPromiseAdapter.php b/Executor/Promise/Adapter/GraphQLPromiseAdapter.php deleted file mode 100644 index c159dec21..000000000 --- a/Executor/Promise/Adapter/GraphQLPromiseAdapter.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Overblog\GraphQLBundle\Executor\Promise\Adapter; - -use GraphQL\Executor\Promise\Adapter\SyncPromise; -use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter as BaseSyncPromiseAdapter; -use GraphQL\Executor\Promise\Promise; -use Overblog\GraphQLBundle\Executor\Promise\PromiseAdapterInterface; - -class GraphQLPromiseAdapter extends BaseSyncPromiseAdapter implements PromiseAdapterInterface -{ - /** - * {@inheritdoc} - */ - public function isThenable($value) - { - $valueOrPromise = $value instanceof Promise ? $value->adoptedPromise : $value; - - return parent::isThenable($valueOrPromise) || $valueOrPromise instanceof SyncPromise; - } - - /** - * {@inheritdoc} - */ - public function convertThenable($thenable) - { - if ($thenable instanceof Promise) { - return $thenable; - } - - return parent::convertThenable($thenable); - } -} diff --git a/Request/Executor.php b/Request/Executor.php index 1de224708..c79482bdf 100644 --- a/Request/Executor.php +++ b/Request/Executor.php @@ -12,7 +12,7 @@ namespace Overblog\GraphQLBundle\Request; use GraphQL\Executor\ExecutionResult; -use GraphQL\Executor\Promise\Promise; +use GraphQL\Executor\Promise\PromiseAdapter; use GraphQL\Schema; use GraphQL\Validator\DocumentValidator; use GraphQL\Validator\Rules\QueryComplexity; @@ -54,7 +54,7 @@ class Executor private $executor; /** - * @var PromiseAdapterInterface + * @var PromiseAdapter */ private $promiseAdapter; @@ -64,7 +64,7 @@ public function __construct( $throwException = false, ErrorHandler $errorHandler = null, $hasDebugInfo = false, - PromiseAdapterInterface $promiseAdapter = null + PromiseAdapter $promiseAdapter = null ) { $this->executor = $executor; $this->dispatcher = $dispatcher; @@ -81,6 +81,13 @@ public function setExecutor(ExecutorInterface $executor) return $this; } + public function setPromiseAdapter(PromiseAdapter $promiseAdapter = null) + { + $this->promiseAdapter = $promiseAdapter; + + return $this; + } + public function addSchema($name, Schema $schema) { $this->schemas[$name] = $schema; @@ -141,6 +148,18 @@ public function execute(array $data, array $context = [], $schemaName = null) $context = $event->getExecutorContext(); } + if ($this->promiseAdapter) { + if (!$this->promiseAdapter instanceof PromiseAdapterInterface && !is_callable([$this->promiseAdapter, 'wait'])) { + throw new \RuntimeException( + sprintf( + 'PromiseAdapter should be an object instantiating "%s" or "%s" with a "wait" method.', + 'Overblog\\GraphQLBundle\\Executor\\Promise\\PromiseAdapterInterface', + 'GraphQL\\Executor\\Promise\\PromiseAdapter' + ) + ); + } + } + $schema = $this->getSchema($schemaName); $startTime = microtime(true); @@ -157,20 +176,19 @@ public function execute(array $data, array $context = [], $schemaName = null) isset($data[ParserInterface::PARAM_OPERATION_NAME]) ? $data[ParserInterface::PARAM_OPERATION_NAME] : null ); - if (!is_object($result) || (!$result instanceof ExecutionResult && !$result instanceof Promise)) { + if ($this->promiseAdapter && $this->promiseAdapter->isThenable($result)) { + $result = $this->promiseAdapter->wait($result); + } + + if (!is_object($result) || !$result instanceof ExecutionResult) { throw new \RuntimeException( sprintf( - 'Execution result should be an object instantiating "%s" or "%s".', - 'GraphQL\\Executor\\ExecutionResult', - 'GraphQL\\Executor\\Promise\\Promise' + 'Execution result should be an object instantiating "%s".', + 'GraphQL\\Executor\\ExecutionResult' ) ); } - if ($this->promiseAdapter && $this->promiseAdapter->isThenable($result)) { - $result = $this->promiseAdapter->wait($result); - } - return $this->prepareResult($result, $startTime, $startMemoryUsage); } diff --git a/Resolver/AccessResolver.php b/Resolver/AccessResolver.php index f1714c191..5cfebccf7 100644 --- a/Resolver/AccessResolver.php +++ b/Resolver/AccessResolver.php @@ -11,8 +11,9 @@ namespace Overblog\GraphQLBundle\Resolver; +use GraphQL\Executor\Promise\Adapter\SyncPromise; use GraphQL\Executor\Promise\Promise; -use GraphQL\Executor\Promise\PromiseAdapter as PromiseAdapterInterface; +use GraphQL\Executor\Promise\PromiseAdapter; use Overblog\GraphQLBundle\Error\UserError; use Overblog\GraphQLBundle\Error\UserWarning; use Overblog\GraphQLBundle\Relay\Connection\Output\Connection; @@ -20,10 +21,10 @@ class AccessResolver { - /** @var PromiseAdapterInterface */ + /** @var PromiseAdapter */ private $promiseAdapter; - public function __construct(PromiseAdapterInterface $promiseAdapter) + public function __construct(PromiseAdapter $promiseAdapter) { $this->promiseAdapter = $promiseAdapter; } @@ -47,15 +48,17 @@ public function resolve(callable $accessChecker, callable $resolveCallback, arra private function filterResultUsingAccess(callable $accessChecker, callable $resolveCallback, array $resolveArgs = []) { $result = call_user_func_array($resolveCallback, $resolveArgs); + if ($result instanceof Promise) { + $result = $result->adoptedPromise; + } - if ($this->promiseAdapter->isThenable($result)) { - if (!$result instanceof Promise) { - $result = $this->promiseAdapter->convertThenable($result); - } - - return $this->promiseAdapter->then($result, function ($result) use ($accessChecker, $resolveArgs) { - return $this->processFilter($result, $accessChecker, $resolveArgs); - }); + if ($this->promiseAdapter->isThenable($result) || $result instanceof SyncPromise) { + return $this->promiseAdapter->then( + new Promise($result, $this->promiseAdapter), + function ($result) use ($accessChecker, $resolveArgs) { + return $this->processFilter($result, $accessChecker, $resolveArgs); + } + ); } return $this->processFilter($result, $accessChecker, $resolveArgs); diff --git a/Resources/config/services.yml b/Resources/config/services.yml index cf73e6356..ed11bd8f1 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -98,7 +98,7 @@ services: - { name: kernel.event_listener, event: graphql.executor.context, method: onExecutorContextEvent } overblog_graphql.promise_adapter.default: - class: Overblog\GraphQLBundle\Executor\Promise\Adapter\GraphQLPromiseAdapter + class: GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter public: false overblog_graphql.react.promise_adapter: diff --git a/Resources/doc/data-fetching/promise.md b/Resources/doc/data-fetching/promise.md index 84326406c..276cf1755 100644 --- a/Resources/doc/data-fetching/promise.md +++ b/Resources/doc/data-fetching/promise.md @@ -3,8 +3,11 @@ The bundle is totally "promise ready", by default it use **Webonyx/GraphQL-Php** SyncPromise adapter (supporting the native deferred feature) and it also comes with [ReactPHP/Promise](https://github.com/reactphp/promise) adapter. -To integrate an other promise implementation, you must create a new service that -implements `Overblog\GraphQLBundle\Executor\Promise\PromiseAdapterInterface`. +To integrate an other promise implementation, you must create a new service that +implements `Overblog\GraphQLBundle\Executor\Promise\PromiseAdapterInterface` +or `GraphQL\Executor\Promise\PromiseAdapter` with a `wait` method that accepts +a Promise like argument and returns the result of the promise resolved +or throw an exception otherwise. Config bundle to use the new service: @@ -23,11 +26,11 @@ in resolver like this: ```php promiseAdapter = $promiseAdapter; } diff --git a/Tests/Functional/app/Resolver/ConnectionResolver.php b/Tests/Functional/app/Resolver/ConnectionResolver.php index cd825b2f9..b0bce24ee 100644 --- a/Tests/Functional/app/Resolver/ConnectionResolver.php +++ b/Tests/Functional/app/Resolver/ConnectionResolver.php @@ -12,9 +12,9 @@ namespace Overblog\GraphQLBundle\Tests\Functional\app\Resolver; use GraphQL\Deferred; +use GraphQL\Executor\Promise\PromiseAdapter; use Overblog\GraphQLBundle\Executor\Promise\Adapter\GraphQLPromiseAdapter; use Overblog\GraphQLBundle\Executor\Promise\Adapter\ReactPromiseAdapter; -use Overblog\GraphQLBundle\Executor\Promise\PromiseAdapterInterface; use Overblog\GraphQLBundle\Relay\Connection\Output\ConnectionBuilder; use Overblog\GraphQLBundle\Relay\Connection\Output\Edge; use React\Promise\Promise; @@ -45,11 +45,11 @@ class ConnectionResolver ]; /** - * @var PromiseAdapterInterface + * @var PromiseAdapter */ private $promiseAdapter; - public function __construct(PromiseAdapterInterface $promiseAdapter) + public function __construct(PromiseAdapter $promiseAdapter) { $this->promiseAdapter = $promiseAdapter; } diff --git a/Tests/Request/ExecutorTest.php b/Tests/Request/ExecutorTest.php index 1a919ff32..7cf88b898 100644 --- a/Tests/Request/ExecutorTest.php +++ b/Tests/Request/ExecutorTest.php @@ -11,6 +11,7 @@ namespace Overblog\GraphQLBundle\Tests\Request; +use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter; use GraphQL\Schema; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; @@ -43,7 +44,7 @@ public function setUp() /** * @expectedException \RuntimeException - * @expectedExceptionMessage Execution result should be an object instantiating "GraphQL\Executor\ExecutionResult" or "GraphQL\Executor\Promise\Promise". + * @expectedExceptionMessage Execution result should be an object instantiating "GraphQL\Executor\ExecutionResult". */ public function testInvalidExecutorReturnNotObject() { @@ -53,7 +54,7 @@ public function testInvalidExecutorReturnNotObject() /** * @expectedException \RuntimeException - * @expectedExceptionMessage Execution result should be an object instantiating "GraphQL\Executor\ExecutionResult" or "GraphQL\Executor\Promise\Promise". + * @expectedExceptionMessage Execution result should be an object instantiating "GraphQL\Executor\ExecutionResult". */ public function testInvalidExecutorReturnInvalidObject() { @@ -61,6 +62,16 @@ public function testInvalidExecutorReturnInvalidObject() $this->executor->execute($this->request); } + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage PromiseAdapter should be an object instantiating "Overblog\GraphQLBundle\Executor\Promise\PromiseAdapterInterface" or "GraphQL\Executor\Promise\PromiseAdapter" with a "wait" method. + */ + public function testInvalidExecutorAdapterPromise() + { + $this->executor->setPromiseAdapter(new ReactPromiseAdapter()); + $this->executor->execute($this->request); + } + public function testDisabledDebugInfo() { $this->assertArrayNotHasKey('debug', $this->executor->disabledDebugInfo()->execute($this->request)->extensions); diff --git a/composer.json b/composer.json index e8e4d5bc4..eba810a4d 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.7-dev" + "dev-master": "0.8-dev" } } }