Completed
Push — 8.x-3.x ( b3d7ed...308578 )
by Sebastian
02:02 queued 40s
created

SchemaPluginBase::processArguments()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 1
nop 1
dl 0
loc 20
rs 9.6
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 \Drupal\Core\Language\LanguageManagerInterface $languageManager
164
   *   The language manager service.
165
   * @param array $parameters
166
   *   The service parameters.
167
   */
168
  public function __construct(
169
    $configuration,
170
    $pluginId,
171
    $pluginDefinition,
172
    FieldPluginManager $fieldManager,
173
    MutationPluginManager $mutationManager,
174
    SubscriptionPluginManager $subscriptionManager,
175
    TypePluginManagerAggregator $typeManagers,
176
    QueryProviderInterface $queryProvider,
177
    AccountProxyInterface $currentUser,
178
    LoggerInterface $logger,
179
    LanguageManagerInterface $languageManager,
180
    array $parameters
181
  ) {
182
    parent::__construct($configuration, $pluginId, $pluginDefinition);
183
    $this->fieldManager = $fieldManager;
184
    $this->mutationManager = $mutationManager;
185
    $this->subscriptionManager = $subscriptionManager;
186
    $this->typeManagers = $typeManagers;
187
    $this->queryProvider = $queryProvider;
188
    $this->currentUser = $currentUser;
189
    $this->parameters = $parameters;
190
    $this->logger = $logger;
191
    $this->languageManager = $languageManager;
192
  }
193
194
  /**
195
   * {@inheritdoc}
196
   */
197
  public function getSchema() {
198
    $config = new SchemaConfig();
199
200
    if ($this->hasMutations()) {
201
      $config->setMutation(new ObjectType([
202
        'name' => 'Mutation',
203
        'fields' => function () {
204
          return $this->getMutations();
205
        },
206
      ]));
207
    }
208
209
    if ($this->hasSubscriptions()) {
210
      $config->setSubscription(new ObjectType([
211
        'name' => 'Subscription',
212
        'fields' => function () {
213
          return $this->getSubscriptions();
214
        },
215
      ]));
216
    }
217
218
    $config->setQuery(new ObjectType([
219
      'name' => 'Query',
220
      'fields' => function () {
221
        return $this->getFields('Root');
222
      },
223
    ]));
224
225
    $config->setTypes(function () {
226
      return $this->getTypes();
227
    });
228
229
    $config->setTypeLoader(function ($name) {
230
      return $this->getType($name);
231
    });
232
233
    return new Schema($config);
234
  }
235
236
  /**
237
   * {@inheritdoc}
238
   */
239
  public function validateSchema() {
240
    return NULL;
241
  }
242
243
  /**
244
   * {@inheritdoc}
245
   */
