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

SchemaPluginBase::getSubscriptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
    // Populate globals with the current language.
250
    $globals['language'] = $this->languageManager->getCurrentLanguage()->getId();
251
252
    // Create the server config.
253
    $config = ServerConfig::create();
254
255
    // Each document (e.g. in a batch query) gets its own resolve context. This
256
    // allows us to collect the cache metadata and contextual values (e.g.
257
    // inheritance for language) for each query separately.
258
    $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...
259
      // Each document (e.g. in a batch query) gets its own resolve context. This
260
      // allows us to collect the cache metadata and contextual values (e.g.
261
      // inheritance for language) for each query separately.
262
      $context = new ResolveContext($globals);
263
      $context->addCacheTags(['graphql_response']);
264
265
      // Always add the language_url cache context.
266
      $context->addCacheContexts([
267
        'languages:language_url',
268
        'languages:language_interface',
269
        'languages:language_content',
270
        'user.permissions',
271
      ]);
272
      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...
273
        $context->addCacheableDependency($this);
274
      }
275
276
      return $context;
277
    });
278
279
    $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...
280
      if (isset($params->queryId)) {
281
        // Assume that pre-parsed documents are already validated. This allows
282
        // us to store pre-validated query documents e.g. for persisted queries
283
        // effectively improving performance by skipping run-time validation.
284
        return [];
285
      }
286
287
      return array_values(DocumentValidator::defaultRules());
288
    });
289
290
    $config->setPersistentQueryLoader([$this->queryProvider, 'getQuery']);
291
    $config->setQueryBatching(TRUE);
292
    $config->setDebug(!!$this->parameters['development']);
293
    $config->setSchema($this->getSchema());
294
295
    // Always log the errors.
296
    $config->setErrorsHandler(function (array $errors, callable $formatter) {
297
      /** @var \GraphQL\Error\Error $error */
298
      foreach ($errors as $error) {
299
        $this->logger->error($error->getMessage());
300
      }
301
302
      return array_map($formatter, $errors);
303
    });
304
305
    return $config;
306
  }
307
308
  /**
309
  /**
310
   * {@inheritdoc}
311
   */
312
  public function hasFields($type) {
313
    return isset($this->pluginDefinition['field_association_map'][$type]);
314
  }
315
316
  /**
317
   * {@inheritdoc}
318
   */
319
  public function hasMutations() {
320
    return !empty($this->pluginDefinition['mutation_map']);
321
  }
322
323
  /**
324
   * {@inheritdoc}
325
   */
326
  public function hasSubscriptions() {
327
    return !empty($this->pluginDefinition['subscription_map']);
328
  }
329
330
  /**
331
   * {@inheritdoc}
332
   */
333
  public function hasType($name) {
334
    return isset($this->pluginDefinition['type_map'][$name]);
335
  }
336
337
  /**
338
   * {@inheritdoc}
339
   */
340
  public function getFields($type) {
341
    $association = $this->pluginDefinition['field_association_map'];
342
    $fields = $this->pluginDefinition['field_map'];
343
344
    if (isset($association[$type])) {
345
      return $this->processFields(array_map(function ($id) use ($fields) {
346
        return $fields[$id];
347
      }, $association[$type]));
348
    }
349
350
    return [];
351
  }
352
353
  /**
354
   * {@inheritdoc}
355
   */
356
  public function getMutations() {
357
    return $this->processMutations($this->pluginDefinition['mutation_map']);
358
  }
359
360
  /**
361
   * {@inheritdoc}
362
   */
363
  public function getSubscriptions() {
364
    return $this->processSubscriptions($this->pluginDefinition['subscription_map']);
365
  }
366
367
  /**
368
   * {@inheritdoc}
369
   */
370
  public function getTypes() {
371
    return array_map(function ($name) {
372
      return $this->getType($name);
373
    }, array_keys($this->pluginDefinition['type_map']));
374
  }
375
376
  /**
377
   * {@inheritdoc}
378
   */
379
  public function getSubTypes($name) {
380
    $association = $this->pluginDefinition['type_association_map'];
381
    return isset($association[$name]) ? $association[$name] : [];
382
  }
383
384
  /**
385
   * {@inheritdoc}
386
   */
387
  public function resolveType($name, $value, ResolveContext $context, ResolveInfo $info) {
388
    $association = $this->pluginDefinition['type_association_map'];
389
    $types = $this->pluginDefinition['type_map'];
390
    if (!isset($association[$name])) {
391
      return NULL;
392
    }
393
394
    foreach ($association[$name] as $type) {
395
      // TODO: Try to avoid loading the type for the check. Consider to make it static!
396
      if (isset($types[$type]) && $instance = $this->buildType($types[$type])) {
397
        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...
398
          return $instance;
399
        }
400
      }
401
    }
