Completed
Pull Request — 8.x-3.x (#525)
by Philipp
02:33
created

SchemaBuilder::buildTypeMap()   B

Complexity

Conditions 5
Paths 1

Size

Total Lines 42
Code Lines 23

Duplication

Lines 9
Ratio 21.43 %

Importance

Changes 0
Metric Value
cc 5
eloc 23
c 0
b 0
f 0
nc 1
nop 0
dl 9
loc 42
rs 8.439
1
<?php
2
3
namespace Drupal\graphql\Plugin;
4
5
use Drupal\Component\Plugin\PluginManagerInterface;
6
use Drupal\Core\Cache\Cache;
7
use Drupal\Core\Cache\CacheBackendInterface;
8
9
// TODO: Clean this up.
10
class SchemaBuilder {
11
12
  /**
13
   * @var \Drupal\Core\Cache\CacheBackendInterface
14
   */
15
  protected $cacheBackend;
16
17
  /**
18
   * @var \Drupal\graphql\Plugin\FieldPluginManager
19
   */
20
  protected $fieldManager;
21
22
  /**
23
   * @var \Drupal\graphql\Plugin\MutationPluginManager
24
   */
25
  protected $mutationManager;
26
27
  /**
28
   * @var \Drupal\graphql\Plugin\TypePluginManager[]
29
   */
30
  protected $typeManagers;
31
32
  /**
33
   * @var array
34
   */
35
  protected $cache;
36
37
  /**
38
   * @var array
39
   */
40
  protected $fields;
41
42
  /**
43
   * @var array
44
   */
45
  protected $mutations;
46
47
  /**
48
   * @var array
49
   */
50
  protected $types;
51
52
  /**
53
   * SchemaBuilder constructor.
54
   *
55
   * @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend
56
   * @param \Drupal\graphql\Plugin\FieldPluginManager $fieldManager
57
   * @param \Drupal\graphql\Plugin\MutationPluginManager $mutationManager
58
   */
59
  public function __construct(
60
    CacheBackendInterface $cacheBackend,
61
    FieldPluginManager $fieldManager,
62
    MutationPluginManager $mutationManager
63
  ) {
64
    $this->fieldManager = $fieldManager;
65
    $this->mutationManager = $mutationManager;
66
    $this->cacheBackend = $cacheBackend;
67
  }
68
69
  /**
70
   * Registers a plugin manager.
71
   *
72
   * @param \Drupal\Component\Plugin\PluginManagerInterface $pluginManager
73
   *   The plugin manager to register.
74
   * @param $id
75
   *   The id of the service.
76
   */
77
  public function addTypeManager(PluginManagerInterface $pluginManager, $id) {
78
    $pieces = explode('.', $id);
79
    $type = end($pieces);
80
81
    $this->typeManagers[$type] = $pluginManager;
82
  }
83
84
  /**
85
   * @return bool
86
   */
87
  public function hasFields($parent) {
88
    return isset($this->getFieldAssociationMap()[$parent]);
89
  }
90
91
  /**
92
   * @return bool
93
   */
94
  public function hasMutations() {
95
    return !empty($this->getMutationMap());
96
  }
97
98
  /**
99
   * @return bool
100
   */
101
  public function hasType($name) {
102
    return isset($this->getTypeMap()[$name]);
103
  }
104
105
  /**
106
   * @return array
107
   */
108
  public function getFields($parent) {
109
    $associations = $this->getFieldAssociationMap();
110
    if (isset($associations[$parent])) {
111
      $map = $this->getFieldMap();
112
      return $this->processFields(array_map(function ($id) use ($map) {
113
        return $map[$id];
114
      }, $associations[$parent]));
115
    }
116
117
    return [];
118
  }
119
120
  /**
121
   * @return array
122
   */
123
  public function getMutations() {
124
    return $this->processMutations($this->getMutationMap());
125
  }
126
127
  /**
128
   * @return array
129
   */
130
  public function getTypes() {
131
    return array_map(function ($name) {
132
      return $this->getType($name);
133
    }, array_keys($this->getTypeMap()));
134
  }
135
136
  /**
137
   * @param $name
138
   *
139
   * @return mixed
140
   */
141
  public function getType($name) {
142
    $types = $this->getTypeMap();
143
    if (isset($types[$name])) {
144
      return $this->buildType($types[$name]);
145
    }
146
147
    $references = $this->getTypeReferenceMap();
148
    do {
149
      if (isset($references[$name])) {
150
        return $this->buildType($types[$references[$name]]);
151
      }
152
    } while (($pos = strpos($name, ':')) !== FALSE && $name = substr($name, 0, $pos));
153
154
    throw new \LogicException(sprintf('Missing type %s.', $name));
155
  }
156
157
  /**
158
   * @param $mutations
159
   *
160
   * @return array
161
   */
162
  public function processMutations($mutations) {
163
    return array_map([$this, 'buildMutation'], $mutations);
164
  }
165
166
  /**
167
   * @param $fields
168
   *
169
   * @return array
170
   */
171
  public function processFields($fields) {
172
    return array_map([$this, 'buildField'], $fields);
173
  }
174
175
  /**
176
   * @param $args
177
   *
178
   * @return array
179
   */
180
  public function processArguments($args) {
181
    return array_map(function ($arg) {
182
      return [
183
        'type' => $this->processType($arg['type']),
184
      ] + $arg;
185
    }, $args);
186
  }
187
188
  /**
189
   * @param $type
190
   *
191
   * @return mixed
192
   */
193
  public function processType($type) {
194
    list($type, $decorators) = $type;
195
196
    return array_reduce($decorators, function ($type, $decorator) {
197
      return $decorator($type);
198
    }, $this->getType($type));
199
  }
200
201
  /**
202
   * @param $type
203
   *
204
   * @return mixed
205
   */
206 View Code Duplication
  protected function buildType($type) {
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...
207
    if (!isset($this->types[$type['id']])) {
208
      $creator = [$type['class'], 'createInstance'];
209
      $this->types[$type['id']] = $creator($this, $this->typeManagers[$type['type']], $type['definition'], $type['id']);
210
    }
211
212
    return $this->types[$type['id']];
213
  }
214
215
  /**
216
   * @param $field
217
   *
218
   * @return mixed
219
   */
220 View Code Duplication
  protected function buildField($field) {
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...
221
    if (!isset($this->fields[$field['id']])) {
222
      $creator = [$field['class'], 'createInstance'];
223
      $this->fields[$field['id']] = $creator($this, $this->fieldManager, $field['definition'], $field['id']);
224
    }
225
226
    return $this->fields[$field['id']];
227
  }
228
229
  /**
230
   * @param $mutation
231
   *
232
   * @return mixed
233
   */
234 View Code Duplication
  protected function buildMutation($mutation) {
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...
235
    if (!isset($this->mutations[$mutation['id']])) {
236
      $creator = [$mutation['class'], 'createInstance'];
237
      $this->fields[$mutation['id']] = $creator($this, $this->mutationManager, $mutation['definition'], $mutation['id']);
238
    }
239
240
    return $this->mutations[$mutation['id']];
241
  }
242
243
  /**
244
   * @return array
245
   */
246 View Code Duplication
  protected function getTypeMap() {
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...
247
    if (!isset($this->cache['types'])) {
248
      if (!isset($this->cache) && $this->cacheGet('types') === NULL) {
249
        $this->cacheSet('types', $this->buildTypeMap());
250
      }
251
    }
252
253
    return $this->cache['types'];
254
  }
255
256
  /**
257
   * @return array
258
   */
259
  protected function buildTypeMap() {
260
    // First collect all definitions by their name, overwriting those with
261
    // lower weights by their higher weighted counterparts. We also collect
262
    // the class from the plugin definition to be able to statically create
263
    // the type instance without loading the plugin managers at all at
264
    // run-time.
265
    $types = array_reduce(array_keys($this->typeManagers), function ($carry, $type) {
266
      $manager = $this->typeManagers[$type];
267
      $definitions = $manager->getDefinitions();
268
269
      return array_reduce(array_keys($definitions), function ($carry, $id) use ($type, $definitions) {
270
        $current = $definitions[$id];
271
        $name = $current['name'];
272
273
        if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
274
          $carry[$name] = [
275
            'type' => $type,
276
            'id' => $id,
277
            'class' => $current['class'],
278
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
279
            'reference' => !empty($current['type']) ? $current['type'] : NULL,
280
          ];
281
        }
282
283
        return $carry;
284
      }, $carry);
285
    }, []);
286
287
    // Retrieve the plugins run-time definitions. These will help us to prevent
288
    // plugin instantiation at run-time unless a plugin is actually called from
289
    // the graphql query execution. Plugins should take care of not having to
290
    // instantiate their plugin instances during schema composition.
291 View Code Duplication
    return array_map(function ($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...
292
      $manager = $this->typeManagers[$type['type']];
293
      /** @var \Drupal\graphql\Plugin\TypePluginInterface $instance */
294
      $instance = $manager->getInstance(['id' => $type['id']]);
295
296
      return [
297
        'definition' => $instance->getDefinition(),
298
      ] + $type;
299
    }, $types);
300
  }
301
302
  /**
303
   * @return array
304
   */
305 View Code Duplication
  protected function getTypeReferenceMap() {
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...
306
    if (!isset($this->cache['types:references'])) {
307
      if (!isset($this->cache) && $this->cacheGet('types:references') === NULL) {
308
        $this->cacheSet('types:references', $this->buildTypeReferenceMap());
309
      }
310
    }
311
312
    return $this->cache['types:references'];
313
  }
314
315
  /**
316
   * @return array
317
   */
318
  protected function buildTypeReferenceMap() {
319
    $types = $this->getTypeMap();
320 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...
321
      $current = $types[$name];
322
      $reference = $current['reference'];
323
324
      if (!empty($reference) && (empty($references[$reference]) || $references[$reference]['weight'] < $current['weight'])) {
325
        $references[$reference] = [
326
          'name' => $name,
327
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
328
        ];
329
      }
330
331
      return $references;
332
    }, []);
