Completed
Push — 8.x-3.x ( 4bc053...679bcf )
by Sebastian
16:30 queued 06:25
created

PluggableSchemaDeriver::buildFieldAssociationMap()   C

Complexity

Conditions 15
Paths 12

Size

Total Lines 59
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 36
nc 12
nop 2
dl 0
loc 59
rs 6.3845
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\Plugin\Deriver;
4
5
use Drupal\Component\Plugin\Derivative\DeriverBase;
6
use Drupal\Component\Plugin\PluginManagerInterface;
7
use Drupal\Component\Utility\SortArray;
8
use Drupal\Core\Cache\Cache;
9
use Drupal\Core\Cache\CacheableDependencyInterface;
10
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
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;
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 View Code Duplication
  public static function create(ContainerInterface $container, $basePluginId) {
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...
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 View Code Duplication
  public function __construct(
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...
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] = [
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) {
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
        foreach ($types[$parent]['definition']['interfaces'] as $interface) {
261
          if (isset($fields[$interface][$field]) && $definitions[$fields[$interface][$field]['id']]['type'] != $definitions[$info['id']]['type']) {
262
            $rename[$parent][$field] = TRUE;
263
          }
264
        }
265
      }
266
    }
267
268
    foreach ($rename as $parent => $names) {
269
      foreach (array_keys($names) as $name) {
270
        $fields[$parent][$name . 'Of' . $parent] = $fields[$parent][$name];
271
        unset($fields[$parent][$name]);
272
      }
273
    }
274
275
    // Only return fields for types that are actually fieldable.
276
    $fieldable = [GRAPHQL_TYPE_PLUGIN, GRAPHQL_INTERFACE_PLUGIN];
277
    $fields = array_intersect_key($fields, array_filter($types, function ($type) use ($fieldable) {
278
      return in_array($type['type'], $fieldable);
279
    }) + ['Root' => NULL]);
280
281
    // We only need the plugin ids in this map.
282
    return array_map(function ($fields) {
283
      return array_map(function ($field) {
284
        return $field['id'];
285
      }, $fields);
286
    }, $fields);
287
  }
288
289
  /**
290
   * Builds an optimized representation of type and composite type relations.
291
   *
292
   * @param array $types
293
   *   The optimized list of types.
294
   *
295
   * @return array
296
   *   The optimized list of types and their associated unions/interfaces.
297
   */
298
  protected function buildTypeAssociationMap(array $types) {
299
    $assocations = array_filter(array_map(function ($type) use ($types) {
300
      // If this is an object type, just return a mapping for it's interfaces.
301
      if ($type['type'] === 'type') {
302
        return array_map(function () use ($type) {
303
          return [$type['definition']['name']];
304
        }, array_flip($type['definition']['interfaces']));
305
      }
306
307
      // For interfaces, find all object types that declare to implement it.
308
      if ($type['type'] === 'interface') {
309
        return [$type['definition']['name'] => array_values(array_map(function ($type) {
310
          return $type['definition']['name'];
311 View Code Duplication
        }, array_filter($types, function ($subType) use ($type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
312
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['interfaces']);
313
        })))];
314
      }
315
316
      // Union types combine the two approaches above.
317
      if ($type['type'] === 'union') {
318
        $explicit = $type['definition']['types'];
319
320
        $implicit = array_values(array_map(function ($type) {
321
          return $type['definition']['name'];
322 View Code Duplication
        }, array_filter($types, function ($subType) use ($type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
323
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['unions']);
324
        })));
325
326
        return [$type['definition']['name'] => array_merge($explicit, $implicit)];
327
      }
328
329
      return [];
330
    }, $types));
331
332
    $assocations = array_map('array_unique', array_reduce($assocations, 'array_merge_recursive', []));
333
    $assocations = array_map(function ($parent) use ($types) {
334
      $children = array_map(function ($child) use ($types) {
335
        return $types[$child] + ['name' => $child];
336
      }, $parent);
337
338
      uasort($children,[SortArray::class, 'sortByWeightElement']);
339
      $children = array_reverse($children);
340
341
      return array_map(function ($child) {
342
        return $child['name'];
343
      }, $children);
344
    }, $assocations);
345
346
    return $assocations;
347
  }
348
349
  /**
350
   * Builds an optimization representation of all registered fields.
351
   *
352
   * @param \Drupal\graphql\Plugin\FieldPluginManager $manager
353
   *   The field plugin manager.
354
   * @param $association
355
   *   The type/field association map.
356
   *
357
   * @return array
358
   *   The optimized list of all registered fields.
359
   */
360
  protected function buildFieldMap(FieldPluginManager $manager, $association) {
361
    return array_reduce($association, function ($carry, $fields) use ($manager) {
362
      return array_reduce($fields, function ($carry, $id) use ($manager) {
363
        if (!isset($carry[$id])) {
364
          $instance = $manager->getInstance(['id' => $id]);
365
          $definition = $manager->getDefinition($id);
366
367
          $carry[$id] = [
368
            'id' => $id,
369
            'class' => $definition['class'],
370
            'definition' => $instance->getDefinition(),
371
          ];
372
        }
373
374
        return $carry;
375
      }, $carry);
376
    }, []);
377
  }
378
379
  /**
380
   * Builds an optimized representation of all registered mutations.
381
   *
382
   * @param \Drupal\graphql\Plugin\MutationPluginManager $manager
383
   *   The mutation plugin manager.
384
   *
385
   * @return array
386
   *   The optimized list of all registered mutations.
387
   */
388 View Code Duplication
  protected function buildMutationMap(MutationPluginManager $manager) {
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...
389
    $definitions = $manager->getDefinitions();
390
    $mutations = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions) {
391
      $current = $definitions[$id];
392
      $name = $current['name'];
393
394
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
395
        $carry[$name] = [
396
          'id' => $id,
397
          'class' => $current['class'],
398
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
399
        ];
400
      }
401
402
      return $carry;
403
    }, []);
404
405
    return array_map(function ($definition) use ($manager) {
406
      $id = $definition['id'];
407
      $instance = $manager->getInstance(['id' => $id]);
408
409
      return [
410
        'definition' => $instance->getDefinition(),
411
      ] + $definition;
412
    }, $mutations);
413
  }
414
415
  /**
416
   * Builds an optimized representation of all registered subscriptions.
417
   *
418
   * @param \Drupal\graphql\Plugin\SubscriptionPluginManager $manager
419
   *   The subscription plugin manager.
420
   *
421
   * @return array
422
   *   The optimized list of all registered subscriptions.
423
   */
424 View Code Duplication
  protected function buildSubscriptionMap(SubscriptionPluginManager $manager) {
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...
425
    $definitions = $manager->getDefinitions();
426
    $subscriptions = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions) {
427
      $current = $definitions[$id];
428
      $name = $current['name'];
429
430
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
431
        $carry[$name] = [
432
          'id' => $id,
433
          'class' => $current['class'],
434
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
435
        ];
436
      }
437
438
      return $carry;
439
    }, []);
440
441
    return array_map(function ($definition) use ($manager) {
442
      $id = $definition['id'];
443
      $instance = $manager->getInstance(['id' => $id]);
444
445
      return [
446
        'definition' => $instance->getDefinition(),
447
      ] + $definition;
448
    }, $subscriptions);
449
  }
450
451
}
452