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