Completed
Push — 8.x-3.x ( 6b3e90...213280 )
by Sebastian
02:36
created

PluggableSchemaDeriver::buildFieldAssociationMap()   C

Complexity

Conditions 7
Paths 1

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 24
nc 1
nop 2
dl 0
loc 37
rs 6.7272
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
103
    $cacheMaxAge = array_reduce($managers, function ($carry, CacheableDependencyInterface $current) {
104
      return Cache::mergeMaxAges($carry, $current->getCacheMaxAge());
105
    }, Cache::PERMANENT);
106
107
    $this->derivatives[$this->basePluginId] = [
108
      'type_map' => $typeMap,
109
      'type_reference_map' => $typeReferenceMap,
110
      'type_association_map' => $typeAssocationMap,
111
      'field_association_map' => $fieldAssocationMap,
112
      'field_map' => $fieldMap,
113
      'mutation_map' => $mutationMap,
114
      'schema_cache_tags' => $cacheTags,
115
      'schema_cache_contexts' => $cacheContexts,
116
      'schema_cache_max_age' => $cacheMaxAge,
117
    ] + $basePluginDefinition;
118
119
    return $this->derivatives;
120
  }
121
122
  /**
123
   * Builds an optimized map of types registered with any of the type managers.
124
   *
125
   * @param array $managers
126
   *   The registered type plugin managers.
127
   *
128
   * @return array
129
   *   The optimized representation/registry of type definitions.
130
   */
131
  protected function buildTypeMap(array $managers) {
132
    // First collect all definitions by their name, overwriting those with
133
    // lower weights by their higher weighted counterparts. We also collect
134
    // the class from the plugin definition to be able to statically create
135
    // the type instance without loading the plugin managers at all at
136
    // run-time.
137
    $types = array_reduce(array_keys($managers), function ($carry, $type) use ($managers) {
138
      $manager = $managers[$type];
139
      $definitions = $manager->getDefinitions();
140
141
      return array_reduce(array_keys($definitions), function ($carry, $id) use ($type, $definitions) {
142
        $current = $definitions[$id];
143
        $name = $current['name'];
144
145
        if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
146
          $carry[$name] = [
147
            'type' => $type,
148
            'id' => $id,
149
            'class' => $current['class'],
150
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
151
            'reference' => !empty($current['type']) ? $current['type'] : NULL,
152
          ];
153
        }
154
155
        return $carry;
156
      }, $carry);
157
    }, []);
158
159
    // Retrieve the plugins run-time definitions. These will help us to prevent
160
    // plugin instantiation at run-time unless a plugin is actually called from
161
    // the graphql query execution. Plugins should take care of not having to
162
    // instantiate their plugin instances during schema composition.
163
    return array_map(function ($type) use ($managers) {
164
      $manager = $managers[$type['type']];
165
      /** @var \Drupal\graphql\Plugin\TypePluginInterface $instance */
166
      $instance = $manager->getInstance(['id' => $type['id']]);
167
168
      return $type + [
169
        'definition' => $instance->getDefinition(),
170
      ] + $type;
171
    }, $types);
172
  }
173
174
  /**
175
   * Builds an optimized map of data type and type name references.
176
   *
177
   * @param array $types
178
   *   The list of types registered with any of the plugin managers.
179
   *
180
   * @return array
181
   *   The optimized list of data type to type name references.
182
   */
183
  protected function buildTypeReferenceMap(array $types) {
184 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...
185
      $current = $types[$name];
186
      $reference = $current['reference'];
187
188
      if (!empty($reference) && (empty($references[$reference]) || $references[$reference]['weight'] < $current['weight'])) {
189
        $references[$reference] = [
190
          'name' => $name,
191
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
192
        ];
193
      }
194
195
      return $references;
196
    }, []);
197
198
    return array_map(function ($reference) {
199
      return $reference['name'];
200
    }, $references);
201
  }
202
203
  /**
204
   * Builds an optimized representation of fields keyed by their parent types.
205
   *
206
   * @param \Drupal\graphql\Plugin\FieldPluginManager $manager
207
   *   The field plugin manager.
208
   * @param $types
209
   *   The optimized list of types.
210
   *
211
   * @return array
212
   *   The list of fields keyed by their parent types.
213
   */
214
  protected function buildFieldAssociationMap(FieldPluginManager $manager, $types) {
215
    $definitions = $manager->getDefinitions();
216
    $fields = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions) {
217
      $current = $definitions[$id];
218
      $parents = $current['parents'] ?: ['Root'];
219
220
      return array_reduce($parents, function ($carry, $parent) use ($current, $id) {
221
        // Allow plugins to define a different name for each parent.
222
        if (strpos($parent, ':') !== FALSE) {
223
          list($parent, $name) = explode(':', $parent);
224
        }
225
226
        $name = isset($name) ? $name : $current['name'];
227
        if (empty($carry[$parent][$name]) || $carry[$parent][$name]['weight'] < $current['weight']) {
228
          $carry[$parent][$name] = [
229
            'id' => $id,
230
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
231
          ];
232
        }
233
234
        return $carry;
235
      }, $carry);
236
    }, []);
