vendor/overblog/graphql-bundle/src/Request/Executor.php line 134

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Overblog\GraphQLBundle\Request;
  4. use ArrayObject;
  5. use Closure;
  6. use GraphQL\Executor\ExecutionResult;
  7. use GraphQL\Executor\Promise\PromiseAdapter;
  8. use GraphQL\GraphQL;
  9. use GraphQL\Type\Schema;
  10. use GraphQL\Validator\DocumentValidator;
  11. use GraphQL\Validator\Rules\DisableIntrospection;
  12. use GraphQL\Validator\Rules\QueryComplexity;
  13. use GraphQL\Validator\Rules\QueryDepth;
  14. use Overblog\GraphQLBundle\Event\Events;
  15. use Overblog\GraphQLBundle\Event\ExecutorArgumentsEvent;
  16. use Overblog\GraphQLBundle\Event\ExecutorContextEvent;
  17. use Overblog\GraphQLBundle\Event\ExecutorResultEvent;
  18. use Overblog\GraphQLBundle\Executor\ExecutorInterface;
  19. use RuntimeException;
  20. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  21. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  22. use function array_keys;
  23. use function is_callable;
  24. use function sprintf;
  25. class Executor
  26. {
  27. public const PROMISE_ADAPTER_SERVICE_ID = 'overblog_graphql.promise_adapter';
  28. private array $schemas = [];
  29. private EventDispatcherInterface $dispatcher;
  30. private PromiseAdapter $promiseAdapter;
  31. private ExecutorInterface $executor;
  32. private bool $useExperimentalExecutor; // TODO: remove in 1.0
  33. /**
  34. * @var callable|null
  35. */
  36. private $defaultFieldResolver;
  37. public function __construct(
  38. ExecutorInterface $executor,
  39. PromiseAdapter $promiseAdapter,
  40. EventDispatcherInterface $dispatcher,
  41. ?callable $defaultFieldResolver = null,
  42. bool $useExperimental = false // TODO: remove in 1.0
  43. ) {
  44. $this->executor = $executor;
  45. $this->promiseAdapter = $promiseAdapter;
  46. $this->dispatcher = $dispatcher;
  47. $this->defaultFieldResolver = $defaultFieldResolver;
  48. $this->useExperimentalExecutor = $useExperimental; // TODO: remove in 1.0
  49. }
  50. public function setExecutor(ExecutorInterface $executor): self
  51. {
  52. $this->executor = $executor;
  53. return $this;
  54. }
  55. public function addSchemaBuilder(string $name, Closure $builder): self
  56. {
  57. $this->schemas[$name] = $builder;
  58. return $this;
  59. }
  60. public function addSchema(string $name, Schema $schema): self
  61. {
  62. $this->schemas[$name] = $schema;
  63. return $this;
  64. }
  65. public function getSchema(string $name = null): Schema
  66. {
  67. if (empty($this->schemas)) {
  68. throw new RuntimeException('At least one schema should be declare.');
  69. }
  70. if (null === $name) {
  71. $name = isset($this->schemas['default']) ? 'default' : array_key_first($this->schemas);
  72. }
  73. if (!isset($this->schemas[$name])) {
  74. throw new NotFoundHttpException(sprintf('Could not found "%s" schema.', $name));
  75. }
  76. $schema = $this->schemas[$name];
  77. if (is_callable($schema)) {
  78. $schema = $schema();
  79. $this->addSchema((string) $name, $schema);
  80. }
  81. return $schema;
  82. }
  83. public function getSchemasNames(): array
  84. {
  85. return array_keys($this->schemas);
  86. }
  87. public function setMaxQueryDepth(int $maxQueryDepth): void
  88. {
  89. /** @var QueryDepth $queryDepth */
  90. $queryDepth = DocumentValidator::getRule(QueryDepth::class);
  91. $queryDepth->setMaxQueryDepth($maxQueryDepth);
  92. }
  93. public function setMaxQueryComplexity(int $maxQueryComplexity): void
  94. {
  95. /** @var QueryComplexity $queryComplexity */
  96. $queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
  97. $queryComplexity->setMaxQueryComplexity($maxQueryComplexity);
  98. }
  99. public function enableIntrospectionQuery(): void
  100. {
  101. DocumentValidator::addRule(new DisableIntrospection(DisableIntrospection::DISABLED));
  102. }
  103. public function disableIntrospectionQuery(): void
  104. {
  105. DocumentValidator::addRule(new DisableIntrospection());
  106. }
  107. /**
  108. * @param array|ArrayObject|object|null $rootValue
  109. */
  110. public function execute(?string $schemaName, array $request, $rootValue = null): ExecutionResult
  111. {
  112. // TODO: remove following if-block in 1.0
  113. if (method_exists(GraphQL::class, 'useExperimentalExecutor')) {
  114. $this->useExperimentalExecutor ? GraphQL::useExperimentalExecutor() : GraphQL::useReferenceExecutor();
  115. }
  116. $schema = $this->getSchema($schemaName);
  117. /** @var string $schemaName */
  118. $schemaName = array_search($schema, $this->schemas);
  119. $executorArgumentsEvent = $this->preExecute(
  120. $schemaName,
  121. $schema,
  122. $request[ParserInterface::PARAM_QUERY] ?? null,
  123. new ArrayObject(),
  124. $rootValue,
  125. $request[ParserInterface::PARAM_VARIABLES],
  126. $request[ParserInterface::PARAM_OPERATION_NAME] ?? null
  127. );
  128. $executorArgumentsEvent->getSchema()->processExtensions();
  129. $result = $this->executor->execute(
  130. $this->promiseAdapter,
  131. $executorArgumentsEvent->getSchema(),
  132. $executorArgumentsEvent->getRequestString(),
  133. $executorArgumentsEvent->getRootValue(),
  134. $executorArgumentsEvent->getContextValue(),
  135. $executorArgumentsEvent->getVariableValue(),
  136. $executorArgumentsEvent->getOperationName(),
  137. $this->defaultFieldResolver
  138. );
  139. $result = $this->postExecute($result, $executorArgumentsEvent);
  140. return $result;
  141. }
  142. /**
  143. * @param mixed $rootValue
  144. */
  145. private function preExecute(
  146. string $schemaName,
  147. Schema $schema,
  148. string $requestString,
  149. ArrayObject $contextValue,
  150. $rootValue = null,
  151. ?array $variableValue = null,
  152. ?string $operationName = null
  153. ): ExecutorArgumentsEvent {
  154. // @phpstan-ignore-next-line (only for Symfony 4.4)
  155. $this->dispatcher->dispatch(new ExecutorContextEvent($contextValue), Events::EXECUTOR_CONTEXT);
  156. /** @var ExecutorArgumentsEvent $object */
  157. // @phpstan-ignore-next-line (only for Symfony 4.4)
  158. $object = $this->dispatcher->dispatch(
  159. // @phpstan-ignore-next-line
  160. ExecutorArgumentsEvent::create($schemaName, $schema, $requestString, $contextValue, $rootValue, $variableValue, $operationName),
  161. Events::PRE_EXECUTOR
  162. );
  163. return $object;
  164. }
  165. private function postExecute(ExecutionResult $result, ExecutorArgumentsEvent $executorArguments): ExecutionResult
  166. {
  167. // @phpstan-ignore-next-line (only for Symfony 4.4)
  168. return $this->dispatcher->dispatch(
  169. new ExecutorResultEvent($result, $executorArguments),
  170. Events::POST_EXECUTOR
  171. )->getResult();
  172. }
  173. }