Completed
Pull Request — 8.x-3.x (#556)
by Philipp
05:59
created

PluggableSchemaDeriver::buildTypeReferenceMap()   B

Complexity

Conditions 5
Paths 1

Size

Total Lines 19
Code Lines 12

Duplication

Lines 13
Ratio 68.42 %

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 1
nop 1
dl 13
loc 19
rs 8.8571
c 0
b 0
f 0
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\TypePluginManagerAggregator;
14
use Symfony\Component\DependencyInjection\ContainerInterface;
15
16
class PluggableSchemaDeriver extends DeriverBase implements ContainerDeriverInterface {
17
18
  /**
19
   * The base plugin id.
20
   *
21
   * @var string
22
   */
23
  protected $basePluginId;
24
25
  /**
26
   * The field manager service.
27
   *
28
   * @var \Drupal\graphql\Plugin\FieldPluginManager
29
   */
30
  protected $fieldManager;
31
32
  /**
33
   * The mutation manager service.
34
   *
35
   * @var \Drupal\graphql\Plugin\MutationPluginManager
36
   */
37
  protected $mutationManager;
38
39
  /**
40
   * The type manager aggregator service.
41
   *
42
   * @var \Drupal\graphql\Plugin\TypePluginManagerAggregator
43
   */
44
  protected $typeManagers;
45
46
  /**
47
   * {@inheritdoc}
48
   */
49
  public static function create(ContainerInterface $container, $basePluginId) {
50
    return new static(
51
      $basePluginId,
52
      $container->get('plugin.manager.graphql.field'),
53
      $container->get('plugin.manager.graphql.mutation'),
54
      $container->get('graphql.type_manager_aggregator')
55
    );
56
  }
57
58
  /**
59
   * PluggableSchemaDeriver constructor.
60
   *
61
   * @param $basePluginId
62
   *   The base plugin id.
63
   * @param \Drupal\graphql\Plugin\FieldPluginManager $fieldManager
64
   *   The field plugin manager.
65
   * @param \Drupal\graphql\Plugin\MutationPluginManager $mutationManager
66
   *   The mutation plugin manager.
67
   * @param \Drupal\graphql\Plugin\TypePluginManagerAggregator $typeManagers
68
   *   The type manager aggregator service.
69
   */
70 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...
71
    $basePluginId,
72
    FieldPluginManager $fieldManager,
73
    MutationPluginManager $mutationManager,
74
    TypePluginManagerAggregator $typeManagers
75
  ) {
76
    $this->basePluginId = $basePluginId;
77
    $this->fieldManager = $fieldManager;
78
    $this->mutationManager = $mutationManager;
79
    $this->typeManagers = $typeManagers;
80
  }
81
82
  /**
83
   * {@inheritdoc}
84
   */
85
  public function getDerivativeDefinitions($basePluginDefinition) {
86
    // Construct the optimized data representation for building the schema.
87
    $typeMap = $this->buildTypeMap(iterator_to_array($this->typeManagers));
88
    $typeReferenceMap = $this->buildTypeReferenceMap($typeMap);
89
    $typeAssocationMap = $this->buildTypeAssociationMap($typeMap);
90
    $fieldAssocationMap = $this->buildFieldAssociationMap($this->fieldManager, $typeMap);
91
    $fieldMap = $this->buildFieldMap($this->fieldManager, $fieldAssocationMap);
92
    $mutationMap = $this->buildMutationMap($this->mutationManager);
93
94
    $managers = array_merge([$this->fieldManager, $this->mutationManager], iterator_to_array($this->typeManagers));
95
    $cacheTags = array_reduce($managers, function ($carry, CacheableDependencyInterface $current) {
96
      return Cache::mergeTags($carry, $current->getCacheTags());
97
    }, []);
98
99
    $cacheContexts = array_reduce($managers, function ($carry, CacheableDependencyInterface $current) {
100
      return Cache::mergeContexts($carry, $current->getCacheContexts());
101
    }, [
102
      // As long as the endpoint url language might have effect, we have to
103
      // keep it.
104
      'languages:language_url',
105
    ]);
106
107
    $cacheMaxAge = array_reduce($managers, function ($carry, CacheableDependencyInterface $current) {
108
      return Cache::mergeMaxAges($carry, $current->getCacheMaxAge());
109
    }, Cache::PERMANENT);
110
111
    $this->derivatives[$this->basePluginId] = [
112
      'type_map' => $typeMap,
113
      'type_reference_map' => $typeReferenceMap,
114
      'type_association_map' => $typeAssocationMap,
115
      'field_association_map' => $fieldAssocationMap,
116
      'field_map' => $fieldMap,
117
      'mutation_map' => $mutationMap,
118
      'schema_cache_tags' => $cacheTags,
119
      'schema_cache_contexts' => $cacheContexts,
120
      'schema_cache_max_age' => $cacheMaxAge,
121
    ] + $basePluginDefinition;
122
123
    return $this->derivatives;
124
  }
125
126
  /**
127
   * Builds an optimized map of types registered with any of the type managers.
128
   *
129
   * @param array $managers
130
   *   The registered type plugin managers.
131
   *
132
   * @return array
133
   *   The optimized representation/registry of type definitions.
134
   */
135
  protected function buildTypeMap(array $managers) {
136
    // First collect all definitions by their name, overwriting those with
137
    // lower weights by their higher weighted counterparts. We also collect
138
    // the class from the plugin definition to be able to statically create
139
    // the type instance without loading the plugin managers at all at
140
    // run-time.
141
    $types = array_reduce(array_keys($managers), function ($carry, $type) use ($managers) {
142
      $manager = $managers[$type];
143
      $definitions = $manager->getDefinitions();
144
145
      return array_reduce(array_keys($definitions), function ($carry, $id) use ($type, $definitions) {
146
        $current = $definitions[$id];
147
        $name = $current['name'];
148
149
        if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
150
          $carry[$name] = [
151
            'type' => $type,
152
            'id' => $id,
153
            'class' => $current['class'],
154
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
155
            'reference' => !empty($current['type']) ? $current['type'] : NULL,
156
          ];
157
        }
158
159
        return $carry;
160
      }, $carry);
161
    }, []);
162
163
    // Retrieve the plugins run-time definitions. These will help us to prevent
164
    // plugin instantiation at run-time unless a plugin is actually called from
165
    // the graphql query execution. Plugins should take care of not having to
166
    // instantiate their plugin instances during schema composition.
167
    return array_map(function ($type) use ($managers) {
168
      $manager = $managers[$type['type']];
169
      /** @var \Drupal\graphql\Plugin\TypePluginInterface $instance */
170
      $instance = $manager->getInstance(['id' => $type['id']]);
171
172
      return $type + [
173
        'definition' => $instance->getDefinition(),
174
      ] + $type;
175
    }, $types);
176
  }