237
238
    // Only return fields for types that are actually fieldable.
239
    $fieldable = [GRAPHQL_TYPE_PLUGIN, GRAPHQL_INTERFACE_PLUGIN];
240
    $fields = array_intersect_key($fields, array_filter($types, function ($type) use ($fieldable) {
241
      return in_array($type['type'], $fieldable);
242
    }) + ['Root' => NULL]);
243
244
    // We only need the plugin ids in this map.
245
    return array_map(function ($fields) {
246
      return array_map(function ($field) {
247
        return $field['id'];
248
      }, $fields);
249
    }, $fields);
250
  }
251
252
  /**
253
   * Builds an optimized representation of type and composite type relations.
254
   *
255
   * @param array $types
256
   *   The optimized list of types.
257
   *
258
   * @return array
259
   *   The optimized list of types and their associated unions/interfaces.
260
   */
261
  protected function buildTypeAssociationMap(array $types) {
262
    $assocations = array_filter(array_map(function ($type) use ($types) {
263
      // If this is an object type, just return a mapping for it's interfaces.
264
      if ($type['type'] === 'type') {
265
        return array_map(function () use ($type) {
266
          return [$type['definition']['name']];
267
        }, array_flip($type['definition']['interfaces']));
268
      }
269
270
      // For interfaces, find all object types that declare to implement it.
271
      if ($type['type'] === 'interface') {
272
        return [$type['definition']['name'] => array_values(array_map(function ($type) {
273
          return $type['definition']['name'];
274 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...
275
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['interfaces']);
276
        })))];
277
      }
278
279
      // Union types combine the two approaches above.
280
      if ($type['type'] === 'union') {
281
        $explicit = $type['definition']['types'];
282
283
        $implicit = array_values(array_map(function ($type) {
284
          return $type['definition']['name'];
285 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...
286
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['unions']);
287
        })));
288
289
        return [$type['definition']['name'] => array_merge($explicit, $implicit)];
290
      }
291
292
      return [];
293
    }, $types));
294
295
    $assocations = array_map('array_unique', array_reduce($assocations, 'array_merge_recursive', []));
296
    $assocations = array_map(function ($parent) use ($types) {
297
      $children = array_map(function ($child) use ($types) {
298
        return $types[$child] + ['name' => $child];
299
      }, $parent);
300
301
      uasort($children,[SortArray::class, 'sortByWeightElement']);
302
      $children = array_reverse($children);
303
304
      return array_map(function ($child) {
305
        return $child['name'];
306
      }, $children);
307
    }, $assocations);
308
309
    return $assocations;
310
  }
311
312
  /**
313
   * Builds an optimization representation of all registered fields.
314
   *
315
   * @param \Drupal\graphql\Plugin\FieldPluginManager $manager
316
   *   The field plugin manager.
317
   * @param $association
318
   *   The type/field association map.
319
   *
320
   * @return array
321
   *   The optimized list of all registered fields.
322
   */
323
  protected function buildFieldMap(FieldPluginManager $manager, $association) {
324
    return array_reduce($association, function ($carry, $fields) use ($manager) {
325
      return array_reduce($fields, function ($carry, $id) use ($manager) {
326
        if (!isset($carry[$id])) {
327
          $instance = $manager->getInstance(['id' => $id]);
328
          $definition = $manager->getDefinition($id);
329
330
          $carry[$id] = [
331
            'id' => $id,
332
            'class' => $definition['class'],
333
            'definition' => $instance->getDefinition(),
334
          ];
335
        }
336
337
        return $carry;
338
      }, $carry);
339
    }, []);
340
  }
341
342
  /**
343
   * Builds an optimized representation of all registered mutations.
344
   *
345
   * @param \Drupal\graphql\Plugin\MutationPluginManager $manager
346
   *   The mutation plugin manager.
347
   *
348
   * @return array
349
   *   The optimized list of all registered mutations.
350
   */
351
  protected function buildMutationMap(MutationPluginManager $manager) {
352
    $definitions = $manager->getDefinitions();
353 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...
354
      $current = $definitions[$id];
355
      $name = $current['name'];
356
357
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
358
        $carry[$name] = [
359
          'id' => $id,
360
          'class' => $current['class'],
361
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
362
        ];
363
      }
364
365
      return $carry;
366
    }, []);
367
368
    return array_map(function ($definition) use ($manager) {
369
      $id = $definition['id'];
370
      $instance = $manager->getInstance(['id' => $id]);
371
372
      return [
373
        'definition' => $instance->getDefinition(),
374
      ] + $definition;
375
    }, $mutations);
376
  }
377
378
}