Issues (645)

src/Plugin/Deriver/PluggableSchemaDeriver.php (9 issues)

1
<?php
2
3
namespace Drupal\graphql\Plugin\Deriver;
4
5
use Drupal\Component\Plugin\Derivative\DeriverBase;
0 ignored issues
show
The type Drupal\Component\Plugin\Derivative\DeriverBase was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Drupal\Component\Plugin\PluginManagerInterface;
0 ignored issues
show
The type Drupal\Component\Plugin\PluginManagerInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Drupal\Component\Utility\SortArray;
0 ignored issues
show
The type Drupal\Component\Utility\SortArray was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Drupal\Core\Cache\Cache;
0 ignored issues
show
The type Drupal\Core\Cache\Cache was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Drupal\Core\Cache\CacheableDependencyInterface;
0 ignored issues
show
The type Drupal\Core\Cache\CacheableDependencyInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
0 ignored issues
show
The type Drupal\Core\Plugin\Disco...ntainerDeriverInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use Drupal\graphql\Plugin\FieldPluginManager;
12
use Drupal\graphql\Plugin\MutationPluginManager;
13
use Drupal\graphql\Plugin\SubscriptionPluginManager;
14
use Drupal\graphql\Plugin\TypePluginManagerAggregator;
15
use Symfony\Component\DependencyInjection\ContainerInterface;
0 ignored issues
show
The type Symfony\Component\Depend...tion\ContainerInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
17
class PluggableSchemaDeriver extends DeriverBase implements ContainerDeriverInterface {
18
19
  /**
20
   * The base plugin id.
21
   *
22
   * @var string
23
   */
24
  protected $basePluginId;
25
26
  /**
27
   * The field manager service.
28
   *
29
   * @var \Drupal\graphql\Plugin\FieldPluginManager
30
   */
31
  protected $fieldManager;
32
33
  /**
34
   * The mutation manager service.
35
   *
36
   * @var \Drupal\graphql\Plugin\MutationPluginManager
37
   */
38
  protected $mutationManager;
39
40
  /**
41
   * The subscription manager service.
42
   *
43
   * @var \Drupal\graphql\Plugin\SubscriptionPluginManager
44
   */
45
  protected $subscriptionManager;
46
47
  /**
48
   * The type manager aggregator service.
49
   *
50
   * @var \Drupal\graphql\Plugin\TypePluginManagerAggregator
51
   */
52
  protected $typeManagers;
53
54
  /**
55
   * {@inheritdoc}
56
   */
57
  public static function create(ContainerInterface $container, $basePluginId) {
58
    return new static(
59
      $basePluginId,
60
      $container->get('plugin.manager.graphql.field'),
61
      $container->get('plugin.manager.graphql.mutation'),
62
      $container->get('plugin.manager.graphql.subscription'),
63
      $container->get('graphql.type_manager_aggregator')
64
    );
65
  }
66
67
  /**
68
   * PluggableSchemaDeriver constructor.
69
   *
70
   * @param $basePluginId
71
   *   The base plugin id.
72
   * @param \Drupal\graphql\Plugin\FieldPluginManager $fieldManager
73
   *   The field plugin manager.
74
   * @param \Drupal\graphql\Plugin\MutationPluginManager $mutationManager
75
   *   The mutation plugin manager.
76
   * @param \Drupal\graphql\Plugin\SubscriptionPluginManager $subscriptionManager
77
   *   The mutation plugin manager.
78
   * @param \Drupal\graphql\Plugin\TypePluginManagerAggregator $typeManagers
79
   *   The type manager aggregator service.
80
   */
81
  public function __construct(
82
    $basePluginId,
83
    FieldPluginManager $fieldManager,
84
    MutationPluginManager $mutationManager,
85
    SubscriptionPluginManager $subscriptionManager,
86
    TypePluginManagerAggregator $typeManagers
87
  ) {
88
    $this->basePluginId = $basePluginId;
89
    $this->fieldManager = $fieldManager;
90
    $this->mutationManager = $mutationManager;
91
    $this->subscriptionManager = $subscriptionManager;
92
    $this->typeManagers = $typeManagers;
93
  }
94
95
  /**
96
   * {@inheritdoc}
97
   */
98
  public function getDerivativeDefinitions($basePluginDefinition) {
99
    // Construct the optimized data representation for building the schema.
100
    $typeMap = $this->buildTypeMap(iterator_to_array($this->typeManagers));
101
    $typeReferenceMap = $this->buildTypeReferenceMap($typeMap);
102
    $typeAssocationMap = $this->buildTypeAssociationMap($typeMap);
103
    $fieldAssocationMap = $this->buildFieldAssociationMap($this->fieldManager, $typeMap);
104
    $fieldMap = $this->buildFieldMap($this->fieldManager, $fieldAssocationMap);
105
    $mutationMap = $this->buildMutationMap($this->mutationManager);
106
    $subscriptionMap = $this->buildSubscriptionMap($this->subscriptionManager);
107
108
    $managers = array_merge([$this->fieldManager, $this->mutationManager, $this->subscriptionManager], iterator_to_array($this->typeManagers));
109
    $cacheTags = array_reduce($managers, function ($carry, CacheableDependencyInterface $current) {
110
      return Cache::mergeTags($carry, $current->getCacheTags());
111
    }, []);
112
113
    $cacheContexts = array_reduce($managers, function ($carry, CacheableDependencyInterface $current) {
114
      return Cache::mergeContexts($carry, $current->getCacheContexts());
115
    }, []);
116
117
    $cacheMaxAge = array_reduce($managers, function ($carry, CacheableDependencyInterface $current) {
118
      return Cache::mergeMaxAges($carry, $current->getCacheMaxAge());
119
    }, Cache::PERMANENT);
120
121
    $this->derivatives[$this->basePluginId] = [
0 ignored issues
show
Bug Best Practice introduced by
The property derivatives does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
122
      'type_map' => $typeMap,
123
      'type_reference_map' => $typeReferenceMap,
124
      'type_association_map' => $typeAssocationMap,
125
      'field_association_map' => $fieldAssocationMap,
126
      'field_map' => $fieldMap,
127
      'mutation_map' => $mutationMap,
128
      'subscription_map' => $subscriptionMap,
129
      'schema_cache_tags' => $cacheTags,
130
      'schema_cache_contexts' => $cacheContexts,
131
      'schema_cache_max_age' => $cacheMaxAge,
132
    ] + $basePluginDefinition;
133
134
    return $this->derivatives;
135
  }
136
137
  /**
138
   * Builds an optimized map of types registered with any of the type managers.
139
   *
140
   * @param array $managers
141
   *   The registered type plugin managers.
142
   *
143
   * @return array
144
   *   The optimized representation/registry of type definitions.
145
   */
146
  protected function buildTypeMap(array $managers) {
147
    // First collect all definitions by their name, overwriting those with
148
    // lower weights by their higher weighted counterparts. We also collect
149
    // the class from the plugin definition to be able to statically create
150
    // the type instance without loading the plugin managers at all at
151
    // run-time.
152
    $types = array_reduce(array_keys($managers), function ($carry, $type) use ($managers) {
153
      $manager = $managers[$type];
154
      $definitions = $manager->getDefinitions();
155
156
      return array_reduce(array_keys($definitions), function ($carry, $id) use ($type, $definitions) {
157
        $current = $definitions[$id];
158
        $name = $current['name'];
159
160
        if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
161
          $carry[$name] = [
162
            'type' => $type,
163
            'id' => $id,
164
            'class' => $current['class'],
165
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
166
            'reference' => !empty($current['type']) ? $current['type'] : NULL,
167
          ];
168
        }
169
170
        return $carry;
171
      }, $carry);
172
    }, []);
173
174
    // Retrieve the plugins run-time definitions. These will help us to prevent
175
    // plugin instantiation at run-time unless a plugin is actually called from
176
    // the graphql query execution. Plugins should take care of not having to
177
    // instantiate their plugin instances during schema composition.
178
    return array_map(function ($type) use ($managers) {
179
      $manager = $managers[$type['type']];
180
      /** @var \Drupal\graphql\Plugin\TypePluginInterface $instance */
181
      $instance = $manager->getInstance(['id' => $type['id']]);
182
183
      return $type + [
184
        'definition' => $instance->getDefinition(),
185
      ] + $type;
186
    }, $types);
187
  }