333
334
    return array_map(function ($reference) {
335
      return $reference['name'];
336
    }, $references);
337
  }
338
339
  /**
340
   * @return array
341
   */
342 View Code Duplication
  protected function getFieldAssociationMap() {
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...
343
    if (!isset($this->cache['fields:associations'])) {
344
      if (!isset($this->cache) && $this->cacheGet('fields:associations') === NULL) {
345
        $this->cacheSet('fields:associations', $this->buildFieldAssociationMap());
346
      }
347
    }
348
349
    return $this->cache['fields:associations'];
350
  }
351
352
  /**
353
   * @return array
354
   */
355
  protected function buildFieldAssociationMap() {
356
    $definitions = $this->fieldManager->getDefinitions();
357
358
    $fields = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions) {
359
      $current = $definitions[$id];
360
      $parents = $current['parents'] ?: ['Root'];
361
362
      return array_reduce($parents, function ($carry, $parent) use ($current, $id) {
363
        // Allow plugins to define a different name for each parent.
364
        if (strpos($parent, ':') !== FALSE) {
365
          list($parent, $name) = explode(':', $parent);
366
        }
367
368
        $name = isset($name) ? $name : $current['name'];
369
        if (empty($carry[$parent][$name]) || $carry[$parent][$name]['weight'] < $current['weight']) {
370
          $carry[$parent][$name] = [
371
            'id' => $id,
372
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
373
          ];
374
        }
375
376
        return $carry;
377
      }, $carry);
378
    }, []);
