1
<?php
2

3
namespace Nuwave\Lighthouse;
4

5
use GraphQL\Error\DebugFlag;
6
use GraphQL\Error\Error;
7
use GraphQL\Executor\ExecutionResult;
8
use GraphQL\GraphQL as GraphQLBase;
9
use GraphQL\Server\Helper;
10
use GraphQL\Server\OperationParams;
11
use GraphQL\Server\RequestError;
12
use GraphQL\Type\Schema;
13
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
14
use Illuminate\Http\Request;
15
use Illuminate\Pipeline\Pipeline;
16
use Illuminate\Support\Collection;
17
use Laragraph\Utils\RequestParser;
18
use Nuwave\Lighthouse\Events\BuildExtensionsResponse;
19
use Nuwave\Lighthouse\Events\ManipulateResult;
20
use Nuwave\Lighthouse\Events\StartExecution;
21
use Nuwave\Lighthouse\Execution\DataLoader\BatchLoader;
22
use Nuwave\Lighthouse\Execution\DataLoader\BatchLoaderRegistry;
23
use Nuwave\Lighthouse\Execution\ErrorPool;
24
use Nuwave\Lighthouse\Schema\AST\ASTBuilder;
25
use Nuwave\Lighthouse\Schema\SchemaBuilder;
26
use Nuwave\Lighthouse\Support\Contracts\CreatesContext;
27
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
28
use Nuwave\Lighthouse\Support\Contracts\ProvidesValidationRules;
29
use Nuwave\Lighthouse\Support\Utils as LighthouseUtils;
30