188
189
  /**
190
   * Builds an optimized map of data type and type name references.
191
   *
192
   * @param array $types
193
   *   The list of types registered with any of the plugin managers.
194
   *
195
   * @return array
196
   *   The optimized list of data type to type name references.
197
   */
198
  protected function buildTypeReferenceMap(array $types) {
199
    $references = array_reduce(array_keys($types), function ($references, $name) use ($types) {
200
      $current = $types[$name];
201
      $reference = $current['reference'];
202
203
      if (!empty($reference) && (empty($references[$reference]) || $references[$reference]['weight'] < $current['weight'])) {
204
        $references[$reference] = [
205
          'name' => $name,
206
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
207
        ];
208
      }
209
210
      return $references;
211
    }, []);
212
213
    return array_map(function ($reference) {
214
      return $reference['name'];
215
    }, $references);
216
  }
217
218
  /**
219
   * Builds an optimized representation of fields keyed by their parent types.
220
   *
221
   * @param \Drupal\graphql\Plugin\FieldPluginManager $manager
222
   *   The field plugin manager.
223
   * @param $types
224
   *   The optimized list of types.
225
   *
226
   * @return array
227
   *   The list of fields keyed by their parent types.
228
   */
229
  protected function buildFieldAssociationMap(FieldPluginManager $manager, $types) {
230
    $definitions = $manager->getDefinitions();
231
    $fields = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions, $types) {
232
      $current = $definitions[$id];
233
      $parents = $current['parents'] ?: ['Root'];
234
235
      return array_reduce($parents, function ($carry, $parent) use ($current, $id, $types) {
0 ignored issues
show
The import $types is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
236
        // Allow plugins to define a different name for each parent.
237
        if (strpos($parent, ':') !== FALSE) {
238
          list($parent, $name) = explode(':', $parent);
239
        }
240
241
        $name = isset($name) ? $name : $current['name'];
242
        if (empty($carry[$parent][$name]) || $carry[$parent][$name]['weight'] < $current['weight']) {
243
          $carry[$parent][$name] = [
244
            'id' => $id,
245
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
246
          ];
247
        }
248
249
        return $carry;
250
      }, $carry);
251
    }, []);