177
178
  /**
179
   * Builds an optimized map of data type and type name references.
180
   *
181
   * @param array $types
182
   *   The list of types registered with any of the plugin managers.
183
   *
184
   * @return array
185
   *   The optimized list of data type to type name references.
186
   */
187
  protected function buildTypeReferenceMap(array $types) {
188 View Code Duplication
    $references = array_reduce(array_keys($types), function ($references, $name) use ($types) {
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...
189
      $current = $types[$name];
190
      $reference = $current['reference'];
191
192
      if (!empty($reference) && (empty($references[$reference]) || $references[$reference]['weight'] < $current['weight'])) {
193
        $references[$reference] = [
194
          'name' => $name,
195
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
196
        ];
197
      }
198
199
      return $references;
200
    }, []);
201
202
    return array_map(function ($reference) {
203
      return $reference['name'];
204
    }, $references);
205
  }
206
207
  /**
208
   * Builds an optimized representation of fields keyed by their parent types.
209
   *
210
   * @param \Drupal\graphql\Plugin\FieldPluginManager $manager
211
   *   The field plugin manager.
212
   * @param $types
213
   *   The optimized list of types.
214
   *
215
   * @return array
216
   *   The list of fields keyed by their parent types.
217
   */
218
  protected function buildFieldAssociationMap(FieldPluginManager $manager, $types) {
219
    $definitions = $manager->getDefinitions();
220
    $fields = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions, $types) {
221
      $current = $definitions[$id];
222
      $parents = $current['parents'] ?: ['Root'];
223
224
      return array_reduce($parents, function ($carry, $parent) use ($current, $id, $types) {
225
        // Allow plugins to define a different name for each parent.
226
        if (strpos($parent, ':') !== FALSE) {
227
          list($parent, $name) = explode(':', $parent);
228
        }
229
230
        $name = isset($name) ? $name : $current['name'];
231
        if (empty($carry[$parent][$name]) || $carry[$parent][$name]['weight'] < $current['weight']) {
232
          $carry[$parent][$name] = [
233
            'id' => $id,
234
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
235
          ];
236
        }
237
238
        return $carry;
239
      }, $carry);
240
    }, []);
241
242
    $rename = [];
243
244
    foreach ($fields as $parent => $fieldList) {
245
      foreach ($fieldList as $field => $info) {
246
        if (!array_key_exists($parent, $types)) {
247
          continue;
248
        }
249
        foreach ($types[$parent]['definition']['interfaces'] as $interface) {
250
          if (isset($fields[$interface][$field]) && $fields[$interface][$field]['id'] != $info['id']) {
251
            $rename[$parent][$field] = TRUE;
252
          }
253
        }
254
      }
255
    }