379
380
    // Only return fields for types that are actually fieldable.
381
    $fieldable = [GRAPHQL_TYPE_PLUGIN, GRAPHQL_INTERFACE_PLUGIN];
382
    $fields = array_intersect_key($fields, array_filter($this->getTypeMap(), function ($type) use ($fieldable) {
383
      return in_array($type['type'], $fieldable);
384
    }) + ['Root' => NULL]);
385
386
    // We only need the plugin ids in this map.
387
    return array_map(function ($fields) {
388
      return array_map(function ($field) {
389
        return $field['id'];
390
      }, $fields);
391
    }, $fields);
392
  }
393
394
  /**
395
   * @return array
396
   */
397 View Code Duplication
  protected function getFieldMap() {
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...
398
    if (!isset($this->cache['fields'])) {
399
      if (!isset($this->cache) && $this->cacheGet('fields') === NULL) {
400
        $this->cacheSet('fields', $this->buildFieldMap());
401
      }
402
    }
403
404
    return $this->cache['fields'];
405
  }
406
407
  /**
408
   * @return array
409
   */
410
  protected function buildFieldMap() {
411
    $association = $this->getFieldAssociationMap();
412
    return array_reduce($association, function ($carry, $fields) {
413
      return array_reduce($fields, function ($carry, $id) {
414
        if (!isset($carry[$id])) {
415
          $instance = $this->fieldManager->getInstance(['id' => $id]);
416
          $definition = $this->fieldManager->getDefinition($id);
417
418
          $carry[$id] = [
419
            'id' => $id,
420
            'class' => $definition['class'],
421
            'definition' => $instance->getDefinition(),
422
          ];
423
        }
424
425
        return $carry;
426
      }, $carry);
427
    }, []);
428
  }
