Completed
Pull Request — 8.x-3.x (#525)
by Sebastian
03:17
created

QueryProcessor   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 317
Duplicated Lines 5.05 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
dl 16
loc 317
rs 9
c 0
b 0
f 0
wmc 35
lcom 1
cbo 12

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
B processQuery() 0 29 3
A executeSingle() 0 5 1
A executeBatch() 0 9 1
C promiseToExecuteOperation() 0 65 14
B promiseToExecute() 0 38 4
A resolveRootValue() 8 8 2
A resolveContextValue() 8 8 2
A resolveValidationRules() 0 12 3
A loadPersistedQuery() 0 12 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Drupal\graphql\GraphQL\Execution;
4
5
use Drupal\Core\Cache\CacheableMetadata;
6
use Drupal\Core\Session\AccountProxyInterface;
7
use Drupal\graphql\GraphQL\Visitors\CacheMetadataCollector;
8
use Drupal\graphql\Plugin\SchemaPluginManager;
9
use Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface;
10
use GraphQL\Error\Error;
11
use GraphQL\Error\FormattedError;
12
use GraphQL\Executor\ExecutionResult;
13
use GraphQL\Executor\Executor;
14
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
15
use GraphQL\Executor\Promise\PromiseAdapter;
16
use GraphQL\Language\AST\DocumentNode;
17
use GraphQL\Language\Parser;
18
use GraphQL\Server\Helper;
19
use GraphQL\Server\OperationParams;
20
use GraphQL\Server\RequestError;
21
use GraphQL\Server\ServerConfig;
22
use GraphQL\Type\Schema;
23
use GraphQL\Utils\AST;
24
use GraphQL\Utils\Utils;
25
use GraphQL\Validator\DocumentValidator;
26
27
class QueryProcessor {
28
29
  /**
30
   * The current user account.
31
   *
32
   * @var \Drupal\Core\Session\AccountProxyInterface
33
   */
34
  protected $currentUser;
35
36
  /**
37
   * The schema plugin manager.
38
   *
39
   * @var \Drupal\graphql\Plugin\SchemaPluginManager
40
   */
41
  protected $pluginManager;
42
43
  /**
44
   * The query provider service.
45
   *
46
   * @var \Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface
47
   */
48
  protected $queryProvider;
49
50
  /**
51
   * Processor constructor.
52
   *
53
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
54
   *   The current user.
55
   * @param \Drupal\graphql\Plugin\SchemaPluginManager $pluginManager
56
   *   The schema plugin manager.
57
   * @param \Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface $queryProvider
58
   *   The query provider service.
59
   */
60
  public function __construct(
61
    AccountProxyInterface $currentUser,
62
    SchemaPluginManager $pluginManager,
63
    QueryProviderInterface $queryProvider
64
  ) {
65
    $this->currentUser = $currentUser;
66
    $this->pluginManager = $pluginManager;
67
    $this->queryProvider = $queryProvider;
68
  }
69
70
  /**
71
   * Processes one or multiple graphql operations.
72
   *
73
   * @param string $schema
74
   *   The plugin id of the schema to use.
75
   * @param \GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[] $params
76
   *   The graphql operation(s) to execute.
77
   * @param array $globals
78
   *   The query context.
79
   *
80
   * @return \Drupal\graphql\GraphQL\Execution\QueryResult|\Drupal\graphql\GraphQL\Execution\QueryResult[]
81
   *   The query result.
82
   *
83
   */
84
  public function processQuery($schema, $params, array $globals = []) {
85
    // Load the plugin from the schema manager.
86
    $plugin = $this->pluginManager->createInstance($schema);
87
    $schema = $plugin->getSchema();
88
89
    // Build the resolve context which is handed to each field resolver.
90
    $context = new ResolveContext($globals);
91
    $debug = $context->getGlobal('development', FALSE);
92
93
    // Create the server config.
94
    $config = ServerConfig::create();
95
    $config->setDebug($debug);
96
    $config->setSchema($schema);
97
    $config->setQueryBatching(TRUE);
98
    $config->setContext($context);
99
    $config->setPersistentQueryLoader(function ($id, OperationParams $params) {
100
      if ($query = $this->queryProvider->getQuery($id, $params)) {
101
        return $query;
102
      }
103
104
      throw new RequestError(sprintf("Failed to load query map for id '%s'.", $id));
105
    });
106
107
    if (is_array($params)) {
108
      return $this->executeBatch($config, $params);
109
    }
110
111
    return $this->executeSingle($config, $params);
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->promiseToExecuteOperation($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->promiseToExecuteOperation($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 promiseToExecuteOperation(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, $batching = FALSE) {
151
    try {
152
      if (!$config->getSchema()) {
153
        throw new \LogicException('Missing schema for query execution.');
154
      }
155
156
      if ($batching && !$config->getQueryBatching()) {
157
        throw new RequestError('Batched queries are not supported by this server.');
158
      }
159
160
      if ($errors = (new Helper())->validateOperationParams($params)) {
161
        $errors = Utils::map($errors, function (RequestError $err) {
162
          return Error::createLocatedError($err, NULL, NULL);
163
        });
164
165
        return $adapter->createFulfilled(new QueryResult(NULL, $errors));
166
      }
167
168
      $variables = $params->variables;
169
      $operation = $params->operation;
170
      $document = $params->queryId ? $this->loadPersistedQuery($config, $params) : $params->query;
171
      if (!$document instanceof DocumentNode) {
172
        $document = Parser::parse($document);
173
      }
174
175
      if ($params->isReadOnly() && AST::getOperation($document, $operation) !== 'query') {
176
        throw new RequestError('GET requests are only supported for query operations.');
177
      }
178
179
      $schema = $config->getSchema();
180
      $resolver = $config->getFieldResolver();
181
      $root = $this->resolveRootValue($config, $params, $document, $operation);
182
      $context = $this->resolveContextValue($config, $params, $document, $operation);
183
      $rules = $this->resolveValidationRules($config, $params, $document, $operation);
184
      $result = $this->promiseToExecute(
185
        $adapter,
186
        $schema,
187
        $document,
188
        $root,
189
        $context,
190
        $variables,
191
        $operation,
192
        $resolver,
193
        $rules
194
      );
195
    }
196
    catch (RequestError $exception) {
197
      $result = $adapter->createFulfilled(new QueryResult(NULL, [Error::createLocatedError($exception)]));
198
    }
199
    catch (Error $exception) {
200
      $result = $adapter->createFulfilled(new QueryResult(NULL, [$exception]));
201
    }
202
203
    return $result->then(function(QueryResult $result) use ($config) {
204
      if ($config->getErrorsHandler()) {
205
        $result->setErrorsHandler($config->getErrorsHandler());
206
      }
207
208
      if ($config->getErrorFormatter() || $config->getDebug()) {
209
        $result->setErrorFormatter(FormattedError::prepareFormatter($config->getErrorFormatter(), $config->getDebug()));
210
      }
211
212
      return $result;
213
    });
214
  }
215
216
  /**
217
   * @param \GraphQL\Executor\Promise\PromiseAdapter $adapter
218
   * @param \GraphQL\Type\Schema $schema
219
   * @param \GraphQL\Language\AST\DocumentNode $document
220
   * @param null $root
221
   * @param null $context
222
   * @param null $variables
223
   * @param null $operation
224
   * @param callable|NULL $resolver
225
   * @param array|NULL $rules
226
   *
227
   * @return \GraphQL\Executor\Promise\Promise
228
   */
229
  protected function promiseToExecute(
230
    PromiseAdapter $adapter,
231
    Schema $schema,
232
    DocumentNode $document,
233
    $root = NULL,
234
    $context = NULL,
235
    $variables = NULL,
236
    $operation = NULL,
237
    callable $resolver = NULL,
238
    array $rules = NULL
239
  ) {
240
    try {
241
      $metadata = new CacheableMetadata();
242
      $visitor = new CacheMetadataCollector($metadata, $variables);
243
      $rules = array_merge($rules ?: DocumentValidator::allRules(), [$visitor]);
244
      if ($errors = DocumentValidator::validate($schema, $document, $rules)) {
245
        return $adapter->createFulfilled(new QueryResult(NULL, $errors));
246
      }
247
248
      // TODO: Implement cache backend lookup with collected cache metadata.
249
250
      return (Executor::promiseToExecute(
251
        $adapter,
252
        $schema,
253
        $document,
254
        $root,
255
        $context,
256
        $variables,
257
        $operation,
258
        $resolver
259
      ))->then(function (ExecutionResult $result) use ($metadata) {
260
        return new QueryResult($result->data, $result->errors, $result->extensions, $metadata);
261
      });
262
    }
263
    catch (Error $exception) {
264
      return $adapter->createFulfilled(new QueryResult(NULL, [$exception]));
265
    }
266
  }
267
268
  /**
269
   * @param \GraphQL\Server\ServerConfig $config
270
   * @param \GraphQL\Server\OperationParams $params
271
   * @param \GraphQL\Language\AST\DocumentNode $document
272
   * @param $operation
273
   *
274
   * @return callable|mixed
275
   */
276 View Code Duplication
  protected function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $document, $operation) {
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...
277
    $root = $config->getRootValue();
278
    if (is_callable($root)) {
279
      $root = $root($params, $document, $operation);
280
    }
281
282
    return $root;
283
  }
284
285
  /**
286
   * @param \GraphQL\Server\ServerConfig $config
287
   * @param \GraphQL\Server\OperationParams $params
288
   * @param \GraphQL\Language\AST\DocumentNode $document
289
   * @param $operation
290
   *
291
   * @return callable|mixed
292
   */
293 View Code Duplication
  protected function resolveContextValue(ServerConfig $config, OperationParams $params, DocumentNode $document, $operation) {
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...
294
    $context = $config->getContext();
295
    if (is_callable($context)) {
296
      $context = $context($params, $document, $operation);
297
    }
298
299
    return $context;
300
  }
301
302
  /**
303
   * @param \GraphQL\Server\ServerConfig $config
304
   * @param \GraphQL\Server\OperationParams $params
305
   * @param \GraphQL\Language\AST\DocumentNode $document
306
   * @param $operation
307
   *
308
   * @return array|callable
309
   */
310
  protected function resolveValidationRules(ServerConfig $config, OperationParams $params, DocumentNode $document, $operation) {
311
    // Allow customizing validation rules per operation:
312
    $rules = $config->getValidationRules();
313
    if (is_callable($rules)) {
314
      $rules = $rules($params, $document, $operation);
315
      if (!is_array($rules)) {
316
        throw new \LogicException(sprintf("Expecting validation rules to be array or callable returning array, but got: %s", Utils::printSafe($rules)));
317
      }
318
    }
319
320
    return $rules;
321
  }
322
323
  /**
324
   * @param \GraphQL\Server\ServerConfig $config
325
   * @param \GraphQL\Server\OperationParams $params
326
   *
327
   * @return mixed
328
   * @throws \GraphQL\Server\RequestError
329
   */
330
  protected function loadPersistedQuery(ServerConfig $config, OperationParams $params) {
331
    if (!$loader = $config->getPersistentQueryLoader()) {
332
      throw new RequestError('Persisted queries are not supported by this server.');
333
    }
334
335
    $source = $loader($params->queryId, $params);
336
    if (!is_string($source) && !$source instanceof DocumentNode) {
337
      throw new \LogicException(sprintf('The persisted query loader must return query string or instance of %s but got: %s.', DocumentNode::class, Utils::printSafe($source)));
338
    }
339
340
    return $source;
341
  }
342
343
}
344