Completed
Pull Request — 8.x-3.x (#525)
by Sebastian
06:05
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
   * @var
20
   */
21
  private $basePluginId;
22
23
  protected $fieldManager;
24
25
  protected $mutationManager;
26
27
  protected $typeManagers;
28
29
  /**
30
   * {@inheritdoc}
31
   */
32
  public static function create(ContainerInterface $container, $basePluginId) {
33
    return new static(
34
      $basePluginId,
35
      $container->get('plugin.manager.graphql.field'),
36
      $container->get('plugin.manager.graphql.mutation'),
37
      $container->get('graphql.type_manager_aggregator')
38
    );
39
  }
40
41
  /**
42
   * SchemaPluginBase constructor.
43
   *
44
   * @param $basePluginId
45
   * @param \Drupal\graphql\Plugin\FieldPluginManager $fieldManager
46
   * @param \Drupal\graphql\Plugin\MutationPluginManager $mutationManager
47
   * @param \Drupal\graphql\Plugin\TypePluginManagerAggregator $typeManagers
48
   */
49 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...
50
    $basePluginId,
51
    FieldPluginManager $fieldManager,
52
    MutationPluginManager $mutationManager,
53
    TypePluginManagerAggregator $typeManagers
54
  ) {
55
    $this->basePluginId = $basePluginId;
56
    $this->fieldManager = $fieldManager;
57
    $this->mutationManager = $mutationManager;
58
    $this->typeManagers = $typeManagers;
59
  }
60
61
  /**
62
   * {@inheritdoc}
63
   */
64
  public function getDerivativeDefinitions($basePluginDefinition) {
65
    // Construct the optimized data representation for building the schema.
66
    $typeMap = $this->buildTypeMap(iterator_to_array($this->typeManagers));
67
    $typeReferenceMap = $this->buildTypeReferenceMap($typeMap);
68
    $typeAssocationMap = $this->buildTypeAssociationMap($typeMap);
69
    $fieldAssocationMap = $this->buildFieldAssociationMap($this->fieldManager, $typeMap);
70
    $fieldMap = $this->buildFieldMap($this->fieldManager, $fieldAssocationMap);
71
    $mutationMap = $this->buildMutationMap($this->mutationManager);
72
73
    $managers = array_merge([$this->fieldManager, $this->mutationManager], iterator_to_array($this->typeManagers));
74
    $cacheTags = array_reduce($managers, function ($carry, CacheableDependencyInterface $current) {
75
      return Cache::mergeTags($carry, $current->getCacheTags());
76
    }, []);
77
78
    $cacheContexts = array_reduce($managers, function ($carry, CacheableDependencyInterface $current) {
79
      return Cache::mergeContexts($carry, $current->getCacheContexts());
80
    }, []);
81
82
    $cacheMaxAge = array_reduce($managers, function ($carry, CacheableDependencyInterface $current) {
83
      return Cache::mergeMaxAges($carry, $current->getCacheMaxAge());
84
    }, Cache::PERMANENT);
85
86
    $this->derivatives[$this->basePluginId] = [
87
      'type_map' => $typeMap,
88
      'type_reference_map' => $typeReferenceMap,
89
      'type_association_map' => $typeAssocationMap,
90
      'field_association_map' => $fieldAssocationMap,
91
      'field_map' => $fieldMap,
92
      'mutation_map' => $mutationMap,
93
      'schema_cache_tags' => $cacheTags,
94
      'schema_cache_contexts' => $cacheContexts,
95
      'schema_cache_max_age' => $cacheMaxAge,
96
    ] + $basePluginDefinition;
97
98
    return $this->derivatives;
99
  }
100
101
  /**
102
   * @return array
103
   */
104
  protected function buildTypeMap(array $managers) {
105
    // First collect all definitions by their name, overwriting those with
106
    // lower weights by their higher weighted counterparts. We also collect
107
    // the class from the plugin definition to be able to statically create
108
    // the type instance without loading the plugin managers at all at
109
    // run-time.
110
    $types = array_reduce(array_keys($managers), function ($carry, $type) use ($managers) {
111
      $manager = $managers[$type];
112
      $definitions = $manager->getDefinitions();
113
114
      return array_reduce(array_keys($definitions), function ($carry, $id) use ($type, $definitions) {
115
        $current = $definitions[$id];
116
        $name = $current['name'];
117
118
        if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
119
          $carry[$name] = [
120
            'type' => $type,
121
            'id' => $id,
122
            'class' => $current['class'],
123
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
124
            'reference' => !empty($current['type']) ? $current['type'] : NULL,
125
          ];
126
        }
127
128
        return $carry;
129
      }, $carry);
130
    }, []);
131
132
    // Retrieve the plugins run-time definitions. These will help us to prevent
133
    // plugin instantiation at run-time unless a plugin is actually called from
134
    // the graphql query execution. Plugins should take care of not having to
135
    // instantiate their plugin instances during schema composition.
136
    return array_map(function ($type) use ($managers) {
137
      $manager = $managers[$type['type']];
138
      /** @var \Drupal\graphql\Plugin\TypePluginInterface $instance */
139
      $instance = $manager->getInstance(['id' => $type['id']]);
140
141
      return $type + [
142
        'definition' => $instance->getDefinition(),
143
      ] + $type;
144
    }, $types);
145
  }
