1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Drupal\graphql\GraphQL\Execution; |
4
|
|
|
|
5
|
|
|
use Drupal\Core\Cache\CacheableDependencyInterface; |
6
|
|
|
use Drupal\Core\Cache\CacheableMetadata; |
7
|
|
|
use Drupal\Core\Cache\CacheBackendInterface; |
8
|
|
|
use Drupal\Core\Cache\Context\CacheContextsManager; |
9
|
|
|
use Drupal\Core\Session\AccountProxyInterface; |
10
|
|
|
use Drupal\graphql\Plugin\SchemaPluginManager; |
11
|
|
|
use Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface; |
12
|
|
|
use GraphQL\Error\Error; |
13
|
|
|
use GraphQL\Error\FormattedError; |
14
|
|
|
use GraphQL\Executor\ExecutionResult; |
15
|
|
|
use GraphQL\Executor\Executor; |
16
|
|
|
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter; |
17
|
|
|
use GraphQL\Executor\Promise\PromiseAdapter; |
18
|
|
|
use GraphQL\Language\AST\DocumentNode; |
19
|
|
|
use GraphQL\Language\Parser; |
20
|
|
|
use GraphQL\Language\Visitor; |
21
|
|
|
use GraphQL\Server\Helper; |
22
|
|
|
use GraphQL\Server\OperationParams; |
23
|
|
|
use GraphQL\Server\RequestError; |
24
|
|
|
use GraphQL\Server\ServerConfig; |
25
|
|
|
use GraphQL\Utils\AST; |
26
|
|
|
use GraphQL\Utils\TypeInfo; |
27
|
|
|
use GraphQL\Utils\Utils; |
28
|
|
|
use GraphQL\Validator\DocumentValidator; |
29
|
|
|
use GraphQL\Validator\Rules\AbstractValidationRule; |
30
|
|
|
use GraphQL\Validator\ValidationContext; |
31
|
|
|
|
32
|
|
|
// TODO: Refactor this and clean it up. |
33
|
|
|
class QueryProcessor { |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* The current user account. |
37
|
|
|
* |
38
|
|
|
* @var \Drupal\Core\Session\AccountProxyInterface |
39
|
|
|
*/ |
40
|
|
|
protected $currentUser; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* The schema plugin manager. |
44
|
|
|
* |
45
|
|
|
* @var \Drupal\graphql\Plugin\SchemaPluginManager |
46
|
|
|
*/ |
47
|
|
|
protected $pluginManager; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* The query provider service. |
51
|
|
|
* |
52
|
|
|
* @var \Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface |
53
|
|
|
*/ |
54
|
|
|
protected $queryProvider; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* The cache backend for caching query results. |
58
|
|
|
* |
59
|
|
|
* @var \Drupal\Core\Cache\CacheBackendInterface |
60
|
|
|
*/ |
61
|
|
|
protected $cacheBackend; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* The cache contexts manager service. |
65
|
|
|
* |
66
|
|
|
* @var \Drupal\Core\Cache\Context\CacheContextsManager |
67
|
|
|
*/ |
68
|
|
|
protected $contextsManager; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* The configuration service parameter. |
72
|
|
|
* |
73
|
|
|
* @var array |
74
|
|
|
*/ |
75
|
|
|
protected $config; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Processor constructor. |
79
|
|
|
* |
80
|
|
|
* @param \Drupal\Core\Session\AccountProxyInterface $currentUser |
81
|
|
|
* The current user. |
82
|
|
|
* @param \Drupal\Core\Cache\Context\CacheContextsManager $contextsManager |
83
|
|
|
* The cache contexts manager service. |
84
|
|
|
* @param \Drupal\graphql\Plugin\SchemaPluginManager $pluginManager |
85
|
|
|
* The schema plugin manager. |
86
|
|
|
* @param \Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface $queryProvider |
87
|
|
|
* The query provider service. |
88
|
|
|
* @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend |
89
|
|
|
* The cache backend for caching query results. |
90
|
|
|
* @param array $config |
91
|
|
|
* The configuration service parameter. |
92
|
|
|
*/ |
93
|
|
|
public function __construct( |
94
|
|
|
AccountProxyInterface $currentUser, |
95
|
|
|
CacheContextsManager $contextsManager, |
96
|
|
|
SchemaPluginManager $pluginManager, |
97
|
|
|
QueryProviderInterface $queryProvider, |
98
|
|
|
CacheBackendInterface $cacheBackend, |
99
|
|
|
array $config |
100
|
|
|
) { |
101
|
|
|
$this->currentUser = $currentUser; |
102
|
|
|
$this->contextsManager = $contextsManager; |
103
|
|
|
$this->pluginManager = $pluginManager; |
104
|
|
|
$this->queryProvider = $queryProvider; |
105
|
|
|
$this->cacheBackend = $cacheBackend; |
106
|
|
|
$this->config = $config; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Processes one or multiple graphql operations. |
111
|
|
|
* |
112
|
|
|
* @param string $schema |
113
|
|
|
* The plugin id of the schema to use. |
114
|
|
|
* @param \GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[] $params |
115
|
|
|
* The graphql operation(s) to execute. |
116
|
|
|
* @param array $globals |
117
|
|
|
* The query context. |
118
|
|
|
* |
119
|
|
|
* @return \Drupal\graphql\GraphQL\Execution\QueryResult|\Drupal\graphql\GraphQL\Execution\QueryResult[] |
120
|
|
|
* The query result. |
121
|
|
|
* |
122
|
|
|
*/ |
123
|
|
|
public function processQuery($schema, $params, array $globals = []) { |
124
|
|
|
// Load the plugin from the schema manager. |
125
|
|
|
$plugin = $this->pluginManager->createInstance($schema); |
126
|
|
|
$schema = $plugin->getSchema(); |
127
|
|
|
|
128
|
|
|
// If the current user has appropriate permissions, allow to bypass |
129
|
|
|
// the secure fields restriction. |
130
|
|
|
$globals['bypass field security'] = $this->currentUser->hasPermission('bypass graphql field security'); |
131
|
|
|
|
132
|
|
|
// Create the server config. |
133
|
|
|
$config = ServerConfig::create(); |
134
|
|
|
$config->setDebug(!empty($this->config['development'])); |
135
|
|
|
$config->setSchema($schema); |
136
|
|
|
$config->setQueryBatching(TRUE); |
137
|
|
|
$config->setContext(function () use ($globals, $plugin) { |
138
|
|
|
// Each document (e.g. in a batch query) gets its own resolve context but |
139
|
|
|
// the global parameters are shared. This allows us to collect the cache |
140
|
|
|
// metadata and contextual values (e.g. inheritance for language) for each |
141
|
|
|
// query separately. |
142
|
|
|
$context = new ResolveContext($globals); |
143
|
|
|
if ($plugin instanceof CacheableDependencyInterface) { |
|
|
|
|
144
|
|
|
$context->addCacheableDependency($plugin)->addCacheTags(['graphql_response']); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
return $context; |
148
|
|
|
}); |
149
|
|
|
|
150
|
|
|
$config->setValidationRules(function (OperationParams $params, DocumentNode $document, $operation) { |
|
|
|
|
151
|
|
|
if (isset($params->queryId)) { |
152
|
|
|
// Assume that pre-parsed documents are already validated. This allows |
153
|
|
|
// us to store pre-validated query documents e.g. for persisted queries |
154
|
|
|
// effectively improving performance by skipping run-time validation. |
155
|
|
|
return []; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
return array_values(DocumentValidator::defaultRules()); |
159
|
|
|
}); |
160
|
|
|
|
161
|
|
|
$config->setPersistentQueryLoader(function ($id, OperationParams $params) { |
162
|
|
|
if ($query = $this->queryProvider->getQuery($id, $params)) { |
163
|
|
|
return $query; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
throw new RequestError(sprintf("Failed to load query map for id '%s'.", $id)); |
167
|
|
|
}); |
168
|
|
|
|
169
|
|
|
if (is_array($params)) { |
170
|
|
|
return $this->executeBatch($config, $params); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
return $this->executeSingle($config, $params); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
178
|
|
|
* @param \GraphQL\Server\OperationParams $params |
179
|
|
|
* |
180
|
|
|
* @return mixed |
181
|
|
|
*/ |
182
|
|
|
public function executeSingle(ServerConfig $config, OperationParams $params) { |
183
|
|
|
$adapter = new SyncPromiseAdapter(); |
184
|
|
|
$result = $this->executeOperationWithReporting($adapter, $config, $params, FALSE); |
185
|
|
|
return $adapter->wait($result); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
190
|
|
|
* @param array $params |
191
|
|
|
* |
192
|
|
|
* @return mixed |
193
|
|
|
*/ |
194
|
|
|
public function executeBatch(ServerConfig $config, array $params) { |
195
|
|
|
$adapter = new SyncPromiseAdapter(); |
196
|
|
|
$result = array_map(function ($params) use ($adapter, $config) { |
197
|
|
|
return $this->executeOperationWithReporting($adapter, $config, $params, TRUE); |
198
|
|
|
}, $params); |
199
|
|
|
|
200
|
|
|
$result = $adapter->all($result); |
201
|
|
|
return $adapter->wait($result); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* @param \GraphQL\Executor\Promise\PromiseAdapter $adapter |
206
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
207
|
|
|
* @param \GraphQL\Server\OperationParams $params |
208
|
|
|
* @param bool $batching |
209
|
|
|
* |
210
|
|
|
* @return \GraphQL\Executor\Promise\Promise |
211
|
|
|
*/ |
212
|
|
|
protected function executeOperationWithReporting(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, $batching = FALSE) { |
213
|
|
|
$result = $this->executeOperation($adapter, $config, $params, $batching); |
214
|
|
|
|
215
|
|
|
// Format and print errors. |
216
|
|
|
return $result->then(function(QueryResult $result) use ($config) { |
217
|
|
|
if ($config->getErrorsHandler()) { |
218
|
|
|
$result->setErrorsHandler($config->getErrorsHandler()); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
if ($config->getErrorFormatter() || $config->getDebug()) { |
222
|
|
|
$result->setErrorFormatter(FormattedError::prepareFormatter($config->getErrorFormatter(), $config->getDebug())); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
return $result; |
226
|
|
|
}); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* @param \GraphQL\Executor\Promise\PromiseAdapter $adapter |
231
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
232
|
|
|
* @param \GraphQL\Server\OperationParams $params |
233
|
|
|
* @param bool $batching |
234
|
|
|
* |
235
|
|
|
* @return \GraphQL\Executor\Promise\Promise |
236
|
|
|
*/ |
237
|
|
|
protected function executeOperation(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, $batching = FALSE) { |
238
|
|
|
try { |
239
|
|
|
if (!$config->getSchema()) { |
240
|
|
|
throw new \LogicException('Missing schema for query execution.'); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
if ($batching && !$config->getQueryBatching()) { |
244
|
|
|
throw new RequestError('Batched queries are not supported by this server.'); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
if ($errors = $this->validateOperationParams($params)) { |
248
|
|
|
return $adapter->createFulfilled(new QueryResult(NULL, $errors)); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
$document = $params->queryId ? $this->loadPersistedQuery($config, $params) : $params->query; |
252
|
|
|
if (!$document instanceof DocumentNode) { |
253
|
|
|
$document = Parser::parse($document); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
// Read the operation type from the document. Subscriptions and mutations |
257
|
|
|
// only work through POST requests. One cannot have mutations and queries |
258
|
|
|
// in the same document, hence this check is sufficient. |
259
|
|
|
$operation = $params->operation; |
260
|
|
|
$type = AST::getOperation($document, $operation); |
261
|
|
|
if ($params->isReadOnly() && $type !== 'query') { |
262
|
|
|
throw new RequestError('GET requests are only supported for query operations.'); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
// If one of the validation rules found any problems, do not resolve the |
266
|
|
|
// query and bail out early instead. |
267
|
|
|
if ($errors = $this->validateOperation($config, $params, $document)) { |
268
|
|
|
return $adapter->createFulfilled(new QueryResult(NULL, $errors)); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
// Only queries can be cached (mutations and subscriptions can't). |
272
|
|
|
if ($type === 'query') { |
273
|
|
|
return $this->executeCacheableOperation($adapter, $config, $params, $document); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
return $this->executeUncachableOperation($adapter, $config, $params, $document); |
277
|
|
|
} |
278
|
|
|
catch (RequestError $exception) { |
279
|
|
|
return $adapter->createFulfilled(new QueryResult(NULL, [Error::createLocatedError($exception)])); |
280
|
|
|
} |
281
|
|
|
catch (Error $exception) { |
282
|
|
|
return $adapter->createFulfilled(new QueryResult(NULL, [$exception])); |
283
|
|
|
} |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* @param \GraphQL\Executor\Promise\PromiseAdapter $adapter |
288
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
289
|
|
|
* @param \GraphQL\Server\OperationParams $params |
290
|
|
|
* @param \GraphQL\Language\AST\DocumentNode $document |
291
|
|
|
* |
292
|
|
|
* @return \GraphQL\Executor\Promise\Promise|mixed |
293
|
|
|
*/ |
294
|
|
|
protected function executeCacheableOperation(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, DocumentNode $document) { |
295
|
|
|
$contextCacheId = 'ccid:' . $this->cacheIdentifier($params, $document, new CacheableMetadata()); |
296
|
|
|
|
297
|
|
|
if (!$config->getDebug() && ($contextCache = $this->cacheBackend->get($contextCacheId)) && $contexts = $contextCache->data) { |
298
|
|
|
$cacheId = 'cid:' . $this->cacheIdentifier($params, $document, (new CacheableMetadata())->addCacheContexts($contexts)); |
299
|
|
|
if (($cache = $this->cacheBackend->get($cacheId)) && $result = $cache->data) { |
300
|
|
|
return $adapter->createFulfilled($result); |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
$result = $this->doExecuteOperation($adapter, $config, $params, $document); |
305
|
|
|
|
306
|
|
|
return $result->then(function (QueryResult $result) use ($contextCacheId, $params, $document) { |
307
|
|
|
// Write this query into the cache if it is cacheable. |
308
|
|
|
if ($result->getCacheMaxAge() !== 0) { |
309
|
|
|
$cacheId = 'cid:' . $this->cacheIdentifier($params, $document, (new CacheableMetadata())->addCacheContexts($result->getCacheContexts())); |
310
|
|
|
$this->cacheBackend->set($contextCacheId, $result->getCacheContexts(), $result->getCacheMaxAge(), $result->getCacheTags()); |
311
|
|
|
$this->cacheBackend->set($cacheId, $result, $result->getCacheMaxAge(), $result->getCacheTags()); |
312
|
|
|
} |
313
|
|
|
return $result; |
314
|
|
|
}); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* @param \GraphQL\Executor\Promise\PromiseAdapter $adapter |
319
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
320
|
|
|
* @param \GraphQL\Server\OperationParams $params |
321
|
|
|
* @param \GraphQL\Language\AST\DocumentNode $document |
322
|
|
|
* |
323
|
|
|
* @return \GraphQL\Executor\Promise\Promise |
324
|
|
|
*/ |
325
|
|
|
protected function executeUncachableOperation(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, DocumentNode $document) { |
326
|
|
|
$result = $this->doExecuteOperation($adapter, $config, $params, $document); |
327
|
|
|
return $result->then(function (QueryResult $result) { |
328
|
|
|
// Mark the query result as uncacheable. |
329
|
|
|
$result->mergeCacheMaxAge(0); |
330
|
|
|
return $result; |
331
|
|
|
}); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* @param \GraphQL\Executor\Promise\PromiseAdapter $adapter |
336
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
337
|
|
|
* @param \GraphQL\Server\OperationParams $params |
338
|
|
|
* @param \GraphQL\Language\AST\DocumentNode $document |
339
|
|
|
* |
340
|
|
|
* @return \GraphQL\Executor\Promise\Promise |
341
|
|
|
*/ |
342
|
|
|
protected function doExecuteOperation(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, DocumentNode $document) { |
343
|
|
|
$operation = $params->operation; |
344
|
|
|
$variables = $params->variables; |
345
|
|
|
$context = $this->resolveContextValue($config, $params, $document, $operation); |
346
|
|
|
$root = $this->resolveRootValue($config, $params, $document, $operation); |
347
|
|
|
$resolver = $config->getFieldResolver(); |
348
|
|
|
$schema = $config->getSchema(); |
349
|
|
|
|
350
|
|
|
$promise = Executor::promiseToExecute( |
351
|
|
|
$adapter, |
352
|
|
|
$schema, |
353
|
|
|
$document, |
354
|
|
|
$root, |
355
|
|
|
$context, |
356
|
|
|
$variables, |
357
|
|
|
$operation, |
358
|
|
|
$resolver |
359
|
|
|
); |
360
|
|
|
|
361
|
|
|
return $promise->then(function (ExecutionResult $result) use ($context) { |
362
|
|
|
|
363
|
|
|
$metadata = (new CacheableMetadata()) |
364
|
|
|
->addCacheContexts($this->filterCacheContexts($context->getCacheContexts())) |
365
|
|
|
->addCacheTags($context->getCacheTags()) |
366
|
|
|
->setCacheMaxAge($context->getCacheMaxAge()); |
367
|
|
|
|
368
|
|
|
// Do not cache in development mode or if there are any errors. |
369
|
|
|
if ($context->getGlobal('development') || !empty($result->errors)) { |
370
|
|
|
$metadata->setCacheMaxAge(0); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
return new QueryResult($result->data, $result->errors, $result->extensions, $metadata); |
374
|
|
|
}); |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
/** |
378
|
|
|
* @param \GraphQL\Server\OperationParams $params |
379
|
|
|
* |
380
|
|
|
* @return array |
381
|
|
|
*/ |
382
|
|
|
protected function validateOperationParams(OperationParams $params) { |
383
|
|
|
$errors = (new Helper())->validateOperationParams($params); |
384
|
|
|
return array_map(function (RequestError $error) { |
385
|
|
|
return Error::createLocatedError($error, NULL, NULL); |
386
|
|
|
}, $errors); |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
/** |
390
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
391
|
|
|
* @param \GraphQL\Server\OperationParams $params |
392
|
|
|
* @param \GraphQL\Language\AST\DocumentNode $document |
393
|
|
|
* |
394
|
|
|
* @return \GraphQL\Error\Error[] |
395
|
|
|
*/ |
396
|
|
|
protected function validateOperation(ServerConfig $config, OperationParams $params, DocumentNode $document) { |
397
|
|
|
$operation = $params->operation; |
398
|
|
|
// Skip validation if there are no validation rules to be applied. |
399
|
|
|
if (!$rules = $this->resolveValidationRules($config, $params, $document, $operation)) { |
400
|
|
|
return []; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
$schema = $config->getSchema(); |
404
|
|
|
$info = new TypeInfo($schema); |
405
|
|
|
$validation = new ValidationContext($schema, $document, $info); |
406
|
|
|
$visitors = array_values(array_map(function (AbstractValidationRule $rule) use ($validation) { |
407
|
|
|
return $rule($validation); |
408
|
|
|
}, $rules)); |
409
|
|
|
|
410
|
|
|
// Run the query visitor with the prepared validation rules and the cache |
411
|
|
|
// metadata collector and query complexity calculator. |
412
|
|
|
Visitor::visit($document, Visitor::visitWithTypeInfo($info, Visitor::visitInParallel($visitors))); |
413
|
|
|
|
414
|
|
|
// Return any possible errors collected during validation. |
415
|
|
|
return $validation->getErrors(); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
/** |
419
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
420
|
|
|
* @param \GraphQL\Server\OperationParams $params |
421
|
|
|
* @param \GraphQL\Language\AST\DocumentNode $document |
422
|
|
|
* @param $operation |
423
|
|
|
* |
424
|
|
|
* @return mixed |
425
|
|
|
*/ |
426
|
|
View Code Duplication |
protected function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $document, $operation) { |
|
|
|
|
427
|
|
|
$root = $config->getRootValue(); |
428
|
|
|
if (is_callable($root)) { |
429
|
|
|
$root = $root($params, $document, $operation); |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
return $root; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
437
|
|
|
* @param \GraphQL\Server\OperationParams $params |
438
|
|
|
* @param \GraphQL\Language\AST\DocumentNode $document |
439
|
|
|
* @param $operation |
440
|
|
|
* |
441
|
|
|
* @return mixed |
442
|
|
|
*/ |
443
|
|
View Code Duplication |
protected function resolveContextValue(ServerConfig $config, OperationParams $params, DocumentNode $document, $operation) { |
|
|
|
|
444
|
|
|
$context = $config->getContext(); |
445
|
|
|
if (is_callable($context)) { |
446
|
|
|
$context = $context($params, $document, $operation); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
return $context; |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
454
|
|
|
* @param \GraphQL\Server\OperationParams $params |
455
|
|
|
* @param \GraphQL\Language\AST\DocumentNode $document |
456
|
|
|
* @param $operation |
457
|
|
|
* |
458
|
|
|
* @return array |
459
|
|
|
*/ |
460
|
|
|
protected function resolveValidationRules(ServerConfig $config, OperationParams $params, DocumentNode $document, $operation) { |
461
|
|
|
// Allow customizing validation rules per operation: |
462
|
|
|
$rules = $config->getValidationRules(); |
463
|
|
|
if (is_callable($rules)) { |
464
|
|
|
$rules = $rules($params, $document, $operation); |
465
|
|
|
if (!is_array($rules)) { |
466
|
|
|
throw new \LogicException(sprintf("Expecting validation rules to be array or callable returning array, but got: %s", Utils::printSafe($rules))); |
467
|
|
|
} |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
return $rules; |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
/** |
474
|
|
|
* @param \GraphQL\Server\ServerConfig $config |
475
|
|
|
* @param \GraphQL\Server\OperationParams $params |
476
|
|
|
* |
477
|
|
|
* @return mixed |
478
|
|
|
* @throws \GraphQL\Server\RequestError |
479
|
|
|
*/ |
480
|
|
|
protected function loadPersistedQuery(ServerConfig $config, OperationParams $params) { |
481
|
|
|
if (!$loader = $config->getPersistentQueryLoader()) { |
482
|
|
|
throw new RequestError('Persisted queries are not supported by this server.'); |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
$source = $loader($params->queryId, $params); |
486
|
|
|
if (!is_string($source) && !$source instanceof DocumentNode) { |
487
|
|
|
throw new \LogicException(sprintf('The persisted query loader must return query string or instance of %s but got: %s.', DocumentNode::class, Utils::printSafe($source))); |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
return $source; |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
/** |
494
|
|
|
* @param \GraphQL\Language\AST\DocumentNode $document |
495
|
|
|
* |
496
|
|
|
* @return array |
497
|
|
|
*/ |
498
|
|
|
protected function serializeDocument(DocumentNode $document) { |
499
|
|
|
return $this->sanitizeRecursive(AST::toArray($document)); |
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
/** |
503
|
|
|
* @param array $item |
504
|
|
|
* |
505
|
|
|
* @return array |
506
|
|
|
*/ |
507
|
|
|
protected function sanitizeRecursive(array $item) { |
508
|
|
|
unset($item['loc']); |
509
|
|
|
|
510
|
|
|
foreach ($item as &$value) { |
511
|
|
|
if (is_array($value)) { |
512
|
|
|
$value = $this->sanitizeRecursive($value); |
513
|
|
|
} |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
return $item; |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
/** |
520
|
|
|
* @param \GraphQL\Server\OperationParams $params |
521
|
|
|
* @param \GraphQL\Language\AST\DocumentNode $document |
522
|
|
|
* @param \Drupal\Core\Cache\CacheableMetadata $metadata |
523
|
|
|
* |
524
|
|
|
* @return string |
525
|
|
|
*/ |
526
|
|
|
protected function cacheIdentifier(OperationParams $params, DocumentNode $document, CacheableMetadata $metadata) { |
527
|
|
|
// Ignore language contexts since they are handled by graphql internally. |
528
|
|
|
$contexts = $this->filterCacheContexts($metadata->getCacheContexts()); |
529
|
|
|
$keys = $this->contextsManager->convertTokensToKeys($contexts)->getKeys(); |
530
|
|
|
|
531
|
|
|
// Sorting the variables will cause fewer cache vectors. |
532
|
|
|
$variables = $params->variables ?: []; |
533
|
|
|
ksort($variables); |
534
|
|
|
|
535
|
|
|
// Prepend the hash of the serialized document to the cache contexts. |
536
|
|
|
$hash = hash('sha256', json_encode([ |
537
|
|
|
'query' => $this->serializeDocument($document), |
538
|
|
|
'variables' => $variables, |
539
|
|
|
])); |
540
|
|
|
|
541
|
|
|
return implode(':', array_values(array_merge([$hash], $keys))); |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
/** |
545
|
|
|
* Filter unused contexts. |
546
|
|
|
* |
547
|
|
|
* Removes the language contexts from a list of context ids. |
548
|
|
|
* |
549
|
|
|
* @param string[] $contexts |
550
|
|
|
* The list of context id's. |
551
|
|
|
* |
552
|
|
|
* @return string[] |
553
|
|
|
* The filtered list of context id's. |
554
|
|
|
*/ |
555
|
|
|
protected function filterCacheContexts(array $contexts) { |
556
|
|
|
return array_filter($contexts, function ($context) { |
557
|
|
|
return strpos($context, 'languages:') !== 0; |
558
|
|
|
}); |
559
|
|
|
} |
560
|
|
|
} |
561
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.