252
253
    $rename = [];
254
255
    foreach ($fields as $parent => $fieldList) {
256
      foreach ($fieldList as $field => $info) {
257
        if (!array_key_exists($parent, $types)) {
258
          continue;
259
        }
260
        if (!isset($types[$parent]['definition']['interfaces']) || !is_array($types[$parent]['definition']['interfaces'])) {
261
          continue;
262
        }
263
        foreach ($types[$parent]['definition']['interfaces'] as $interface) {
264
          if (isset($fields[$interface][$field]) && $definitions[$fields[$interface][$field]['id']]['type'] !== $definitions[$info['id']]['type']) {
265
            $rename[$parent][$field] = TRUE;
266
          }
267
        }
268
      }
269
    }
270
271
    foreach ($rename as $parent => $names) {
272
      foreach (array_keys($names) as $name) {
273
        $fields[$parent][$name . 'Of' . $parent] = $fields[$parent][$name];
274
        unset($fields[$parent][$name]);
275
      }
276
    }
277
278
    // Only return fields for types that are actually fieldable.
279
    $fieldable = [GRAPHQL_TYPE_PLUGIN, GRAPHQL_INTERFACE_PLUGIN];
280
    $fields = array_intersect_key($fields, array_filter($types, function ($type) use ($fieldable) {
281
      return in_array($type['type'], $fieldable);
282
    }) + ['Root' => NULL]);
283
284
    // We only need the plugin ids in this map.
285
    return array_map(function ($fields) {
286
      return array_map(function ($field) {
287
        return $field['id'];
288
      }, $fields);
289
    }, $fields);
290
  }
291
292
  /**
293
   * Builds an optimized representation of type and composite type relations.
294
   *
295
   * @param array $types
296
   *   The optimized list of types.
297
   *
298
   * @return array
299
   *   The optimized list of types and their associated unions/interfaces.
300
   */