146
147
  /**
148
   * @return array
149
   */
150
  protected function buildTypeReferenceMap(array $types) {
151 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...
152
      $current = $types[$name];
153
      $reference = $current['reference'];
154
155
      if (!empty($reference) && (empty($references[$reference]) || $references[$reference]['weight'] < $current['weight'])) {
156
        $references[$reference] = [
157
          'name' => $name,
158
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
159
        ];
160
      }
161
162
      return $references;
163
    }, []);
164
165
    return array_map(function ($reference) {
166
      return $reference['name'];
167
    }, $references);
168
  }
169
170
  /**
171
   * @return array
172
   */
173
  protected function buildFieldAssociationMap(FieldPluginManager $manager, $types) {
174
    $definitions = $manager->getDefinitions();
175
    $fields = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions) {
176
      $current = $definitions[$id];
177
      $parents = $current['parents'] ?: ['Root'];
178
179
      return array_reduce($parents, function ($carry, $parent) use ($current, $id) {
180
        // Allow plugins to define a different name for each parent.
181
        if (strpos($parent, ':') !== FALSE) {
182
          list($parent, $name) = explode(':', $parent);
183
        }
184
185
        $name = isset($name) ? $name : $current['name'];
186
        if (empty($carry[$parent][$name]) || $carry[$parent][$name]['weight'] < $current['weight']) {
187
          $carry[$parent][$name] = [
188
            'id' => $id,
189
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
190
          ];
191
        }
192
193
        return $carry;
194
      }, $carry);
195
    }, []);
196
197
    // Only return fields for types that are actually fieldable.
198
    $fieldable = [GRAPHQL_TYPE_PLUGIN, GRAPHQL_INTERFACE_PLUGIN];
199
    $fields = array_intersect_key($fields, array_filter($types, function ($type) use ($fieldable) {
200
      return in_array($type['type'], $fieldable);
201
    }) + ['Root' => NULL]);
202
203
    // We only need the plugin ids in this map.
204
    return array_map(function ($fields) {
205
      return array_map(function ($field) {
206
        return $field['id'];
207
      }, $fields);
208
    }, $fields);
209
  }
210
211
  /**
212
   * @return array
213
   */
214
  protected function buildTypeAssociationMap(array $types) {
215
    $assocations = array_filter(array_map(function ($type) use ($types) {
216
      // If this is an object type, just return a mapping for it's interfaces.
217
      if ($type['type'] === 'type') {
218
        return array_map(function () use ($type) {
219
          return [$type['definition']['name']];
220
        }, array_flip($type['definition']['interfaces']));
221
      }
222
223
      // For interfaces, find all object types that declare to implement it.
224
      if ($type['type'] === 'interface') {
225
        return [$type['definition']['name'] => array_values(array_map(function ($type) {
226
          return $type['definition']['name'];
227 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...
228
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['interfaces']);
229
        })))];
230
      }
231
232
      // Union types combine the two approaches above.
233
      if ($type['type'] === 'union') {
234
        $explicit = $type['definition']['types'];
235
236
        $implicit = array_values(array_map(function ($type) {
237
          return $type['definition']['name'];
238 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...
239
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['unions']);
240
        })));
241
242
        return [$type['definition']['name'] => array_merge($explicit, $implicit)];
243
      }
244
245
      return [];
246
    }, $types));
247
248
    $assocations = array_map('array_unique', array_reduce($assocations, 'array_merge_recursive', []));
249
    $assocations = array_map(function ($parent) use ($types) {
250
      $children = array_map(function ($child) use ($types) {
251
        return $types[$child] + ['name' => $child];
252
      }, $parent);
253
254
      uasort($children,[SortArray::class, 'sortByWeightElement']);
255
      $children = array_reverse($children);
256
257
      return array_map(function ($child) {
258
        return $child['name'];
259
      }, $children);
260
    }, $assocations);
261
262
    return $assocations;
263
  }
264
265
  /**
266
   * @return array
267
   */
268
  protected function buildFieldMap(FieldPluginManager $manager, $association) {
269
    return array_reduce($association, function ($carry, $fields) use ($manager) {
270
      return array_reduce($fields, function ($carry, $id) use ($manager) {
271
        if (!isset($carry[$id])) {
272
          $instance = $manager->getInstance(['id' => $id]);
273
          $definition = $manager->getDefinition($id);
274
275
          $carry[$id] = [
276
            'id' => $id,
277
            'class' => $definition['class'],
278
            'definition' => $instance->getDefinition(),
279
          ];
280
        }
281
282
        return $carry;
283
      }, $carry);
284
    }, []);
285
  }
286
287
  /**
288
   * @return array
289
   */
290
  protected function buildMutationMap(MutationPluginManager $manager) {
291
    $definitions = $manager->getDefinitions();
292 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...
293
      $current = $definitions[$id];
294
      $name = $current['name'];
295
296
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
297
        $carry[$name] = [
298
          'id' => $id,
299
          'class' => $current['class'],
300
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
301
        ];
302
      }
303
304
      return $carry;
305
    }, []);
306
307
    return array_map(function ($definition) use ($manager) {
308
      $id = $definition['id'];
309
      $instance = $manager->getInstance(['id' => $id]);
310
311
      return [
312
        'definition' => $instance->getDefinition(),
313
      ] + $definition;
314
    }, $mutations);
315
  }
316
317
}