QueryProcessor   D
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 495
Duplicated Lines 0 %

Importance

Changes 9
Bugs 1 Features 0
Metric Value
wmc 59
eloc 155
c 9
b 1
f 0
dl 0
loc 495
rs 4.08

20 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveContextValue() 0 7 2
A __construct() 0 10 1
A doExecuteOperation() 0 37 5
A sanitizeRecursive() 0 10 3
A executeBatch() 0 8 1
C executeOperation() 0 46 13
A executeOperationWithReporting() 0 14 4
A maxAgeToExpire() 0 3 2
A executeCacheableOperation() 0 23 6
A resolveRootValue() 0 7 2
A processQuery() 0 10 2
A serializeDocument() 0 2 1
A filterCacheContexts() 0 3 1
A resolveValidationRules() 0 11 3
A validateOperationParams() 0 5 1
A cacheIdentifier() 0 16 2
A executeUncachableOperation() 0 6 1
A validateOperation() 0 25 4
A loadPersistedQuery() 0 11 4
A executeSingle() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like QueryProcessor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryProcessor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Drupal\graphql\GraphQL\Execution;
4
5
use Drupal\Core\Cache\Cache;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Cache\Cache was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Drupal\Core\Cache\CacheableMetadata;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Cache\CacheableMetadata was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Drupal\Core\Cache\CacheBackendInterface;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Cache\CacheBackendInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Drupal\Core\Cache\Context\CacheContextsManager;
0 ignored issues
show
Bug introduced by
The type Drupal\Core\Cache\Context\CacheContextsManager was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Drupal\graphql\GraphQL\Cache\CacheableRequestError;
10
use Drupal\graphql\Plugin\SchemaPluginManager;
11
use GraphQL\Error\Error;
12
use GraphQL\Error\FormattedError;
13
use GraphQL\Executor\ExecutionResult;
14
use GraphQL\Executor\Executor;
15
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
16
use GraphQL\Executor\Promise\PromiseAdapter;
17
use GraphQL\Language\AST\DocumentNode;
18
use GraphQL\Language\Parser;
19
use GraphQL\Language\Visitor;
20
use GraphQL\Server\Helper;
21
use GraphQL\Server\OperationParams;
22
use GraphQL\Server\RequestError;
23
use GraphQL\Server\ServerConfig;
24
use GraphQL\Utils\AST;
25
use GraphQL\Utils\TypeInfo;
26
use GraphQL\Utils\Utils;
27
use GraphQL\Validator\Rules\AbstractValidationRule;
28
use GraphQL\Validator\Rules\QueryComplexity;
29
use GraphQL\Validator\ValidationContext;
30
use Symfony\Component\HttpFoundation\RequestStack;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\HttpFoundation\RequestStack was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
31
32
/**
33
 * Query processor class.
34
 */