31
class GraphQL
32
{
33
    /**
34
     * @var \GraphQL\Type\Schema
35
     */
36
    protected $executableSchema;
37

38
    /**
39
     * @var \Nuwave\Lighthouse\Schema\SchemaBuilder
40
     */
41
    protected $schemaBuilder;
42

43
    /**
44
     * @var \Illuminate\Pipeline\Pipeline
45
     */
46
    protected $pipeline;
47

48
    /**
49
     * @var \Illuminate\Contracts\Events\Dispatcher
50
     */
51
    protected $eventDispatcher;
52

53
    /**
54
     * @var \Nuwave\Lighthouse\Schema\AST\ASTBuilder
55
     */
56
    protected $astBuilder;
57

58
    /**
59
     * @var \Nuwave\Lighthouse\Support\Contracts\CreatesContext
60
     */
61
    protected $createsContext;
62

63
    /**
64
     * @var \Nuwave\Lighthouse\Execution\ErrorPool
65
     */
66
    protected $errorPool;
67

68
    /**
69
     * @var \Nuwave\Lighthouse\Support\Contracts\ProvidesValidationRules
70
     */
71
    protected $providesValidationRules;
72

73 1
    public function __construct(
74
        SchemaBuilder $schemaBuilder,
75
        Pipeline $pipeline,
76
        EventDispatcher $eventDispatcher,
77
        ASTBuilder $astBuilder,
78
        CreatesContext $createsContext,
79
        ErrorPool $errorPool,
80
        ProvidesValidationRules $providesValidationRules
81
    ) {
82 1
        $this->schemaBuilder = $schemaBuilder;
83 1
        $this->pipeline = $pipeline;
84 1
        $this->eventDispatcher = $eventDispatcher;
85 1
        $this->astBuilder = $astBuilder;
86 1
        $this->createsContext = $createsContext;
87 1
        $this->errorPool = $errorPool;
88 1
        $this->providesValidationRules = $providesValidationRules;
89
    }
90

91
    /**
92
     * @return array<string, mixed>|array<int, array<string, mixed>>
93
     */
94 1
    public function executeRequest(Request $request, RequestParser $requestParser, Helper $graphQLHelper): array
95
    {
96 1
        $operationParams = $requestParser->parseRequest($request);
97

98 1
        return LighthouseUtils::applyEach(
99
            /**
100
             * @return array<string, mixed>
101
             */
102
            function (OperationParams $operationParams) use ($graphQLHelper): array {
103 1
                $errors = $graphQLHelper->validateOperationParams($operationParams);
104

105 1
                if (count($errors) > 0) {
106 1
                    $errors = array_map(
107
                        static function (RequestError $err): Error {
108 1
                            return Error::createLocatedError($err, null, null);
109 1
                        },
110
                        $errors
111
                    );
112

113 1
                    return $this->applyDebugSettings(
114 1
                        new ExecutionResult(null, $errors)
115
                    );
116
                }
117

118 1
                return $this->executeOperation($operationParams);
119 1
            },
120
            $operationParams
121
        );
122
    }
123

124
    /**
125
     * Run a single GraphQL operation against the schema and get a result.
126
     *
127
     * @return array<string, mixed>
128
     */
129 1
    public function executeOperation(OperationParams $params): array
130
    {
131 1
        $result = $this->executeQuery(
132 1
            $params->query,
133 1
            $this->createsContext->generate(
134 1
                app('request')
135
            ),
136 1
            $params->variables,
137 1
            null,
138 1
            $params->operation
139
        );
140

141 1
        return $this->applyDebugSettings($result);
142
    }
143

144
    /**
145
     * Apply the debug settings from the config and get the result as an array.
146
     *
147
     * @return array<string, mixed>
148
     */
149 1
    public function applyDebugSettings(ExecutionResult $result): array
150
    {
151
        // If debugging is set to false globally, do not add GraphQL specific
152
        // debugging info either. If it is true, then we fetch the debug
153
        // level from the Lighthouse configuration.
154 1
        return $result->toArray(
155 1
            config('app.debug')
156 1
                ? (int) config('lighthouse.debug')
157 1
                : DebugFlag::NONE
158
        );
159
    }
160

161
    /**
162
     * Execute a GraphQL query on the Lighthouse schema and return the raw result.
163
     *
164
     * To render the @see ExecutionResult, you will probably want to call `->toArray($debug)` on it,
165
     * with $debug being a combination of flags in @see \GraphQL\Error\DebugFlag
166
     *
167
     * @param  string|\GraphQL\Language\AST\DocumentNode  $query
168
     * @param  array<mixed>|null  $variables
169
     * @param  mixed|null  $rootValue
170
     */
171 1
    public function executeQuery(
172
        $query,
173
        GraphQLContext $context,
174
        ?array $variables = [],
175
        $rootValue = null,
176
        ?string $operationName = null
177
    ): ExecutionResult {
178
        // Building the executable schema might take a while to do,
179
        // so we do it before we fire the StartExecution event.
180
        // This allows tracking the time for batched queries independently.
181 1
        $this->prepSchema();
182

183 1
        $this->eventDispatcher->dispatch(
184 1
            new StartExecution
185
        );
186

187 1
        $result = GraphQLBase::executeQuery(
188 1
            $this->executableSchema,
189
            $query,
190
            $rootValue,
191
            $context,
192
            $variables,
193
            $operationName,
194 1
            null,
195 1
            $this->providesValidationRules->validationRules()
196
        );
197

198
        /** @var array<\Nuwave\Lighthouse\Execution\ExtensionsResponse|null> $extensionsResponses */
199 1
        $extensionsResponses = (array) $this->eventDispatcher->dispatch(
200 1
            new BuildExtensionsResponse
201
        );
202

203 1
        foreach ($extensionsResponses as $extensionsResponse) {
204 1
            if ($extensionsResponse !== null) {
205 1
                $result->extensions[$extensionsResponse->key()] = $extensionsResponse->content();
206
            }
207
        }
208

209 1
        foreach ($this->errorPool->errors() as $error) {
210 1
            $result->errors [] = $error;
211
        }
212

213 1
        $result->setErrorsHandler(
214
            function (array $errors, callable $formatter): array {
215
                // User defined error handlers, implementing \Nuwave\Lighthouse\Execution\ErrorHandler
216
                // This allows the user to register multiple handlers and pipe the errors through.
217 1
                $handlers = [];
218 1
                foreach (config('lighthouse.error_handlers', []) as $handlerClass) {
219 1
                    $handlers [] = app($handlerClass);
220
                }
221

222 1
                return (new Collection($errors))
223
                    ->map(function (Error $error) use ($handlers, $formatter): ?array {
224 1
                        return $this->pipeline
225 1
                            ->send($error)
226 1
                            ->through($handlers)
227
                            ->then(function (?Error $error) use ($formatter): ?array {
228 1
                                if ($error === null) {
229 0
                                    return null;
230
                                }
231

232 1
                                return $formatter($error);
233 1
                            });
234 1
                    })
235 1
                    ->filter()
236 1
                    ->all();
237
            }
238
        );
239

240
        // Allow listeners to manipulate the result after each resolved query
241 1
        $this->eventDispatcher->dispatch(
242 1
            new ManipulateResult($result)
243
        );
244

245 1
        $this->cleanUp();
246

247 1
        return $result;
248
    }
249

250
    /**
251
     * Ensure an executable GraphQL schema is present.
252
     */
253 1
    public function prepSchema(): Schema
254
    {
255 1
        if (! isset($this->executableSchema)) {
256 1
            $this->executableSchema = $this->schemaBuilder->build(
257 1
                $this->astBuilder->documentAST()
258
            );
259
        }
260

261 1
        return $this->executableSchema;
262
    }
263

264
    /**
265
     * Clean up after executing a query.
266
     */
267 1
    protected function cleanUp(): void
268
    {
269 1
        BatchLoaderRegistry::forgetInstances();
270 1
        $this->errorPool->clear();
271

272
        // TODO remove in v6
273 1
        BatchLoader::forgetInstances();
274
    }
275
}

Read our documentation on viewing source code .

Loading