Completed
Pull Request — 8.x-3.x (#519)
by Sebastian
02:41
created

QueryProcessor::processQuery()   D

Complexity

Conditions 16
Paths 197

Size

Total Lines 83
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 43
nc 197
nop 6
dl 0
loc 83
rs 4.5839
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Drupal\graphql\GraphQL\Execution;
4
5
use Drupal\Core\Cache\Cache;
6
use Drupal\Core\Cache\CacheableDependencyInterface;
7
use Drupal\Core\Cache\CacheableMetadata;
8
use Drupal\Core\Cache\CacheBackendInterface;
9
use Drupal\Core\Cache\Context\CacheContextsManager;
10
use Drupal\Core\Session\AccountProxyInterface;
11
use Drupal\graphql\GraphQL\Execution\Visitor\CacheMetadataVisitor;
12
use Drupal\graphql\GraphQL\Execution\Visitor\MaxComplexityVisitor;
13
use Drupal\graphql\GraphQL\Schema\SchemaLoader;
14
use Symfony\Component\HttpFoundation\RequestStack;
15
use Youshido\GraphQL\Exception\ResolveException;
16
17
class QueryProcessor {
18
19
  /**
20
   * The current user account.
21
   *
22
   * @var \Drupal\Core\Session\AccountProxyInterface
23
   */
24
  protected $currentUser;
25
26
  /**
27
   * The schema loader service.
28
   *
29
   * @var \Drupal\graphql\GraphQL\Schema\SchemaLoader
30
   */
31
  protected $schemaLoader;
32
33
  /**
34
   * The cache contexts manager service.
35
   *
36
   * @var \Drupal\Core\Cache\Context\CacheContextsManager
37
   */
38
  protected $contextsManager;
39
40
  /**
41
   * The cache backend for caching responses.
42
   *
43
   * @var \Drupal\Core\Cache\CacheBackendInterface
44
   */
45
  protected $cacheBackend;
46
47
  /**
48
   * The request stack.
49
   *
50
   * @var \Symfony\Component\HttpFoundation\RequestStack
51
   */
52
  protected $requestStack;
53
54
  /**
55
   * QueryProcessor constructor.
56
   *
57
   * @param \Drupal\graphql\GraphQL\Schema\SchemaLoader $schemaLoader
58
   *   The schema loader service.
59
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
60
   *   The current user.
61
   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
62
   * @param \Drupal\Core\Cache\Context\CacheContextsManager $contextsManager
63
   *   The cache contexts manager service.
64
   * @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend
65
   *   The cache backend for caching response.
66
   */
67
  public function __construct(
68
    SchemaLoader $schemaLoader,
69
    AccountProxyInterface $currentUser,
70
    RequestStack $requestStack,
71
    CacheBackendInterface $cacheBackend,
72
    CacheContextsManager $contextsManager
73
  ) {
74
    $this->currentUser = $currentUser;
75
    $this->schemaLoader = $schemaLoader;
76
    $this->requestStack = $requestStack;
77
    $this->cacheBackend = $cacheBackend;
78
    $this->contextsManager = $contextsManager;
79
  }
80
81
  /**
82
   * Processes a graphql query.
83
   *
84
   * @param string $id
85
   *   The name of the schema to process the query against.
86
   * @param string $query
87
   *   The GraphQL query.
88
   * @param array $variables
89
   *   The query variables.
90
   * @param bool $useCache
91
   *   Whether to use caching.
92
   * @param int|null $maxComplexity
93
   *   The maximum complexity of the query or NULL if any complexity is allowed.
94
   * @param bool $bypassSecurity
95
   *   Bypass field security
96
   *
97
   * @return \Drupal\graphql\GraphQL\Execution\QueryResult .
98
   *   The query result.
99
   */
100
  public function processQuery($id, $query, array $variables = [], $useCache = TRUE, $maxComplexity = NULL, $bypassSecurity = FALSE) {
101
    if (!$schema = $this->schemaLoader->getSchema($id)) {
102
      throw new \InvalidArgumentException(sprintf('Could not load schema %s', [$id]));
103
    }
104
105
    // The processor isolates the parsing and execution of the query.
106
    $processor = new Processor($schema, $query, $variables);
107
    $context = $processor->getExecutionContext();
108
    $container = $context->getContainer();
109
110
    // Set up some parameters in the container.
111
    $secure = $bypassSecurity || $this->currentUser->hasPermission('bypass graphql field security');
112
    $metadata = new CacheableMetadata();
113
    $container->set('secure', $secure);
114
    $container->set('metadata', $metadata);
115
116
    $visitor = new CacheMetadataVisitor();
117
    /** @var \Drupal\Core\Cache\RefinableCacheableDependencyInterface $metadata */
118
    $metadata = $processor->reduceRequest($visitor, function ($result) use ($id) {
119
      $metadata = new CacheableMetadata();
120
      $metadata->addCacheableDependency($result);
121
122
      // Add the global cache metadata and the schema's cache metadata.
123
      $metadata->addCacheableDependency($this->schemaLoader->getCacheMetadata($id));
124
125
      // TODO: Use a cache context that includes the AST instead of the raw values.
126
      $metadata->addCacheContexts(['gql']);
127
      $metadata->addCacheTags(['graphql_response']);
128
129
      return $metadata;
130
    }) ?: new CacheableMetadata();
131
132
    // The cache identifier will later be re-used for writing the cache entry.
133
    $cid = $this->getCacheIdentifier($metadata);
134
    if (!empty($useCache) && $metadata->getCacheMaxAge() !== 0) {
135
      if (($cache = $this->cacheBackend->get($cid)) && $result = $cache->data) {
136
        return $result;
137
      }
138
    }
139
140
    if (!empty($maxComplexity)) {
141
      $visitor = new MaxComplexityVisitor();
142
      $processor->reduceRequest($visitor, function ($result) use ($maxComplexity) {
143
        if ($result > $maxComplexity) {
144
          throw new ResolveException('Maximum complexity exceeded.');
145
        }
146
      });
147
    }
148
149
    // Retrieve the result from the processor.
150
    $data = $processor->resolveRequest();
151
152
    // Add collected cache metadata from the query processor.
153
    if ($container->has('metadata') && ($collected = $container->get('metadata'))) {
154
      if ($collected instanceof CacheableDependencyInterface) {
0 ignored issues
show
Bug introduced by
The class Drupal\Core\Cache\CacheableDependencyInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

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 the composer.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 or require-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 ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
155
        $metadata->addCacheableDependency($collected);
156
      }
157
    }
158
159
    // If we encountered any errors, do not cache anything.
160
    if ($context->hasErrors()) {
161
      $metadata->setCacheMaxAge(0);
162
    }
163
164
    // Build the result object. The keys are only set if they contain data.
165
    $result = new QueryResult(array_filter([
166
      'data' => $data,
167
      'errors' => $processor->getExecutionContext()->getErrorsArray(),
168
    ]), $metadata);
169
170
    // Write the query result into the cache if the cache metadata permits.
171
    if (!empty($useCache) && $metadata->getCacheMaxAge() !== 0) {
172
      $tags = $metadata->getCacheTags();
173
      $expire = $this->maxAgeToExpire($metadata->getCacheMaxAge());
174
175
      // The cache identifier for the cache entry is built based on the
176
      // previously extracted cache contexts from the query visitor. That
177
      // means, that dynamically returned cache contexts have no effect.
178
      $this->cacheBackend->set($cid, $result, $expire, $tags);
179
    }
180
181
    return $result;
182
  }
183
184
  /**
185
   * Maps a max age value to an "expire" value for the Cache API.
186
   *
187
   * @param int $maxAge
188
   *   A max age value.
189
   *
190
   * @return int
191
   *   A corresponding "expire" value.
192
   *
193
   * @see \Drupal\Core\Cache\CacheBackendInterface::set()
194
   */
195 View Code Duplication
  protected function maxAgeToExpire($maxAge) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
196
    if ($maxAge === Cache::PERMANENT) {
197
      return Cache::PERMANENT;
198
    }
199
200
    return (int) $this->requestStack->getMasterRequest()->server->get('REQUEST_TIME') + $maxAge;
201
  }
202
203
  /**
204
   * Generates a cache identifier for the passed cache contexts.
205
   *
206
   * @param \Drupal\Core\Cache\CacheableDependencyInterface $metadata
207
   *   Optional array of cache context tokens.
208
   *
209
   * @return string The generated cache identifier.
210
   *   The generated cache identifier.
211
   */
212
  protected function getCacheIdentifier(CacheableDependencyInterface $metadata) {
213
    $tokens = $metadata->getCacheContexts();
214
    $keys = $this->contextsManager->convertTokensToKeys($tokens)->getKeys();
215
    return implode(':', array_merge(['graphql'], array_values($keys)));
216
  }
217
218
}
219