35
class QueryProcessor {
36
37
  /**
38
   * The schema plugin manager.
39
   *
40
   * @var \Drupal\graphql\Plugin\SchemaPluginManager
41
   */
42
  protected $pluginManager;
43
44
  /**
45
   * The cache backend for caching query results.
46
   *
47
   * @var \Drupal\Core\Cache\CacheBackendInterface
48
   */
49
  protected $cacheBackend;
50
51
  /**
52
   * The cache contexts manager service.
53
   *
54
   * @var \Drupal\Core\Cache\Context\CacheContextsManager
55
   */
56
  protected $contextsManager;
57
58
  /**
59
   * The request stack.
60
   *
61
   * @var \Symfony\Component\HttpFoundation\RequestStack
62
   */
63
  protected $requestStack;
64
65
  /**
66
   * Processor constructor.
67
   *
68
   * @param \Drupal\Core\Cache\Context\CacheContextsManager $contextsManager
69
   *   The cache contexts manager service.
70
   * @param \Drupal\graphql\Plugin\SchemaPluginManager $pluginManager
71
   *   The schema plugin manager.
72
   * @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend
73
   *   The cache backend for caching query results.
74
   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
75
   *   The request stack.
76
   */
77
  public function __construct(
78
    CacheContextsManager $contextsManager,
79
    SchemaPluginManager $pluginManager,
80
    CacheBackendInterface $cacheBackend,
81
    RequestStack $requestStack
82
  ) {
83
    $this->contextsManager = $contextsManager;
84
    $this->pluginManager = $pluginManager;
85
    $this->cacheBackend = $cacheBackend;
86
    $this->requestStack = $requestStack;
87
  }
88
89
  /**
90
   * Processes one or multiple graphql operations.
91
   *
92
   * @param string $schema
93
   *   The plugin id of the schema to use.
94
   * @param \GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[] $params
95
   *   The graphql operation(s) to execute.
96
   *
97
   * @return \Drupal\graphql\GraphQL\Execution\QueryResult|\Drupal\graphql\GraphQL\Execution\QueryResult[]
98
   *   The query result.
99
   *
100
   * @throws \Drupal\Component\Plugin\Exception\PluginException
101
   */
102
  public function processQuery($schema, $params) {
103
    // Load the plugin from the schema manager.
104
    $plugin = $this->pluginManager->createInstance($schema);
105
    $config = $plugin->getServer();
106
107
    if (is_array($params)) {
108
      return $this->executeBatch($config, $params);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->executeBatch($config, $params) returns the type GraphQL\Executor\ExecutionResult which is incompatible with the documented return type Drupal\graphql\GraphQL\E...L\Execution\QueryResult.
Loading history...
109
    }
110
111
    return $this->executeSingle($config, $params);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->executeSingle($config, $params) returns the type GraphQL\Executor\ExecutionResult which is incompatible with the documented return type Drupal\graphql\GraphQL\E...L\Execution\QueryResult.
Loading history...
112
  }
113
114
  /**
115
   * @param \GraphQL\Server\ServerConfig $config
116
   * @param \GraphQL\Server\OperationParams $params
117
   *
118
   * @return mixed
119
   */
120
  public function executeSingle(ServerConfig $config, OperationParams $params) {
121
    $adapter = new SyncPromiseAdapter();
122
    $result = $this->executeOperationWithReporting($adapter, $config, $params, FALSE);
123
    return $adapter->wait($result);
124
  }
125
126
  /**
127
   * @param \GraphQL\Server\ServerConfig $config
128
   * @param array $params
129
   *
130
   * @return mixed
131
   */
132
  public function executeBatch(ServerConfig $config, array $params) {
133
    $adapter = new SyncPromiseAdapter();
134
    $result = array_map(function ($params) use ($adapter, $config) {
135
      return $this->executeOperationWithReporting($adapter, $config, $params, TRUE);
136
    }, $params);
137
138
    $result = $adapter->all($result);
139
    return $adapter->wait($result);
140
  }
141
142
  /**
143
   * @param \GraphQL\Executor\Promise\PromiseAdapter $adapter
144
   * @param \GraphQL\Server\ServerConfig $config
145
   * @param \GraphQL\Server\OperationParams $params
146
   * @param bool $batching
147
   *
148
   * @return \GraphQL\Executor\Promise\Promise
149
   */
150
  protected function executeOperationWithReporting(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, $batching = FALSE) {
151
    $result = $this->executeOperation($adapter, $config, $params, $batching);
152
153
    // Format and print errors.
154
    return $result->then(function (QueryResult $result) use ($config) {
155
      if ($config->getErrorsHandler()) {
156
        $result->setErrorsHandler($config->getErrorsHandler());
157
      }
158
159
      if ($config->getErrorFormatter() || $config->getDebugFlag()) {
160
        $result->setErrorFormatter(FormattedError::prepareFormatter($config->getErrorFormatter(), $config->getDebugFlag()));
161
      }
162
163
      return $result;
164
    });
165
  }
166
167
  /**
168
   * @param \GraphQL\Executor\Promise\PromiseAdapter $adapter
169
   * @param \GraphQL\Server\ServerConfig $config
170
   * @param \GraphQL\Server\OperationParams $params
171
   * @param bool $batching
172
   *
173
   * @return \GraphQL\Executor\Promise\Promise
174
   */
175
  protected function executeOperation(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, $batching = FALSE) {
176
    try {
177
      if (!$config->getSchema()) {
178
        throw new Error('Missing schema for query execution.');
179
      }
180
181
      if ($batching && !$config->getQueryBatching()) {
182
        throw new RequestError('Batched queries are not supported by this server.');
183
      }
184
185
      if ($errors = $this->validateOperationParams($params)) {
186
        return $adapter->createFulfilled(new QueryResult(NULL, $errors));
187
      }
188
189
      $persisted = isset($params->queryId);
190
      $document = $persisted ? $this->loadPersistedQuery($config, $params) : $params->query;
191
      if (!$document instanceof DocumentNode) {
192
        $document = Parser::parse($document);
193
      }
194
195
      // Read the operation type from the document. Subscriptions and mutations
196
      // only work through POST requests. One cannot have mutations and queries
197
      // in the same document, hence this check is sufficient.
198
      $operation = $params->operation;
199
      $type = AST::getOperationAST($document, $operation);
200
      if ($params->isReadOnly() && $type->operation !== 'query') {
201
        throw new RequestError('GET requests are only supported for query operations.');
202
      }
203
204
      // Only queries can be cached (mutations and subscriptions can't).
205
      if ($type->operation === 'query') {
206
        return $this->executeCacheableOperation($adapter, $config, $params, $document, !$persisted);
207
      }
208
209
      return $this->executeUncachableOperation($adapter, $config, $params, $document, !$persisted);
210
    }
211
    catch (CacheableRequestError $exception) {
212
      return $adapter->createFulfilled(
213
        new QueryResult(NULL, [Error::createLocatedError($exception)], [], $exception)
214
          );
215
    }
216
    catch (RequestError $exception) {
217
      return $adapter->createFulfilled(new QueryResult(NULL, [Error::createLocatedError($exception)]));
218
    }
219
    catch (Error $exception) {
220
      return $adapter->createFulfilled(new QueryResult(NULL, [$exception]));
221
    }
222
  }
223
224
  /**
225
   * @param \GraphQL\Executor\Promise\PromiseAdapter $adapter
226
   * @param \GraphQL\Server\ServerConfig $config
227
   * @param \GraphQL\Server\OperationParams $params
228
   * @param \GraphQL\Language\AST\DocumentNode $document
229
   * @param bool $validate
230
   *
231
   * @return \GraphQL\Executor\Promise\Promise|mixed
232
   */
233
  protected function executeCacheableOperation(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, DocumentNode $document, $validate = TRUE) {
234
    $contextCacheId = 'ccid:' . $this->cacheIdentifier($params, $document);
235
    if (!$config->getDebugFlag() && $contextCache = $this->cacheBackend->get($contextCacheId)) {
236
      $contexts = $contextCache->data ?: [];
237
      $cid = 'cid:' . $this->cacheIdentifier($params, $document, $contexts);
238
      if ($cache = $this->cacheBackend->get($cid)) {
239
        return $adapter->createFulfilled($cache->data);
240
      }
241
    }
242
243
    $result = $this->doExecuteOperation($adapter, $config, $params, $document, $validate);
244
    return $result->then(function (QueryResult $result) use ($contextCacheId, $params, $document) {
245
      // Write this query into the cache if it is cacheable.
246
      if ($result->getCacheMaxAge() !== 0) {
247
        $contexts = $result->getCacheContexts();
248
        $expire = $this->maxAgeToExpire($result->getCacheMaxAge());
249
        $tags = $result->getCacheTags();
250
        $cid = 'cid:' . $this->cacheIdentifier($params, $document, $contexts);
251
        $this->cacheBackend->set($contextCacheId, $contexts, $expire, $tags);
252
        $this->cacheBackend->set($cid, $result, $expire, $tags);
253
      }
254
255
      return $result;
256
    });
257
  }
258
259
  /**
260
   * @param \GraphQL\Executor\Promise\PromiseAdapter $adapter
261
   * @param \GraphQL\Server\ServerConfig $config
262
   * @param \GraphQL\Server\OperationParams $params
263
   * @param \GraphQL\Language\AST\DocumentNode $document
264
   * @param bool $validate
265
   *
266
   * @return \GraphQL\Executor\Promise\Promise
267
   */
268
  protected function executeUncachableOperation(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, DocumentNode $document, $validate = TRUE) {
269
    $result = $this->doExecuteOperation($adapter, $config, $params, $document, $validate);
270
    return $result->then(function (QueryResult $result) {
271
      // Mark the query result as uncacheable.
272
      $result->mergeCacheMaxAge(0);
273
      return $result;
274
    });
275
  }
276
277
  /**
278
   * @param \GraphQL\Executor\Promise\PromiseAdapter $adapter
279
   * @param \GraphQL\Server\ServerConfig $config
280
   * @param \GraphQL\Server\OperationParams $params
281
   * @param \GraphQL\Language\AST\DocumentNode $document
282
   * @param bool $validate
283
   *
284
   * @return \GraphQL\Executor\Promise\Promise
285
   */
286
  protected function doExecuteOperation(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, DocumentNode $document, $validate = TRUE) {
287
    // If one of the validation rules found any problems, do not resolve the
288
    // query and bail out early instead.
289
    if ($validate && $errors = $this->validateOperation($config, $params, $document)) {
290
      return $adapter->createFulfilled(new QueryResult(NULL, $errors));
291
    }
292
293
    $operation = $params->operation;
294
    $variables = $params->variables;
295
    $context = $this->resolveContextValue($config, $params, $document, $operation);
296
    $root = $this->resolveRootValue($config, $params, $document, $operation);
297
    $resolver = $config->getFieldResolver();
298
    $schema = $config->getSchema();
299
300
    $promise = Executor::promiseToExecute(
301
      $adapter,
302
      $schema,
0 ignored issues
show
Bug introduced by
It seems like $schema can also be of type null; however, parameter $schema of GraphQL\Executor\Executor::promiseToExecute() does only seem to accept GraphQL\Type\Schema, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

302
      /** @scrutinizer ignore-type */ $schema,
Loading history...
303
      $document,
304
      $root,
305
      $context,
306
      $variables,
307
      $operation,
308
      $resolver
309
    );
310
311
    return $promise->then(function (ExecutionResult $result) use ($context) {
312
      $metadata = (new CacheableMetadata())
313
        ->addCacheContexts($context->getCacheContexts())
314
        ->addCacheTags($context->getCacheTags())
315
        ->setCacheMaxAge($context->getCacheMaxAge());
316
317
      // Do not cache in development mode or if there are any errors.
318
      if ($context->getGlobal('development') || !empty($result->errors)) {
319
        $metadata->setCacheMaxAge(0);
320
      }
321
322
      return new QueryResult($result->data, $result->errors, $result->extensions, $metadata);
323
    });
324
  }
325
326
  /**
327
   * @param \GraphQL\Server\OperationParams $params
328
   *
329
   * @return array
330
   */
331
  protected function validateOperationParams(OperationParams $params) {
332
    $errors = (new Helper())->validateOperationParams($params);
333
    return array_map(function (RequestError $error) {
334
      return Error::createLocatedError($error, NULL, NULL);
335
    }, $errors);
336
  }
337
338
  /**
339
   * @param \GraphQL\Server\ServerConfig $config
340
   * @param \GraphQL\Server\OperationParams $params
341
   * @param \GraphQL\Language\AST\DocumentNode $document
342
   *
343
   * @return \GraphQL\Error\Error[]
344
   * @throws \Exception
345
   */
346
  protected function validateOperation(ServerConfig $config, OperationParams $params, DocumentNode $document) {
347
    $operation = $params->operation;
348
    // Skip validation if there are no validation rules to be applied.
349
    if (!$rules = $this->resolveValidationRules($config, $params, $document, $operation)) {
350
      return [];
351
    }
352
353
    $schema = $config->getSchema();
354
    $info = new TypeInfo($schema);
0 ignored issues
show
Bug introduced by
It seems like $schema can also be of type null; however, parameter $schema of GraphQL\Utils\TypeInfo::__construct() does only seem to accept GraphQL\Type\Schema, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

354
    $info = new TypeInfo(/** @scrutinizer ignore-type */ $schema);
Loading history...
355
    $validation = new ValidationContext($schema, $document, $info);
0 ignored issues
show
Bug introduced by
It seems like $schema can also be of type null; however, parameter $schema of GraphQL\Validator\ValidationContext::__construct() does only seem to accept GraphQL\Type\Schema, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

355
    $validation = new ValidationContext(/** @scrutinizer ignore-type */ $schema, $document, $info);
Loading history...
356
    $visitors = array_values(array_map(function (AbstractValidationRule $rule) use ($validation, $params) {
357
      // Set current variable values for QueryComplexity validation rule case.
358
      // @see \GraphQL\GraphQL::promiseToExecute for equivalent
359
      if ($rule instanceof QueryComplexity && !empty($params->variables)) {
360
        $rule->setRawVariableValues($params->variables);
361
      }
362
      return $rule($validation);
363
    }, $rules));
364
365
    // Run the query visitor with the prepared validation rules and the cache
366
    // metadata collector and query complexity calculator.
367
    Visitor::visit($document, Visitor::visitWithTypeInfo($info, Visitor::visitInParallel($visitors)));
368
369
    // Return any possible errors collected during validation.
370
    return $validation->getErrors();
371
  }
372
373
  /**
374
   * @param \GraphQL\Server\ServerConfig $config
375
   * @param \GraphQL\Server\OperationParams $params
376
   * @param \GraphQL\Language\AST\DocumentNode $document
377
   * @param $operation
378
   *
379
   * @return mixed
380
   */
381
  protected function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $document, $operation) {
382
    $root = $config->getRootValue();
383
    if (is_callable($root)) {
384
      $root = $root($params, $document, $operation);
385
    }
386
387
    return $root;
388
  }
