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

SchemaBuilder::hasType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
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) && ($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
477
      $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...
478
        'id' => $id,
479
        'definition' => $instance->getDefinition(),
480
      ];
481
    }, $mutations);
482
  }
483
484
  /**
485
   * @param $id
486
   * @param $data
487
   */
488
  protected function cacheSet($id, $data) {
489
    $this->cacheBackend->set("builder:$id", $data, Cache::PERMANENT);
490
    $this->cache[$id] = $data;
491
  }
492
493
  /**
494
   * @param $id
495
   *
496
   * @return mixed
497
   */
498
  protected function cacheGet($id) {
499
    $keys = [
500
      'builder:types' => 'types',
501
      'builder:types:references' => 'types:references',
502
      'builder:fields' => 'fields',
503
      'builder:fields:associations' => 'fields:associations',
504
      'builder:mutations' => 'mutations',
505
    ];
506
507
    $ids = array_keys($keys);
508
    $result = $this->cacheBackend->getMultiple($ids);
509
510
    $this->cache = [];
511
    foreach ($result as $key => $data) {
512
      $this->cache[$keys[$key]] = $data->data;
513
    }
514
515
    return isset($this->cache[$id]) ? $this->cache[$id] : NULL;
516
  }
517
}
518