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

SchemaPluginBase::getServer()   B

Complexity

Conditions 4
Paths 1

Size

Total Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 1
nop 0
dl 0
loc 62
rs 8.829
c 0
b 0
f 0

How to fix   Long Method   

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\Plugin\GraphQL\Schemas;
4
5
use Drupal\Component\Plugin\PluginBase;
6
use Drupal\Core\Cache\CacheableDependencyInterface;
7
use Drupal\Core\Language\LanguageManagerInterface;
8
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
9
use Drupal\Core\Session\AccountProxyInterface;
10
use Drupal\graphql\GraphQL\Execution\ResolveContext;
11
use Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface;
12
use Drupal\graphql\Plugin\FieldPluginManager;
13
use Drupal\graphql\Plugin\MutationPluginManager;
14
use Drupal\graphql\Plugin\SubscriptionPluginManager;
15
use Drupal\graphql\Plugin\SchemaBuilderInterface;
16
use Drupal\graphql\Plugin\SchemaPluginInterface;
17
use Drupal\graphql\Plugin\TypePluginManagerAggregator;
18
use GraphQL\Language\AST\DocumentNode;
19
use GraphQL\Server\OperationParams;
20
use GraphQL\Server\ServerConfig;
21
use GraphQL\Type\Definition\ObjectType;
22
use GraphQL\Type\Definition\ResolveInfo;
23
use GraphQL\Type\Schema;
24
use GraphQL\Type\SchemaConfig;
25
use GraphQL\Validator\DocumentValidator;
26
use Psr\Log\LoggerInterface;
27
use Symfony\Component\DependencyInjection\ContainerInterface;
28
29
abstract class SchemaPluginBase extends PluginBase implements SchemaPluginInterface, SchemaBuilderInterface, ContainerFactoryPluginInterface, CacheableDependencyInterface {
30
31
  /**
32
   * The field plugin manager.
33
   *
34
   * @var \Drupal\graphql\Plugin\FieldPluginManager
35
   */
36
  protected $fieldManager;
37
38
  /**
39
   * The mutation plugin manager.
40
   *
41
   * @var \Drupal\graphql\Plugin\MutationPluginManager
42
   */
43
  protected $mutationManager;
44
45
  /**
46
   * The subscription plugin manager.
47
   *
48
   * @var \Drupal\graphql\Plugin\SubscriptionPluginManager
49
   */
50
  protected $subscriptionManager;
51
52
  /**
53
   * The type manager aggregator service.
54
   *
55
   * @var \Drupal\graphql\Plugin\TypePluginManagerAggregator
56
   */
57
  protected $typeManagers;
58
59
  /**
60
   * Static cache of field definitions.
61
   *
62
   * @var array
63
   */
64
  protected $fields = [];
65
66
  /**
67
   * Static cache of mutation definitions.
68
   *
69
   * @var array
70
   */
71
  protected $mutations = [];
72
73
  /**
74
   * Static cache of subscription definitions.
75
   *
76
   * @var array
77
   */
78
  protected $subscriptions = [];
79
80
  /**
81
   * Static cache of type instances.
82
   *
83
   * @var array
84
   */
85
  protected $types = [];
86
87
  /**
88
   * The service parameters
89
   *
90
   * @var array
91
   */
92
  protected $parameters;
93
94
  /**
95
   * The query provider service.
96
   *
97
   * @var \Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface
98
   */
99
  protected $queryProvider;
100
101
  /**
102
   * The current user.
103
   *
104
   * @var \Drupal\Core\Session\AccountProxyInterface
105
   */
106
  protected $currentUser;
107
108
  /**
109
   * The logger service.
110
   *
111
   * @var \Psr\Log\LoggerInterface
112
   */
113
  protected $logger;
114
115
  /**
116
   * @var \Drupal\Core\Language\LanguageManagerInterface
117
   */
118
  protected $languageManager;
119
120
  /**
121
   * {@inheritdoc}
122
   */
123
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
124
    return new static(
125
      $configuration,
126
      $plugin_id,
127
      $plugin_definition,
128
      $container->get('plugin.manager.graphql.field'),
129
      $container->get('plugin.manager.graphql.mutation'),
130
      $container->get('plugin.manager.graphql.subscription'),
131
      $container->get('graphql.type_manager_aggregator'),
132
      $container->get('graphql.query_provider'),
133
      $container->get('current_user'),
134
      $container->get('logger.channel.graphql'),
135
      $container->get('language_manager'),
136
      $container->getParameter('graphql.config')
137
    );
138
  }