389
390
  /**
391
   * @param \GraphQL\Server\ServerConfig $config
392
   * @param \GraphQL\Server\OperationParams $params
393
   * @param \GraphQL\Language\AST\DocumentNode $document
394
   * @param $operation
395
   *
396
   * @return mixed
397
   */
398
  protected function resolveContextValue(ServerConfig $config, OperationParams $params, DocumentNode $document, $operation) {
399
    $context = $config->getContext();
400
    if (is_callable($context)) {
401
      $context = $context($params, $document, $operation);
402
    }
403
404
    return $context;
405
  }
406
407
  /**
408
   * @param \GraphQL\Server\ServerConfig $config
409
   * @param \GraphQL\Server\OperationParams $params
410
   * @param \GraphQL\Language\AST\DocumentNode $document
411
   * @param $operation
412
   *
413
   * @return array
414
   * @throws \GraphQL\Server\RequestError
415
   */
416
  protected function resolveValidationRules(ServerConfig $config, OperationParams $params, DocumentNode $document, $operation) {
417
    // Allow customizing validation rules per operation:
418
    $rules = $config->getValidationRules();
419
    if (is_callable($rules)) {
420
      $rules = $rules($params, $document, $operation);
421
      if (!is_array($rules)) {
422
        throw new RequestError(sprintf("Expecting validation rules to be array or callable returning array, but got: %s", Utils::printSafe($rules)));
423
      }
424
    }
425
426
    return $rules;
427
  }
