Completed
Pull Request — 8.x-3.x (#525)
by Philipp
02:40
created

QueryProcessor::executeOperation()   C

Complexity

Conditions 15
Paths 97

Size

Total Lines 78
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 46
nc 97
nop 4
dl 0
loc 78
rs 5.15
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\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\Language\Visitor;
19
use GraphQL\Server\Helper;
20
use GraphQL\Server\OperationParams;
21
use GraphQL\Server\RequestError;
22
use GraphQL\Server\ServerConfig;
23
use GraphQL\Type\Schema;
24
use GraphQL\Utils\AST;
25
use GraphQL\Utils\Utils;
26
use GraphQL\Validator\DocumentValidator;
27
28
class QueryProcessor {
29
30
  /**
31
   * The current user account.
32
   *
33
   * @var \Drupal\Core\Session\AccountProxyInterface
34
   */
35
  protected $currentUser;
36
37
  /**
38
   * The schema plugin manager.
39
   *
40
   * @var \Drupal\graphql\Plugin\SchemaPluginManager
41
   */
42
  protected $pluginManager;
43
44
  /**
45
   * The query provider service.
46
   *
47
   * @var \Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface
48
   */
49
  protected $queryProvider;
50
51
  /**
52
   * Processor constructor.
53
   *
54
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
55
   *   The current user.
56
   * @param \Drupal\graphql\Plugin\SchemaPluginManager $pluginManager
57
   *   The schema plugin manager.
58
   * @param \Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface $queryProvider
59
   *   The query provider service.
60
   */
61
  public function __construct(
62
    AccountProxyInterface $currentUser,
63
    SchemaPluginManager $pluginManager,
64
    QueryProviderInterface $queryProvider
65
  ) {
66
    $this->currentUser = $currentUser;
67
    $this->pluginManager = $pluginManager;
68
    $this->queryProvider = $queryProvider;
69
  }
70
71
  /**
72
   * Processes one or multiple graphql operations.
73
   *
74
   * @param string $schema
75
   *   The plugin id of the schema to use.
76
   * @param \GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[] $params
77
   *   The graphql operation(s) to execute.
78
   * @param array $globals
79
   *   The query context.
80
   *
81
   * @return \Drupal\graphql\GraphQL\Execution\QueryResult|\Drupal\graphql\GraphQL\Execution\QueryResult[]
82
   *   The query result.
83
   *
84
   */
85
  public function processQuery($schema, $params, array $globals = []) {
86
    // Load the plugin from the schema manager.
87
    $plugin = $this->pluginManager->createInstance($schema);
88
    $schema = $plugin->getSchema();
89
90
    // Create the server config.
91
    $config = ServerConfig::create();
92
    $config->setDebug(!empty($globals['development']));
93
    $config->setSchema($schema);
94
    $config->setQueryBatching(TRUE);
95
    $config->setContext(function () use ($globals) {
96
      // Each document (e.g. in a batch query) gets its own resolve context but
97
      // the global parameters are shared. This allows us to collect the cache
98
      // metadata and contextual values (e.g. inheritance for language) for each
99
      // query separately.
100
      return new ResolveContext($globals);
101
    });
102
103
    $config->setPersistentQueryLoader(function ($id, OperationParams $params) {
104
      if ($query = $this->queryProvider->getQuery($id, $params)) {
105
        return $query;
106
      }
107
108
      throw new RequestError(sprintf("Failed to load query map for id '%s'.", $id));
109
    });
110
111
    if (is_array($params)) {
112
      return $this->executeBatch($config, $params);
113
    }
114
115
    return $this->executeSingle($config, $params);
116
  }
117
118
  /**
119
   * @param \GraphQL\Server\ServerConfig $config
120
   * @param \GraphQL\Server\OperationParams $params
121
   *
122
   * @return mixed
123
   */
124
  public function executeSingle(ServerConfig $config, OperationParams $params) {
125
    $adapter = new SyncPromiseAdapter();
126
    $result = $this->executeOperation($adapter, $config, $params, FALSE);
127
    return $adapter->wait($result);
128
  }
129
130
  /**
131
   * @param \GraphQL\Server\ServerConfig $config
132
   * @param array $params
133
   *
134
   * @return mixed
135
   */
136
  public function executeBatch(ServerConfig $config, array $params) {
137
    $adapter = new SyncPromiseAdapter();
138
    $result = array_map(function ($params) use ($adapter, $config) {
139
      return $this->executeOperation($adapter, $config, $params, TRUE);
140
    }, $params);
141
142
    $result = $adapter->all($result);
143
    return $adapter->wait($result);
144
  }
145
146
  /**
147
   * @param \GraphQL\Executor\Promise\PromiseAdapter $adapter
148
   * @param \GraphQL\Server\ServerConfig $config
149
   * @param \GraphQL\Server\OperationParams $params
150
   * @param bool $batching
151
   *
152
   * @return \GraphQL\Executor\Promise\Promise
153
   */
154
  protected function executeOperation(PromiseAdapter $adapter, ServerConfig $config, OperationParams $params, $batching = FALSE) {
155
    try {
156
      if (!$config->getSchema()) {
157
        throw new \LogicException('Missing schema for query execution.');
158
      }
159
160
      if ($batching && !$config->getQueryBatching()) {
161
        throw new RequestError('Batched queries are not supported by this server.');
162
      }
163
164
      if ($errors = (new Helper())->validateOperationParams($params)) {
165
        $errors = Utils::map($errors, function (RequestError $err) {
166
          return Error::createLocatedError($err, NULL, NULL);
167
        });
168
169
        return $adapter->createFulfilled(new QueryResult(NULL, $errors));
170
      }
171
172
      $schema = $config->getSchema();
173
      $variables = $params->variables;
174
      $operation = $params->operation;
175
      $document = $params->queryId ? $this->loadPersistedQuery($config, $params) : $params->query;
176
      if (!$document instanceof DocumentNode) {
177
        $document = Parser::parse($document);
178
179
        // Assume that pre-parsed documents are already validated. This allows
180
        // us to store pre-validated query documents e.g. for persisted queries
181
        // effectively improving performance by skipping run-time validation.
182
        $rules = $this->resolveValidationRules($config, $params, $document, $operation);
183
        if ($errors = DocumentValidator::validate($schema, $document, $rules)) {
184
          return $adapter->createFulfilled(new QueryResult(NULL, $errors));
185
        }
186
      }
187
188
      if ($params->isReadOnly() && AST::getOperation($document, $operation) !== 'query') {
189
        throw new RequestError('GET requests are only supported for query operations.');
190
      }
191
192
      // TODO: Collect cache metadata from AST and perform a cach lookup.
193
194
      $resolver = $config->getFieldResolver();
195
      $root = $this->resolveRootValue($config, $params, $document, $operation);
196
      $context = $this->resolveContextValue($config, $params, $document, $operation);
197
      $promise = Executor::promiseToExecute(
198
        $adapter,
199
        $schema,
200
        $document,
201
        $root,
202
        $context,
203
        $variables,
204
        $operation,
205
        $resolver
206
      );
207
208
      return $promise->then(function (ExecutionResult $result) use ($context) {
209
        $metadata = (new CacheableMetadata())->addCacheableDependency($context);
210
        return new QueryResult($result->data, $result->errors, $result->extensions, $metadata);
211
      });
212
    }
213
    catch (RequestError $exception) {
214
      $result = $adapter->createFulfilled(new QueryResult(NULL, [Error::createLocatedError($exception)]));
215
    }
216
    catch (Error $exception) {
217
      $result = $adapter->createFulfilled(new QueryResult(NULL, [$exception]));
218
    }
219
220
    return $result->then(function(QueryResult $result) use ($config) {
221
      if ($config->getErrorsHandler()) {
222
        $result->setErrorsHandler($config->getErrorsHandler());
223
      }
224
225
      if ($config->getErrorFormatter() || $config->getDebug()) {
226
        $result->setErrorFormatter(FormattedError::prepareFormatter($config->getErrorFormatter(), $config->getDebug()));
227
      }
228
229
      return $result;
230
    });
231
  }
232
233
  /**
234
   * @param \GraphQL\Server\ServerConfig $config
235
   * @param \GraphQL\Server\OperationParams $params
236
   * @param \GraphQL\Language\AST\DocumentNode $document
237
   * @param $operation
238
   *
239
   * @return callable|mixed
240
   */
241 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...
242
    $root = $config->getRootValue();
243
    if (is_callable($root)) {
244
      $root = $root($params, $document, $operation);
245
    }
246
247
    return $root;
248
  }
249
250
  /**
251
   * @param \GraphQL\Server\ServerConfig $config
252
   * @param \GraphQL\Server\OperationParams $params
253
   * @param \GraphQL\Language\AST\DocumentNode $document
254
   * @param $operation
255
   *
256
   * @return callable|mixed
257
   */
258 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...
259
    $context = $config->getContext();
260
    if (is_callable($context)) {
261
      $context = $context($params, $document, $operation);
262
    }
263
264
    return $context;
265
  }
