$errors, * callable(\GraphQL\Error\Error $error): ?array * ): array */ protected $errorsHandler; public function __construct( SchemaBuilder $schemaBuilder, Pipeline $pipeline, EventDispatcher $eventDispatcher, ErrorPool $errorPool, ProvidesValidationRules $providesValidationRules, GraphQLHelper $graphQLHelper, ConfigRepository $configRepository ) { $this->schemaBuilder = $schemaBuilder; $this->pipeline = $pipeline; $this->eventDispatcher = $eventDispatcher; $this->errorPool = $errorPool; $this->providesValidationRules = $providesValidationRules; $this->graphQLHelper = $graphQLHelper; $this->configRepository = $configRepository; } /** * Run one ore more GraphQL operations against the schema. * * @param \GraphQL\Server\OperationParams|array $operationOrOperations * @return array|array> */ public function executeOperationOrOperations($operationOrOperations, GraphQLContext $context): array { $this->eventDispatcher->dispatch( new StartOperationOrOperations($operationOrOperations) ); $resultOrResults = LighthouseUtils::applyEach( /** * @return array */ function (OperationParams $operationParams) use ($context): array { return $this->executeOperation($operationParams, $context); }, $operationOrOperations ); $this->eventDispatcher->dispatch( new EndOperationOrOperations($resultOrResults) ); return $resultOrResults; } /** * Run a single GraphQL operation against the schema and get a result. * * @return array */ public function executeOperation(OperationParams $params, GraphQLContext $context): array { $errors = $this->graphQLHelper->validateOperationParams($params); $query = $params->query; if (! is_string($query) || $query === '') { $errors[] = new RequestError( 'GraphQL Request parameter "query" is required and must not be empty.' ); } if (count($errors) > 0) { $errors = array_map( static function (RequestError $err): Error { return Error::createLocatedError($err); }, $errors ); return $this->serializable( new ExecutionResult(null, $errors) ); } /** @var string $query Otherwise we would have bailed with an error */ $result = $this->executeQuery( $query, $context, $params->variables, null, $params->operation ); return $this->serializable($result); } /** * Execute a GraphQL query on the Lighthouse schema and return the raw result. * * To render the @see ExecutionResult, you will probably want to call `->toArray($debug)` on it, * with $debug being a combination of flags in @see \GraphQL\Error\DebugFlag * * @param string|\GraphQL\Language\AST\DocumentNode $query * @param array|null $variables * @param mixed|null $rootValue */ public function executeQuery( $query, GraphQLContext $context, ?array $variables = [], $rootValue = null, ?string $operationName = null ): ExecutionResult { // TODO make executeQuery require a DocumentNode and move this parsing out of here if (is_string($query)) { try { $query = Parser::parse($query); } catch (SyntaxError $syntaxError) { return new ExecutionResult(null, [$syntaxError]); } } // Building the executable schema might take a while to do, // so we do it before we fire the StartExecution event. // This allows tracking the time for batched queries independently. $schema = $this->schemaBuilder->schema(); $this->eventDispatcher->dispatch( new StartExecution($query, $variables, $operationName, $context) ); $result = GraphQLBase::executeQuery( $schema, $query, $rootValue, $context, $variables, $operationName, null, $this->providesValidationRules->validationRules() ); /** @var array<\Nuwave\Lighthouse\Execution\ExtensionsResponse|null> $extensionsResponses */ $extensionsResponses = (array) $this->eventDispatcher->dispatch( new BuildExtensionsResponse ); foreach ($extensionsResponses as $extensionsResponse) { if ($extensionsResponse !== null) { $result->extensions[$extensionsResponse->key()] = $extensionsResponse->content(); } } foreach ($this->errorPool->errors() as $error) { $result->errors [] = $error; } // Allow listeners to manipulate the result after each resolved query $this->eventDispatcher->dispatch( new ManipulateResult($result) ); $this->eventDispatcher->dispatch( new EndExecution($result) ); $this->cleanUpAfterExecution(); return $result; } protected function cleanUpAfterExecution(): void { BatchLoaderRegistry::forgetInstances(); $this->errorPool->clear(); // TODO remove in v6 BatchLoader::forgetInstances(); } /** * Convert the result to a serializable array. * * @return array */ public function serializable(ExecutionResult $result): array { $result->setErrorsHandler($this->errorsHandler()); return $result->toArray($this->debugFlag()); } /** * @return \Closure( * array<\GraphQL\Error\Error> $errors, * callable(\GraphQL\Error\Error $error): ?array * ): array */ protected function errorsHandler(): \Closure { if (! isset($this->errorsHandler)) { $this->errorsHandler = function (array $errors, callable $formatter): array { // User defined error handlers, implementing \Nuwave\Lighthouse\Execution\ErrorHandler // This allows the user to register multiple handlers and pipe the errors through. $handlers = []; foreach ($this->configRepository->get('lighthouse.error_handlers', []) as $handlerClass) { $handlers [] = app($handlerClass); } return (new Collection($errors)) ->map(function (Error $error) use ($handlers, $formatter): ?array { return $this->pipeline ->send($error) ->through($handlers) ->then(function (?Error $error) use ($formatter): ?array { if ($error === null) { return null; } return $formatter($error); }); }) ->filter() ->all(); }; } return $this->errorsHandler; } protected function debugFlag(): int { // If debugging is set to false globally, do not add GraphQL specific // debugging info either. If it is true, then we fetch the debug // level from the Lighthouse configuration. return $this->configRepository->get('app.debug') ? (int) $this->configRepository->get('lighthouse.debug') : DebugFlag::NONE; } /** * Ensure an executable GraphQL schema is present. * * @deprecated * @see \Nuwave\Lighthouse\Schema\SchemaBuilder::schema() */ public function prepSchema(): Schema { return $this->schemaBuilder->schema(); } }