428
429
  /**
430
   * @param \GraphQL\Server\ServerConfig $config
431
   * @param \GraphQL\Server\OperationParams $params
432
   *
433
   * @return mixed
434
   * @throws \GraphQL\Server\RequestError
435
   */
436
  protected function loadPersistedQuery(ServerConfig $config, OperationParams $params) {
437
    if (!$loader = $config->getPersistentQueryLoader()) {
438
      throw new RequestError('Persisted queries are not supported by this server.');
439
    }
440
441
    $source = $loader($params->queryId, $params);
442
    if (!is_string($source) && !$source instanceof DocumentNode) {
443
      throw new RequestError(sprintf('The persisted query loader must return query string or instance of %s but got: %s.', DocumentNode::class, Utils::printSafe($source)));
444
    }
445
446
    return $source;
447
  }
448
449
  /**
450
   * @param \GraphQL\Language\AST\DocumentNode $document
451
   *
452
   * @return array
453
   */
454
  protected function serializeDocument(DocumentNode $document) {
455
    return $this->sanitizeRecursive(AST::toArray($document));
456
  }
457
458
  /**
459
   * @param array $item
460
   *
461
   * @return array
462
   */
463
  protected function sanitizeRecursive(array $item) {
464
    unset($item['loc']);
465
466
    foreach ($item as &$value) {
467
      if (is_array($value)) {
468
        $value = $this->sanitizeRecursive($value);
469
      }
470
    }
471
472
    return $item;
473
  }
