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

SchemaBuilder::buildFieldAssociationMap()   C

Complexity

Conditions 7
Paths 1

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 24
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 38
rs 6.7272
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 \Drupal\graphql\Plugin\GraphQL\Types\TypePluginBase
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->mutations[$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) && ($result = $this->cacheGet('types')) !== NULL) {
249
        return $result;
250
      }
251
252
      $this->cacheSet('types', $this->buildTypeMap());
253
    }
254
255
    return $this->cache['types'];
256
  }
257
258
  /**
259
   * @return array
260
   */
261
  protected function buildTypeMap() {
262
    // First collect all definitions by their name, overwriting those with
263
    // lower weights by their higher weighted counterparts. We also collect
264
    // the class from the plugin definition to be able to statically create
265
    // the type instance without loading the plugin managers at all at
266
    // run-time.
267
    $types = array_reduce(array_keys($this->typeManagers), function ($carry, $type) {
268
      $manager = $this->typeManagers[$type];
269
      $definitions = $manager->getDefinitions();
270
271
      return array_reduce(array_keys($definitions), function ($carry, $id) use ($type, $definitions) {
272
        $current = $definitions[$id];
273
        $name = $current['name'];
274
275
        if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
276
          $carry[$name] = [
277
            'type' => $type,
278
            'id' => $id,
279
            'class' => $current['class'],
280
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
281
            'reference' => !empty($current['type']) ? $current['type'] : NULL,
282
          ];
283
        }
284
285
        return $carry;
286
      }, $carry);
287
    }, []);
288
289
    // Retrieve the plugins run-time definitions. These will help us to prevent
290
    // plugin instantiation at run-time unless a plugin is actually called from
291
    // the graphql query execution. Plugins should take care of not having to
292
    // instantiate their plugin instances during schema composition.
293
    return array_map(function ($type) {
294
      $manager = $this->typeManagers[$type['type']];
295
      /** @var \Drupal\graphql\Plugin\TypePluginInterface $instance */
296
      $instance = $manager->getInstance(['id' => $type['id']]);
297
298
      return $type + [
299
        'definition' => $instance->getDefinition(),
300
      ] + $type;
301
    }, $types);
302
  }
303
304
  /**
305
   * @return array
306
   */
307 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...
308
    if (!isset($this->cache['types:references'])) {
309
      if (!isset($this->cache) && ($result = $this->cacheGet('types:references')) !== NULL) {
310
        return $result;
311
      }
312
313
      $this->cacheSet('types:references', $this->buildTypeReferenceMap());
314
    }
315
316
    return $this->cache['types:references'];
317
  }
318
319
  /**
320
   * @return array
321
   */
322
  protected function buildTypeReferenceMap() {
323
    $types = $this->getTypeMap();
324 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...
325
      $current = $types[$name];
326
      $reference = $current['reference'];
327
328
      if (!empty($reference) && (empty($references[$reference]) || $references[$reference]['weight'] < $current['weight'])) {
329
        $references[$reference] = [
330
          'name' => $name,
331
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
332
        ];
333
      }
334
335
      return $references;
336
    }, []);
337
338
    return array_map(function ($reference) {
339
      return $reference['name'];
340
    }, $references);
341
  }
342
343
  /**
344
   * @return array
345
   */
346 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...
347
    if (!isset($this->cache['fields:associations'])) {
348
      if (!isset($this->cache) && ($result = $this->cacheGet('fields:associations')) !== NULL) {
349
        return $result;
350
      }
351
352
      $this->cacheSet('fields:associations', $this->buildFieldAssociationMap());
353
    }
354
355
    return $this->cache['fields:associations'];
356
  }
357
358
  /**
359
   * @return array
360
   */
361
  protected function buildFieldAssociationMap() {
362
    $definitions = $this->fieldManager->getDefinitions();
363
364
    $fields = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions) {
365
      $current = $definitions[$id];
366
      $parents = $current['parents'] ?: ['Root'];
367
368
      return array_reduce($parents, function ($carry, $parent) use ($current, $id) {
369
        // Allow plugins to define a different name for each parent.
370
        if (strpos($parent, ':') !== FALSE) {
371
          list($parent, $name) = explode(':', $parent);
372
        }
373
374
        $name = isset($name) ? $name : $current['name'];
375
        if (empty($carry[$parent][$name]) || $carry[$parent][$name]['weight'] < $current['weight']) {
376
          $carry[$parent][$name] = [
377
            'id' => $id,
378
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
379
          ];
380
        }
381
382
        return $carry;
383
      }, $carry);
384
    }, []);
385
386
    // Only return fields for types that are actually fieldable.
387
    $fieldable = [GRAPHQL_TYPE_PLUGIN, GRAPHQL_INTERFACE_PLUGIN];
388
    $fields = array_intersect_key($fields, array_filter($this->getTypeMap(), function ($type) use ($fieldable) {
389
      return in_array($type['type'], $fieldable);
390
    }) + ['Root' => NULL]);
391
392
    // We only need the plugin ids in this map.
393
    return array_map(function ($fields) {
394
      return array_map(function ($field) {
395
        return $field['id'];
396
      }, $fields);
397
    }, $fields);
398
  }
399
400
  /**
401
   * @return array
402
   */
403 View Code Duplication
  protected function getTypeAssociationMap() {
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...
404
    if (!isset($this->cache['types:associations'])) {
405
      if (!isset($this->cache) && ($result = $this->cacheGet('types:associations')) !== NULL) {
406
        return $result;
407
      }
408
409
      $this->cacheSet('types:associations', $this->buildTypeAssociationMap());
410
    }
411
412
    return $this->cache['types:associations'];
413
  }
414
415
  /**
416
   * @return array
417
   */