402
403
    return NULL;
404
  }
405
406
  /**
407
   * {@inheritdoc}
408
   */
409
  public function getType($name) {
410
    $types = $this->pluginDefinition['type_map'];
411
    $references = $this->pluginDefinition['type_reference_map'];
412
    if (isset($types[$name])) {
413
      return $this->buildType($this->pluginDefinition['type_map'][$name]);
414
    }
415
416
    do {
417
      if (isset($references[$name])) {
418
        return $this->buildType($types[$references[$name]]);
419
      }
420
    } while (($pos = strpos($name, ':')) !== FALSE && $name = substr($name, 0, $pos));
421
422
    throw new \LogicException(sprintf('Missing type %s.', $name));
423
  }
424
425
  /**
426
   * {@inheritdoc}
427
   */
428
  public function processMutations(array $mutations) {
429
    return array_map([$this, 'buildMutation'], $mutations);
430
  }
431
432
  /**
433
   * {@inheritdoc}
434
   */
435
  public function processSubscriptions(array $subscriptions) {
436
    return array_map([$this, 'buildSubscription'], $subscriptions);
437
  }
438
439
  /**
440
   * {@inheritdoc}
441
   */
442
  public function processFields(array $fields) {
443
    return array_map([$this, 'buildField'], $fields);
444
  }
445
446
  /**
447
   * {@inheritdoc}
448
   */
449
  public function processArguments(array $args) {
450
    return array_map(function ($arg) {
451
      return [
452
        'type' => $this->processType($arg['type']),
453
      ] + $arg;
454
    }, $args);
455
  }
456
457
  /**
458
   * {@inheritdoc}
459
   */
460
  public function processType(array $type) {
461
    list($type, $decorators) = $type;
462
463
    return array_reduce($decorators, function ($type, $decorator) {
464
      return $decorator($type);
465
    }, $this->getType($type));
466
  }
467
468
  /**
469
   * Retrieves the type instance for the given reference.
470
   *
471
   * @param array $type
472
   *   The type reference.
473
   *
474
   * @return \GraphQL\Type\Definition\Type
475
   *   The type instance.
476
   */
477
  protected function buildType($type) {
478
    if (!isset($this->types[$type['id']])) {
479
      $creator = [$type['class'], 'createInstance'];
480
      $manager = $this->typeManagers->getTypeManager($type['type']);
481
      $this->types[$type['id']] = $creator($this, $manager, $type['definition'], $type['id']);
482
    }
483
484
    return $this->types[$type['id']];
485
  }
486
487
  /**
488
   * Retrieves the field definition for a given field reference.
489
   *
490
   * @param array $field
491
   *   The type reference.
492
   *
493
   * @return array
494
   *   The field definition.
495
   */
496 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...
497
    if (!isset($this->fields[$field['id']])) {
498
      $creator = [$field['class'], 'createInstance'];
499
      $this->fields[$field['id']] = $creator($this, $this->fieldManager, $field['definition'], $field['id']);
500
    }
501
502
    return $this->fields[$field['id']];
503
  }
504
505
  /**
506
   * Retrieves the mutation definition for a given field reference.
507
   *
508
   * @param array $mutation
509
   *   The mutation reference.
510
   *
511
   * @return array
512
   *   The mutation definition.
513
   */
514 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...
515
    if (!isset($this->mutations[$mutation['id']])) {
516
      $creator = [$mutation['class'], 'createInstance'];
517
      $this->mutations[$mutation['id']] = $creator($this, $this->mutationManager, $mutation['definition'], $mutation['id']);
518
    }
519
520
    return $this->mutations[$mutation['id']];
521
  }
522
523
  /**
524
   * Retrieves the subscription definition for a given field reference.
525
   *
526
   * @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...
527
   *   The subscription reference.
528
   *
529
   * @return array
530
   *   The subscription definition.
531
   */
532 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...
533
    if (!isset($this->subscriptions[$subscription['id']])) {
534
      $creator = [$subscription['class'], 'createInstance'];
535
      $this->subscriptions[$subscription['id']] = $creator($this, $this->subscriptionManager, $subscription['definition'], $subscription['id']);
536
    }
537
538
    return $this->subscriptions[$subscription['id']];
539
  }
540
541
  /**
542
   * {@inheritdoc}
543
   */
544
  public function getCacheContexts() {
545
    return [];
546
  }
547
548
  /**
549
   * {@inheritdoc}
550
   */
551
  public function getCacheTags() {
552
    return $this->pluginDefinition['schema_cache_tags'];
553
  }
554
555
  /**
556
   * {@inheritdoc}
557
   */
558
  public function getCacheMaxAge() {
559
    return $this->pluginDefinition['schema_cache_max_age'];
560
  }
561
}
562