429
430
  /**
431
   * @return array
432
   */
433 View Code Duplication
  protected function getMutationMap() {
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...
434
    if (!isset($this->cache['mutations'])) {
435
      if (!isset($this->cache) && $this->cacheGet('mutations') === NULL) {
436
        $this->cacheSet('mutations', $this->buildMutationMap());
437
      }
438
    }
439
440
    return $this->cache['mutations'];
441
  }
442
443
  /**
444
   * @return array
445
   */
446
  protected function buildMutationMap() {
447
    $mutations = $this->mutationManager->getDefinitions();
448 View Code Duplication
    $mutations = array_reduce(array_keys($mutations), function ($carry, $id) use ($mutations) {
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...
449
      $current = $mutations[$id];
450
      $name = $current['name'];
451
452
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
453
        $carry[$name] = [
454
          'id' => $id,
455
          'class' => $current['class'],
456
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
457
        ];
458
      }
459
460
      return $carry;
461
    }, []);
462
463 View Code Duplication
    return array_map(function ($definition) {
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...
464
      $id = $definition['id'];
465
      $instance = $this->mutationManager->getInstance(['id' => $id]);
466
467
      $carry[$id] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$carry was never initialized. Although not strictly required by PHP, it is generally a good practice to add $carry = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
468
        'id' => $id,
469
        'definition' => $instance->getDefinition(),
470
      ];
471
    }, $mutations);
472
  }
473
474
  /**
475
   * @param $id
476
   * @param $data
477
   */
478
  protected function cacheSet($id, $data) {
479
    $this->cacheBackend->set("builder:$id", $data, Cache::PERMANENT);
480
    $this->cache[$id] = $data;
481
  }
482
483
  /**
484
   * @param $id
485
   *
486
   * @return mixed
487
   */
488
  protected function cacheGet($id) {
489
    $keys = [
490
      'builder:types' => 'types',
491
      'builder:types:references' => 'types:references',
492
      'builder:fields' => 'fields',
493
      'builder:fields:associations' => 'fields:associations',
494
      'builder:mutations' => 'mutations',
495
    ];
496
497
    $ids = array_keys($keys);
498
    $result = $this->cacheBackend->getMultiple($ids);
499
500
    $this->cache = [];
501
    foreach ($result as $key => $data) {
502
      $this->cache[$keys[$key]] = $data->data;
503
    }
504
505
    return $this->cache[$id];
506
  }
507
}
508