418
  protected function buildTypeAssociationMap() {
419
    $types = $this->getTypeMap();
420
    $assocations = array_filter(array_map(function ($type) use ($types) {
421
      // If this is an object type, just return a mapping for it's interfaces.
422
      if ($type['type'] === 'type') {
423
        return array_map(function () use ($type) {
424
          return [$type['definition']['name']];
425
        }, array_flip($type['definition']['interfaces']));
426
      }
427
428
      // For interfaces, find all object types that declare to implement it.
429
      if ($type['type'] === 'interface') {
430
        return [$type['definition']['name'] => array_values(array_map(function ($type) {
431
          return $type['definition']['name'];
432 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...
433
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['interfaces']);
434
        })))];
435
      }
436
437
      // Union types combine the two approaches above.
438
      if ($type['type'] === 'union') {
439
        $explicit = $type['definition']['types'];
440
441
        $implicit = array_values(array_map(function ($type) {
442
          return $type['definition']['name'];
443 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...
444
          return $subType['type'] === 'type' && in_array($type['definition']['name'], $subType['definition']['unions']);
445
        })));
446
447
        return [$type['definition']['name'] => array_merge($explicit, $implicit)];
448
      }
449
450
      return [];
451
    }, $types));
452
453
    return array_map('array_unique', array_reduce($assocations, 'array_merge_recursive', []));
454
  }
455
456
  /**
457
   * Retrieve the list of derivatives associated with a composite type.
458
   *
459
   * @return string[]
460
   *   The list of possible sub typenames.
461
   */
462
  public function getSubTypes($name) {
463
    $types = $this->getTypeAssociationMap();
464
    return isset($types[$name]) ? $types[$name] : [];
465
  }
466
467
  /**
468
   * Resolve the matching type.
469
   */
470
  public function resolveType($name, $value, $context, $info) {
471
    $types = $this->getTypeMap();
472
    $associations = $this->getTypeAssociationMap();
473
    if (!isset($associations[$name])) {
474
      return NULL;
475
    }
476
477
    foreach ($associations[$name] as $type) {
478
      // TODO: Avoid loading the type for the check. Make it static!
479
      if (array_key_exists($type, $types) && $instance = $this->buildType($types[$type])) {
480
        if ($instance->isTypeOf($value, $context, $info)) {
481
          return $instance;
482
        }
483
      }
484
    }
485
486
    return NULL;
487
  }
488
489
490
  /**
491
   * @return array
492
   */
493 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...
494
    if (!isset($this->cache['fields'])) {
495
      if (!isset($this->cache) && ($result = $this->cacheGet('fields')) !== NULL) {
496
        return $result;
497
      }
498
499
      $this->cacheSet('fields', $this->buildFieldMap());
500
    }
501
502
    return $this->cache['fields'];
503
  }
504
505
  /**
506
   * @return array
507
   */
508
  protected function buildFieldMap() {
509
    $association = $this->getFieldAssociationMap();
510
    return array_reduce($association, function ($carry, $fields) {
511
      return array_reduce($fields, function ($carry, $id) {
512
        if (!isset($carry[$id])) {
513
          $instance = $this->fieldManager->getInstance(['id' => $id]);
514
          $definition = $this->fieldManager->getDefinition($id);
515
516
          $carry[$id] = [
517
            'id' => $id,
518
            'class' => $definition['class'],
519
            'definition' => $instance->getDefinition(),
520
          ];
521
        }
522
523
        return $carry;
524
      }, $carry);
525
    }, []);
526
  }
527
528
  /**
529
   * @return array
530
   */
531 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...
532
    if (!isset($this->cache['mutations'])) {
533
      if (!isset($this->cache) && ($result = $this->cacheGet('mutations')) !== NULL) {
534
        return $result;
535
      }
536
537
      $this->cacheSet('mutations', $this->buildMutationMap());
538
    }
539
540
    return $this->cache['mutations'];
541
  }
542
543
  /**
544
   * @return array
545
   */
546
  protected function buildMutationMap() {
547
    $definitions = $this->mutationManager->getDefinitions();
548 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...
549
      $current = $definitions[$id];
550
      $name = $current['name'];
551
552
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
553
        $carry[$name] = [
554
          'id' => $id,
555
          'class' => $current['class'],
556
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
557
        ];
558
      }
559
560
      return $carry;
561
    }, []);
562
563
    return array_map(function ($definition) {
564
      $id = $definition['id'];
565
      $instance = $this->mutationManager->getInstance(['id' => $id]);
566
      return [
567
        'definition' => $instance->getDefinition(),
568
      ] + $definition;
569
    }, $mutations);
570
  }
571
572
  /**
573
   * @param $id
574
   * @param $data
575
   */
576
  protected function cacheSet($id, $data) {
577
    $this->cacheBackend->set("builder:$id", $data, Cache::PERMANENT);
578
    $this->cache[$id] = $data;
579
  }
580
581
  /**
582
   * @param $id
583
   *
584
   * @return mixed
585
   */
586
  protected function cacheGet($id) {
587
    $keys = [
588
      'builder:types' => 'types',
589
      'builder:types:references' => 'types:references',
590
      'builder:fields' => 'fields',
591
      'builder:fields:associations' => 'fields:associations',
592
      'builder:mutations' => 'mutations',
593
    ];
594
595
    $ids = array_keys($keys);
596
    $result = $this->cacheBackend->getMultiple($ids);
597
598
    $this->cache = [];
599
    foreach ($result as $key => $data) {
600
      $this->cache[$keys[$key]] = $data->data;
601
    }
602
603
    return isset($this->cache[$id]) ? $this->cache[$id] : NULL;
604
  }
605
}
606