139
140
  /**
141
   * SchemaPluginBase constructor.
142
   *
143
   * @param array $configuration
144
   *   The plugin configuration array.
145
   * @param string $pluginId
146
   *   The plugin id.
147
   * @param array $pluginDefinition
148
   *   The plugin definition array.
149
   * @param \Drupal\graphql\Plugin\FieldPluginManager $fieldManager
150
   *   The field plugin manager.
151
   * @param \Drupal\graphql\Plugin\MutationPluginManager $mutationManager
152
   *   The mutation plugin manager.
153
   * @param \Drupal\graphql\Plugin\SubscriptionPluginManager $subscriptionManager
154
   *   The subscription plugin manager.
155
   * @param \Drupal\graphql\Plugin\TypePluginManagerAggregator $typeManagers
156
   *   The type manager aggregator service.
157
   * @param \Drupal\graphql\GraphQL\QueryProvider\QueryProviderInterface $queryProvider
158
   *   The query provider service.
159
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
160
   *   The current user.
161
   * @param \Psr\Log\LoggerInterface $logger
162
   *   The logger service.
163
   * @param array $parameters
164
   *   The service parameters.
165
   */
166
  public function __construct(
167
    $configuration,
168
    $pluginId,
169
    $pluginDefinition,
170
    FieldPluginManager $fieldManager,
171
    MutationPluginManager $mutationManager,
172
    SubscriptionPluginManager $subscriptionManager,
173
    TypePluginManagerAggregator $typeManagers,
174
    QueryProviderInterface $queryProvider,
175
    AccountProxyInterface $currentUser,
176
    LoggerInterface $logger,
177
    LanguageManagerInterface $languageManager,
178
    array $parameters
179
  ) {
180
    parent::__construct($configuration, $pluginId, $pluginDefinition);
181
    $this->fieldManager = $fieldManager;
182
    $this->mutationManager = $mutationManager;
183
    $this->subscriptionManager = $subscriptionManager;
184
    $this->typeManagers = $typeManagers;
185
    $this->queryProvider = $queryProvider;
186
    $this->currentUser = $currentUser;
187
    $this->parameters = $parameters;
188
    $this->logger = $logger;
189
    $this->languageManager = $languageManager;
190
  }
191
192
  /**
193
   * {@inheritdoc}
194
   */
195
  public function getSchema() {
196
    $config = new SchemaConfig();
197
198
    if ($this->hasMutations()) {
199
      $config->setMutation(new ObjectType([
200
        'name' => 'Mutation',
201
        'fields' => function () {
202
          return $this->getMutations();
203
        },
204
      ]));
205
    }
206
207
    if ($this->hasSubscriptions()) {
208
      $config->setSubscription(new ObjectType([
209
        'name' => 'Subscription',
210
        'fields' => function () {
211
          return $this->getSubscriptions();
212
        },
213
      ]));
214
    }
215
216
    $config->setQuery(new ObjectType([
217
      'name' => 'Query',
218
      'fields' => function () {
219
        return $this->getFields('Root');
220
      },
221
    ]));
222
223
    $config->setTypes(function () {
224
      return $this->getTypes();
225
    });
226
227
    $config->setTypeLoader(function ($name) {
228
      return $this->getType($name);
229
    });
230
231
    return new Schema($config);
232
  }
233
234
  /**
235
   * {@inheritdoc}
236
   */
237
  public function validateSchema() {
238
    return NULL;
239
  }
240
241
  /**
242
   * {@inheritdoc}
243
   */