256
257
    foreach ($rename as $parent => $names) {
258
      foreach (array_keys($names) as $name) {
259
        $fields[$parent][$name . 'Of' . $parent] = $fields[$parent][$name];
260
        unset($fields[$parent][$name]);
261
      }
262
    }
263
264
    // Only return fields for types that are actually fieldable.
265
    $fieldable = [GRAPHQL_TYPE_PLUGIN, GRAPHQL_INTERFACE_PLUGIN];
266
    $fields = array_intersect_key($fields, array_filter($types, function ($type) use ($fieldable) {
267
      return in_array($type['type'], $fieldable);
268
    }) + ['Root' => NULL]);
269
270
    // We only need the plugin ids in this map.
271
    return array_map(function ($fields) {
272
      return array_map(function ($field) {
273
        return $field['id'];
274
      }, $fields);
275
    }, $fields);
276
  }
277
278
  /**
279
   * Builds an optimized representation of type and composite type relations.
280
   *
281
   * @param array $types
282
   *   The optimized list of types.
283
   *
284
   * @return array
285
   *   The optimized list of types and their associated unions/interfaces.
286
   */
287
  protected function buildTypeAssociationMap(array $types) {
288
    $assocations = array_filter(array_map(function ($type) use ($types) {
289
      // If this is an object type, just return a mapping for it's interfaces.
290
      if ($type['type'] === 'type') {
291
        return array_map(function () use ($type) {
292
          return [$type['definition']['name']];
293
        }, array_flip($type['definition']['interfaces']));
294
      }
295
296
      // For interfaces, find all object types that declare to implement it.
297
      if ($type['type'] === 'interface') {
298
        return [$type['definition']['name'] => array_values(array_map(function ($type) {
299
          return $type['definition']['name'];
300 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...
301
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['interfaces']);
302
        })))];
303
      }
304
305
      // Union types combine the two approaches above.
306
      if ($type['type'] === 'union') {
307
        $explicit = $type['definition']['types'];
308
309
        $implicit = 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']['unions']);
313
        })));
314
315
        return [$type['definition']['name'] => array_merge($explicit, $implicit)];
316
      }
317
318
      return [];
319
    }, $types));
320
321
    $assocations = array_map('array_unique', array_reduce($assocations, 'array_merge_recursive', []));
322
    $assocations = array_map(function ($parent) use ($types) {
323
      $children = array_map(function ($child) use ($types) {
324
        return $types[$child] + ['name' => $child];
325
      }, $parent);
326
327
      uasort($children,[SortArray::class, 'sortByWeightElement']);
328
      $children = array_reverse($children);
329
330
      return array_map(function ($child) {
331
        return $child['name'];
332
      }, $children);
333
    }, $assocations);
334
335
    return $assocations;
336
  }
337
338
  /**
339
   * Builds an optimization representation of all registered fields.
340
   *
341
   * @param \Drupal\graphql\Plugin\FieldPluginManager $manager
342
   *   The field plugin manager.
343
   * @param $association
344
   *   The type/field association map.
345
   *
346
   * @return array
347
   *   The optimized list of all registered fields.
348
   */
349
  protected function buildFieldMap(FieldPluginManager $manager, $association) {
350
    return array_reduce($association, function ($carry, $fields) use ($manager) {
351
      return array_reduce($fields, function ($carry, $id) use ($manager) {
352
        if (!isset($carry[$id])) {
353
          $instance = $manager->getInstance(['id' => $id]);
354
          $definition = $manager->getDefinition($id);
355
356
          $carry[$id] = [
357
            'id' => $id,
358
            'class' => $definition['class'],
359
            'definition' => $instance->getDefinition(),
360
          ];
361
        }
362
363
        return $carry;
364
      }, $carry);
365
    }, []);
366
  }
367
368
  /**
369
   * Builds an optimized representation of all registered mutations.
370
   *
371
   * @param \Drupal\graphql\Plugin\MutationPluginManager $manager
372
   *   The mutation plugin manager.
373
   *
374
   * @return array
375
   *   The optimized list of all registered mutations.
376
   */
377
  protected function buildMutationMap(MutationPluginManager $manager) {
378
    $definitions = $manager->getDefinitions();
379 View Code Duplication
    $mutations = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions) {
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...
380
      $current = $definitions[$id];
381
      $name = $current['name'];
382
383
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
384
        $carry[$name] = [
385
          'id' => $id,
386
          'class' => $current['class'],
387
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
388
        ];
389
      }
390
391
      return $carry;
392
    }, []);
393
394
    return array_map(function ($definition) use ($manager) {
395
      $id = $definition['id'];
396
      $instance = $manager->getInstance(['id' => $id]);
397
398
      return [
399
        'definition' => $instance->getDefinition(),
400
      ] + $definition;
401
    }, $mutations);
402
  }
403
404
}
405