266
267
  /**
268
   * @param \GraphQL\Server\ServerConfig $config
269
   * @param \GraphQL\Server\OperationParams $params
270
   * @param \GraphQL\Language\AST\DocumentNode $document
271
   * @param $operation
272
   *
273
   * @return array|callable
274
   */
275
  protected function resolveValidationRules(ServerConfig $config, OperationParams $params, DocumentNode $document, $operation) {
276
    // Allow customizing validation rules per operation:
277
    $rules = $config->getValidationRules();
278
    if (is_callable($rules)) {
279
      $rules = $rules($params, $document, $operation);
280
      if (!is_array($rules)) {
281
        throw new \LogicException(sprintf("Expecting validation rules to be array or callable returning array, but got: %s", Utils::printSafe($rules)));
282
      }
283
    }
284
285
    return $rules;
286
  }
287
288
  /**
289
   * @param \GraphQL\Server\ServerConfig $config
290
   * @param \GraphQL\Server\OperationParams $params
291
   *
292
   * @return mixed
293
   * @throws \GraphQL\Server\RequestError
294
   */
295
  protected function loadPersistedQuery(ServerConfig $config, OperationParams $params) {
296
    if (!$loader = $config->getPersistentQueryLoader()) {
297
      throw new RequestError('Persisted queries are not supported by this server.');
298
    }
299
300
    $source = $loader($params->queryId, $params);
301
    if (!is_string($source) && !$source instanceof DocumentNode) {
302
      throw new \LogicException(sprintf('The persisted query loader must return query string or instance of %s but got: %s.', DocumentNode::class, Utils::printSafe($source)));
303
    }
304
305
    return $source;
306
  }
307
308
}
309