244
  public function getServer() {
245
    // If the current user has appropriate permissions, allow to bypass
246
    // the secure fields restriction.
247
    $globals['bypass field security'] = $this->currentUser->hasPermission('bypass graphql field security');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$globals was never initialized. Although not strictly required by PHP, it is generally a good practice to add $globals = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
248
249
    // Create the server config.
250
    $config = ServerConfig::create();
251
252
    // Each document (e.g. in a batch query) gets its own resolve context. This
253
    // allows us to collect the cache metadata and contextual values (e.g.
254
    // inheritance for language) for each query separately.
255
    $config->setContext(function ($params, $document, $operation) use ($globals) {
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $document is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $operation is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
256
      // Each document (e.g. in a batch query) gets its own resolve context. This
257
      // allows us to collect the cache metadata and contextual values (e.g.
258
      // inheritance for language) for each query separately.
259
      $context = new ResolveContext($globals, [
260
        'language' => $this->languageManager->getCurrentLanguage()->getId(),
261
      ]);
262
      $context->addCacheTags(['graphql_response']);
263
264
      // Always add the language_url cache context.
265
      $context->addCacheContexts([
266
        'languages:language_url',
267
        'languages:language_interface',
268
        'languages:language_content',
269
        'user.permissions',
270
      ]);
271
      if ($this 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...
272
        $context->addCacheableDependency($this);
273
      }
274
275
      return $context;
276
    });
277
278
    $config->setValidationRules(function (OperationParams $params, DocumentNode $document, $operation) {
0 ignored issues
show
Unused Code introduced by
The parameter $document is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $operation is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
279
      if (isset($params->queryId)) {
280
        // Assume that pre-parsed documents are already validated. This allows
281
        // us to store pre-validated query documents e.g. for persisted queries
282
        // effectively improving performance by skipping run-time validation.
283
        return [];
284
      }
285
286
      return array_values(DocumentValidator::defaultRules());
287
    });
288
289
    $config->setPersistentQueryLoader([$this->queryProvider, 'getQuery']);
290
    $config->setQueryBatching(TRUE);
291
    $config->setDebug(!!$this->parameters['development']);
292
    $config->setSchema($this->getSchema());
293
294
    // Always log the errors.
295
    $config->setErrorsHandler(function (array $errors, callable $formatter) {
296
      /** @var \GraphQL\Error\Error $error */
297
      foreach ($errors as $error) {
298
        $this->logger->error($error->getMessage());
299
      }
300
301
      return array_map($formatter, $errors);
302
    });
303
304
    return $config;
305
  }
306
307
  /**
308
  /**
309
   * {@inheritdoc}
310
   */
311
  public function hasFields($type) {
312
    return isset($this->pluginDefinition['field_association_map'][$type]);
313
  }
314
315
  /**
316
   * {@inheritdoc}
317
   */
318
  public function hasMutations() {
319
    return !empty($this->pluginDefinition['mutation_map']);
320
  }
321
322
  /**
323
   * {@inheritdoc}
324
   */
325
  public function hasSubscriptions() {
326
    return !empty($this->pluginDefinition['subscription_map']);
327
  }
328
329
  /**
330
   * {@inheritdoc}
331
   */
332
  public function hasType($name) {
333
    return isset($this->pluginDefinition['type_map'][$name]);
334
  }
335
336
  /**
337
   * {@inheritdoc}
338
   */
339
  public function getFields($type) {
340
    $association = $this->pluginDefinition['field_association_map'];
341
    $fields = $this->pluginDefinition['field_map'];
342
343
    if (isset($association[$type])) {
344
      return $this->processFields(array_map(function ($id) use ($fields) {
345
        return $fields[$id];
346
      }, $association[$type]));
347
    }
348
349
    return [];
350
  }
351
352
  /**
353
   * {@inheritdoc}
354
   */
355
  public function getMutations() {
356
    return $this->processMutations($this->pluginDefinition['mutation_map']);
357
  }
358
359
  /**
360
   * {@inheritdoc}
361
   */
362
  public function getSubscriptions() {
363
    return $this->processSubscriptions($this->pluginDefinition['subscription_map']);
364
  }
365
366
  /**
367
   * {@inheritdoc}
368
   */
369
  public function getTypes() {
370
    return array_map(function ($name) {
371
      return $this->getType($name);
372
    }, array_keys($this->pluginDefinition['type_map']));
373
  }
374
375
  /**
376
   * {@inheritdoc}
377
   */
378
  public function getSubTypes($name) {
379
    $association = $this->pluginDefinition['type_association_map'];
380
    return isset($association[$name]) ? $association[$name] : [];
381
  }
382
383
  /**
384
   * {@inheritdoc}
385
   */
386
  public function resolveType($name, $value, ResolveContext $context, ResolveInfo $info) {
387
    $association = $this->pluginDefinition['type_association_map'];
388
    $types = $this->pluginDefinition['type_map'];
389
    if (!isset($association[$name])) {
390
      return NULL;
391
    }
392
393
    foreach ($association[$name] as $type) {
394
      // TODO: Try to avoid loading the type for the check. Consider to make it static!
395
      if (isset($types[$type]) && $instance = $this->buildType($types[$type])) {
396
        if ($instance->isTypeOf($value, $context, $info)) {
0 ignored issues
show
Bug introduced by
The method isTypeOf() does not exist on GraphQL\Type\Definition\Type. Did you maybe mean isType()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
397
          return $instance;
398
        }
399
      }
400
    }
401
402
    return NULL;
403
  }
404
405
  /**
406
   * {@inheritdoc}
407
   */