301
  protected function buildTypeAssociationMap(array $types) {
302
    $assocations = array_filter(array_map(function ($type) use ($types) {
303
      // If this is an object type, just return a mapping for it's interfaces.
304
      if ($type['type'] === 'type') {
305
        return array_map(function () use ($type) {
306
          return [$type['definition']['name']];
307
        }, array_flip($type['definition']['interfaces']));
308
      }
309
310
      // For interfaces, find all object types that declare to implement it.
311
      if ($type['type'] === 'interface') {
312
        return [$type['definition']['name'] => array_values(array_map(function ($type) {
313
          return $type['definition']['name'];
314
        }, array_filter($types, function ($subType) use ($type) {
315
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['interfaces']);
316
        })))];
317
      }
318
319
      // Union types combine the two approaches above.
320
      if ($type['type'] === 'union') {
321
        $explicit = $type['definition']['types'];
322
323
        $implicit = array_values(array_map(function ($type) {
324
          return $type['definition']['name'];
325
        }, array_filter($types, function ($subType) use ($type) {
326
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['unions']);
327
        })));
328
329
        return [$type['definition']['name'] => array_merge($explicit, $implicit)];
330
      }
331
332
      return [];
333
    }, $types));
334
335
    $assocations = array_map('array_unique', array_reduce($assocations, 'array_merge_recursive', []));
336
    $assocations = array_map(function ($parent) use ($types) {
337
      $children = array_map(function ($child) use ($types) {
338
        return $types[$child] + ['name' => $child];
339
      }, $parent);
340
341
      uasort($children,[SortArray::class, 'sortByWeightElement']);
342
      $children = array_reverse($children);
343
344
      return array_map(function ($child) {
345
        return $child['name'];
346
      }, $children);
347
    }, $assocations);
348
349
    return $assocations;
350
  }
351
352
  /**
353
   * Builds an optimization representation of all registered fields.
354
   *
355
   * @param \Drupal\graphql\Plugin\FieldPluginManager $manager
356
   *   The field plugin manager.
357
   * @param $association
358
   *   The type/field association map.
359
   *
360
   * @return array
361
   *   The optimized list of all registered fields.
362
   */
363
  protected function buildFieldMap(FieldPluginManager $manager, $association) {
364
    return array_reduce($association, function ($carry, $fields) use ($manager) {
365
      return array_reduce($fields, function ($carry, $id) use ($manager) {
366
        if (!isset($carry[$id])) {
367
          $instance = $manager->getInstance(['id' => $id]);
368
          $definition = $manager->getDefinition($id);
369
370
          $carry[$id] = [
371
            'id' => $id,
372
            'class' => $definition['class'],
373
            'definition' => $instance->getDefinition(),
374
          ];
375
        }
376
377
        return $carry;
378
      }, $carry);
379
    }, []);
380
  }
381
382
  /**
383
   * Builds an optimized representation of all registered mutations.
384
   *
385
   * @param \Drupal\graphql\Plugin\MutationPluginManager $manager
386
   *   The mutation plugin manager.
387
   *
388
   * @return array
389
   *   The optimized list of all registered mutations.
390
   */
391
  protected function buildMutationMap(MutationPluginManager $manager) {
392
    $definitions = $manager->getDefinitions();
393
    $mutations = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions) {
394
      $current = $definitions[$id];
395
      $name = $current['name'];
396
397
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
398
        $carry[$name] = [
399
          'id' => $id,
400
          'class' => $current['class'],
401
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
402
        ];
403
      }
404
405
      return $carry;
406
    }, []);
407
408
    return array_map(function ($definition) use ($manager) {
409
      $id = $definition['id'];
410
      $instance = $manager->getInstance(['id' => $id]);
411
412
      return [
413
        'definition' => $instance->getDefinition(),
414
      ] + $definition;
415
    }, $mutations);
416
  }
417
418
  /**
419
   * Builds an optimized representation of all registered subscriptions.
420
   *
421
   * @param \Drupal\graphql\Plugin\SubscriptionPluginManager $manager
422
   *   The subscription plugin manager.
423
   *
424
   * @return array
425
   *   The optimized list of all registered subscriptions.
426
   */
427
  protected function buildSubscriptionMap(SubscriptionPluginManager $manager) {
428
    $definitions = $manager->getDefinitions();
429
    $subscriptions = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions) {
430
      $current = $definitions[$id];
431
      $name = $current['name'];
432
433
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
434
        $carry[$name] = [
435
          'id' => $id,
436
          'class' => $current['class'],
437
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
438
        ];
439
      }
440
441
      return $carry;
442
    }, []);
443
444
    return array_map(function ($definition) use ($manager) {
445
      $id = $definition['id'];
446
      $instance = $manager->getInstance(['id' => $id]);
447
448
      return [
449
        'definition' => $instance->getDefinition(),
450
      ] + $definition;
451
    }, $subscriptions);
452
  }
453
454
}
455