246
  public function getServer() {
247
    // If the current user has appropriate permissions, allow to bypass
248
    // the secure fields restriction.
249
    $globals['bypass field security'] = $this->currentUser->hasPermission('bypass graphql field security');
250
251
    // Create the server config.
252
    $config = ServerConfig::create();
253
254
    // Each document (e.g. in a batch query) gets its own resolve context. This
255
    // allows us to collect the cache metadata and contextual values (e.g.
256
    // inheritance for language) for each query separately.
257
    $config->setContext(function ($params, $document, $operation) use ($globals) {
258
      // Each document (e.g. in a batch query) gets its own resolve context. This
259
      // allows us to collect the cache metadata and contextual values (e.g.
260
      // inheritance for language) for each query separately.
261
      $context = new ResolveContext($globals, [
262
        'language' => $this->languageManager->getCurrentLanguage()->getId(),
263
      ]);
264
265
      $context->addCacheTags(['graphql']);
266
267
      // Always add the language_url cache context.
268
      $context->addCacheContexts([
269
        'languages:language_url',
270
        'languages:language_interface',
271
        'languages:language_content',
272
        'user.permissions',
273
      ]);
274
275
      return $context;
276
    });
277
278
    $config->setValidationRules(function (OperationParams $params, DocumentNode $document, $operation) {
279
      if (isset($params->queryId) && empty($params->getOriginalInput('query')) {
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected '{'
Loading history...
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)) {
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_filter(array_map(function ($arg) {
450
      try {
451
        $type = $this->processType($arg['type']);
452
      }
453
      catch (\Exception $e) {
454
        // Allow optional arguments that are removed if the input type is
455
        // not defined.
456
        if (empty($arg['optional'])) {
457
          throw $e;
458
        }
459
460
        return NULL;
461
      }
462
463
      return [
464
        'type' => $type,
465
      ] + $arg;
466
    }, $args));
467
  }
468
469
  /**
470
   * {@inheritdoc}
471
   */
472
  public function processType(array $type) {
473
    list($type, $decorators) = $type;
474
475
    return array_reduce($decorators, function ($type, $decorator) {
476
      return $decorator($type);
477
    }, $this->getType($type));
478
  }
479
480
  /**
481
   * Retrieves the type instance for the given reference.
482
   *
483
   * @param array $type
484
   *   The type reference.
485
   *
486
   * @return \GraphQL\Type\Definition\Type
487
   *   The type instance.
488
   */
489
  protected function buildType($type) {
490
    if (!isset($this->types[$type['id']])) {
491
      $creator = [$type['class'], 'createInstance'];
492
      $manager = $this->typeManagers->getTypeManager($type['type']);
493
      $this->types[$type['id']] = $creator($this, $manager, $type['definition'], $type['id']);
494
    }
495
496
    return $this->types[$type['id']];
497
  }
498
499
  /**
500
   * Retrieves the field definition for a given field reference.
501
   *
502
   * @param array $field
503
   *   The type reference.
504
   *
505
   * @return array
506
   *   The field definition.
507
   */
508
  protected function buildField($field) {
509
    if (!isset($this->fields[$field['id']])) {
510
      $creator = [$field['class'], 'createInstance'];
511
      $this->fields[$field['id']] = $creator($this, $this->fieldManager, $field['definition'], $field['id']);
512
    }
513
514
    return $this->fields[$field['id']];
515
  }
516
517
  /**
518
   * Retrieves the mutation definition for a given field reference.
519
   *
520
   * @param array $mutation
521
   *   The mutation reference.
522
   *
523
   * @return array
524
   *   The mutation definition.
525
   */
526
  protected function buildMutation($mutation) {
527
    if (!isset($this->mutations[$mutation['id']])) {
528
      $creator = [$mutation['class'], 'createInstance'];
529
      $this->mutations[$mutation['id']] = $creator($this, $this->mutationManager, $mutation['definition'], $mutation['id']);
530
    }
531
532
    return $this->mutations[$mutation['id']];
533
  }
534
535
  /**
536
   * Retrieves the subscription definition for a given field reference.
537
   *
538
   * @param array $mutation
539
   *   The subscription reference.
540
   *
541
   * @return array
542
   *   The subscription definition.
543
   */
544
  protected function buildSubscription($subscription) {
545
    if (!isset($this->subscriptions[$subscription['id']])) {
546
      $creator = [$subscription['class'], 'createInstance'];
547
      $this->subscriptions[$subscription['id']] = $creator($this, $this->subscriptionManager, $subscription['definition'], $subscription['id']);
548
    }
549
550
    return $this->subscriptions[$subscription['id']];
551
  }
552
553
  /**
554
   * {@inheritdoc}
555
   */
556
  public function getCacheContexts() {
557
    return [];
558
  }
559
560
  /**
561
   * {@inheritdoc}
562
   */
563
  public function getCacheTags() {
564
    return $this->pluginDefinition['schema_cache_tags'];
565
  }
566
567
  /**
568
   * {@inheritdoc}
569
   */
570
  public function getCacheMaxAge() {
571
    return $this->pluginDefinition['schema_cache_max_age'];
572
  }
573
}
574