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

SchemaBuilder::processType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 7
rs 9.4285
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->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 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...
294
      $manager = $this->typeManagers[$type['type']];
295
      /** @var \Drupal\graphql\Plugin\TypePluginInterface $instance */
296
      $instance = $manager->getInstance(['id' => $type['id']]);
297
298
      return [
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 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...
404
    if (!isset($this->cache['fields'])) {
405
      if (!isset($this->cache) && ($result = $this->cacheGet('fields')) !== NULL) {
406
        return $result;
407
      }
408
409
      $this->cacheSet('fields', $this->buildFieldMap());
410
    }
411
412
    return $this->cache['fields'];
413
  }
414
415
  /**
416
   * @return array
417
   */
418
  protected function buildFieldMap() {
419
    $association = $this->getFieldAssociationMap();
420
    return array_reduce($association, function ($carry, $fields) {
421
      return array_reduce($fields, function ($carry, $id) {
422
        if (!isset($carry[$id])) {
423
          $instance = $this->fieldManager->getInstance(['id' => $id]);
424
          $definition = $this->fieldManager->getDefinition($id);
425
426
          $carry[$id] = [
427
            'id' => $id,
428
            'class' => $definition['class'],
429
            'definition' => $instance->getDefinition(),
430
          ];
431
        }
432
433
        return $carry;
434
      }, $carry);
435
    }, []);
436
  }
437
438
  /**
439
   * @return array
440
   */
441 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...
442
    if (!isset($this->cache['mutations'])) {
443
      if (!isset($this->cache) && ($result = $this->cacheGet('mutations')) !== NULL) {
444
        return $result;
445
      }
446
447
      $this->cacheSet('mutations', $this->buildMutationMap());
448
    }
449
450
    return $this->cache['mutations'];
451
  }
452
453
  /**
454
   * @return array
455
   */
456
  protected function buildMutationMap() {
457
    $mutations = $this->mutationManager->getDefinitions();
458 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...
459
      $current = $mutations[$id];
460
      $name = $current['name'];
461
462
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
463
        $carry[$name] = [
464
          'id' => $id,
465
          'class' => $current['class'],
466
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
467
        ];
468
      }
469
470
      return $carry;
471
    }, []);
472
473 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...
474
      $id = $definition['id'];
475
      $instance = $this->mutationManager->getInstance(['id' => $id]);
476
      return [
477
        'definition' => $instance->getDefinition(),
478
      ] + $definition;
479
    }, $mutations);
480
  }
481
482
  /**
483
   * @param $id
484
   * @param $data
485
   */
486
  protected function cacheSet($id, $data) {
487
    $this->cacheBackend->set("builder:$id", $data, Cache::PERMANENT);
488
    $this->cache[$id] = $data;
489
  }
490
491
  /**
492
   * @param $id
493
   *
494
   * @return mixed
495
   */
496
  protected function cacheGet($id) {
497
    $keys = [
498
      'builder:types' => 'types',
499
      'builder:types:references' => 'types:references',
500
      'builder:fields' => 'fields',
501
      'builder:fields:associations' => 'fields:associations',
502
      'builder:mutations' => 'mutations',
503
    ];
504
505
    $ids = array_keys($keys);
506
    $result = $this->cacheBackend->getMultiple($ids);
507
508
    $this->cache = [];
509
    foreach ($result as $key => $data) {
510
      $this->cache[$keys[$key]] = $data->data;
511
    }
512
513
    return isset($this->cache[$id]) ? $this->cache[$id] : NULL;
514
  }
515
}
516