408
  public function getType($name) {
409
    $types = $this->pluginDefinition['type_map'];
410
    $references = $this->pluginDefinition['type_reference_map'];
411
    if (isset($types[$name])) {
412
      return $this->buildType($this->pluginDefinition['type_map'][$name]);
413
    }
414
415
    do {
416
      if (isset($references[$name])) {
417
        return $this->buildType($types[$references[$name]]);
418
      }
419
    } while (($pos = strpos($name, ':')) !== FALSE && $name = substr($name, 0, $pos));
420
421
    throw new \LogicException(sprintf('Missing type %s.', $name));
422
  }
423
424
  /**
425
   * {@inheritdoc}
426
   */
427
  public function processMutations(array $mutations) {
428
    return array_map([$this, 'buildMutation'], $mutations);
429
  }
430
431
  /**
432
   * {@inheritdoc}
433
   */
434
  public function processSubscriptions(array $subscriptions) {
435
    return array_map([$this, 'buildSubscription'], $subscriptions);
436
  }
437
438
  /**
439
   * {@inheritdoc}
440
   */
441
  public function processFields(array $fields) {
442
    return array_map([$this, 'buildField'], $fields);
443
  }
444
445
  /**
446
   * {@inheritdoc}
447
   */
448
  public function processArguments(array $args) {
449
    return array_map(function ($arg) {
450
      return [
451
        'type' => $this->processType($arg['type']),
452
      ] + $arg;
453
    }, $args);
454
  }
455
456
  /**
457
   * {@inheritdoc}
458
   */
459
  public function processType(array $type) {
460
    list($type, $decorators) = $type;
461
462
    return array_reduce($decorators, function ($type, $decorator) {
463
      return $decorator($type);
464
    }, $this->getType($type));
465
  }
466
467
  /**
468
   * Retrieves the type instance for the given reference.
469
   *
470
   * @param array $type
471
   *   The type reference.
472
   *
473
   * @return \GraphQL\Type\Definition\Type
474
   *   The type instance.
475
   */
476
  protected function buildType($type) {
477
    if (!isset($this->types[$type['id']])) {
478
      $creator = [$type['class'], 'createInstance'];
479
      $manager = $this->typeManagers->getTypeManager($type['type']);
480
      $this->types[$type['id']] = $creator($this, $manager, $type['definition'], $type['id']);
481
    }
482
483
    return $this->types[$type['id']];
484
  }
485
486
  /**
487
   * Retrieves the field definition for a given field reference.
488
   *
489
   * @param array $field
490
   *   The type reference.
491
   *
492
   * @return array
493
   *   The field definition.
494
   */
495 View Code Duplication
  protected function buildField($field) {
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...
496
    if (!isset($this->fields[$field['id']])) {
497
      $creator = [$field['class'], 'createInstance'];
498
      $this->fields[$field['id']] = $creator($this, $this->fieldManager, $field['definition'], $field['id']);
499
    }
500
501
    return $this->fields[$field['id']];
502
  }
503
504
  /**
505
   * Retrieves the mutation definition for a given field reference.
506
   *
507
   * @param array $mutation
508
   *   The mutation reference.
509
   *
510
   * @return array
511
   *   The mutation definition.
512
   */
513 View Code Duplication
  protected function buildMutation($mutation) {
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...
514
    if (!isset($this->mutations[$mutation['id']])) {
515
      $creator = [$mutation['class'], 'createInstance'];
516
      $this->mutations[$mutation['id']] = $creator($this, $this->mutationManager, $mutation['definition'], $mutation['id']);
517
    }
518
519
    return $this->mutations[$mutation['id']];
520
  }
521
522
  /**
523
   * Retrieves the subscription definition for a given field reference.
524
   *
525
   * @param array $mutation
0 ignored issues
show
Bug introduced by
There is no parameter named $mutation. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
526
   *   The subscription reference.
527
   *
528
   * @return array
529
   *   The subscription definition.
530
   */
531 View Code Duplication
  protected function buildSubscription($subscription) {
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...
532
    if (!isset($this->subscriptions[$subscription['id']])) {
533
      $creator = [$subscription['class'], 'createInstance'];
534
      $this->subscriptions[$subscription['id']] = $creator($this, $this->subscriptionManager, $subscription['definition'], $subscription['id']);
535
    }
536
537
    return $this->subscriptions[$subscription['id']];
538
  }
539
540
  /**
541
   * {@inheritdoc}
542
   */
543
  public function getCacheContexts() {
544
    return [];
545
  }
546
547
  /**
548
   * {@inheritdoc}
549
   */
550
  public function getCacheTags() {
551
    return $this->pluginDefinition['schema_cache_tags'];
552
  }
553
554
  /**
555
   * {@inheritdoc}
556
   */
557
  public function getCacheMaxAge() {
558
    return $this->pluginDefinition['schema_cache_max_age'];
559
  }
560
}
561