474
475
  /**
476
   * @param \GraphQL\Server\OperationParams $params
477
   * @param \GraphQL\Language\AST\DocumentNode $document
478
   * @param array $contexts
479
   *
480
   * @return string
481
   */
482
  protected function cacheIdentifier(OperationParams $params, DocumentNode $document, array $contexts = []) {
483
    // Ignore language contexts since they are handled by graphql internally.
484
    $contexts = $contexts;
485
    $keys = $this->contextsManager->convertTokensToKeys($contexts)->getKeys();
486
487
    // Sorting the variables will cause fewer cache vectors.
488
    $variables = $params->variables ?: [];
489
    ksort($variables);
490
491
    // Prepend the hash of the serialized document to the cache contexts.
492
    $hash = hash('sha256', json_encode([
493
      'query' => $this->serializeDocument($document),
494
      'variables' => $variables,
495
    ]));
496
497
    return implode(':', array_values(array_merge([$hash], $keys)));
498
  }
499
500
  /**
501
   * Filter unused contexts.
502
   *
503
   * Removes the language contexts from a list of context ids.
504
   *
505
   * @param string[] $contexts
506
   *   The list of context id's.
507
   *
508
   * @return string[]
509
   *   The filtered list of context id's.
510
   */
511
  protected function filterCacheContexts(array $contexts) {
512
    return array_filter($contexts, function ($context) {
513
      return strpos($context, 'languages:') !== 0;
514
    });
515
  }
516
517
  /**
518
   * Maps a cache max age value to an "expire" value for the Cache API.
519
   *
520
   * @param int $maxAge
521
   *
522
   * @return int
523
   *   A corresponding "expire" value.
524
   *
525
   * @see \Drupal\Core\Cache\CacheBackendInterface::set()
526
   */
527
  protected function maxAgeToExpire($maxAge) {
528
    $time = $this->requestStack->getMainRequest()->server->get('REQUEST_TIME');
529
    return ($maxAge === Cache::PERMANENT) ? Cache::PERMANENT : (int) $time + $maxAge;
530
  }
531
532
}
533