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

SchemaBuilder   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 432
Duplicated Lines 15.97 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 69
loc 432
rs 6.5957
wmc 56
lcom 1
cbo 1

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A addTypeManager() 0 6 1
A hasFields() 0 3 1
A hasMutations() 0 3 1
A hasType() 0 3 1
A getFields() 0 11 2
A getMutations() 0 3 1
A getTypes() 0 5 1
B getType() 0 15 5
A processFields() 0 3 1
A processArguments() 0 7 1
A processType() 0 7 1
A getTypeMap() 0 7 2
A buildType() 8 8 2
A buildField() 8 8 2
A buildMutation() 8 8 2
B buildTypeMap() 9 42 5
A getTypeReferenceMap() 0 7 2
B buildTypeReferenceMap() 13 20 5
A getFieldAssociationMap() 0 7 2
C buildFieldAssociationMap() 0 38 7
A getFieldMap() 0 7 2
A buildFieldMap() 0 19 2
A getMutationMap() 0 7 2
B buildMutationMap() 23 27 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SchemaBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SchemaBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Drupal\graphql\Plugin;
4
5
use Drupal\Component\Plugin\PluginManagerInterface;
6
7
class SchemaBuilder {
8
9
  /**
10
   * @var \Drupal\graphql\Plugin\FieldPluginManager
11
   */
12
  protected $fieldManager;
13
14
  /**
15
   * @var \Drupal\graphql\Plugin\MutationPluginManager
16
   */
17
  protected $mutationManager;
18
19
  /**
20
   * @var \Drupal\graphql\Plugin\TypePluginManager[]
21
   */
22
  protected $typeManagers;
23
24
  /**
25
   * @var array
26
   */
27
  protected $fields;
28
29
  /**
30
   * @var array
31
   */
32
  protected $mutations;
33
34
  /**
35
   * @var array
36
   */
37
  protected $types;
38
39
  /**
40
   * SchemaBuilder constructor.
41
   *
42
   * @param \Drupal\graphql\Plugin\FieldPluginManager $fieldManager
43
   * @param \Drupal\graphql\Plugin\MutationPluginManager $mutationManager
44
   */
45
  public function __construct(
46
    FieldPluginManager $fieldManager,
47
    MutationPluginManager $mutationManager
48
  ) {
49
    $this->fieldManager = $fieldManager;
50
    $this->mutationManager = $mutationManager;
51
  }
52
53
  /**
54
   * Registers a plugin manager.
55
   *
56
   * @param \Drupal\Component\Plugin\PluginManagerInterface $pluginManager
57
   *   The plugin manager to register.
58
   * @param $id
59
   *   The id of the service.
60
   */
61
  public function addTypeManager(PluginManagerInterface $pluginManager, $id) {
62
    $pieces = explode('.', $id);
63
    $type = end($pieces);
64
65
    $this->typeManagers[$type] = $pluginManager;
66
  }
67
68
  /**
69
   * @return bool
70
   */
71
  public function hasFields($parent) {
72
    return isset($this->getFieldAssociationMap()[$parent]);
73
  }
74
75
  /**
76
   * @return bool
77
   */
78
  public function hasMutations() {
79
    return !empty($this->getMutationMap());
80
  }
81
82
  /**
83
   * @return bool
84
   */
85
  public function hasType($name) {
86
    return isset($this->getTypeMap()[$name]);
87
  }
88
89
  /**
90
   * @return array
91
   */
92
  public function getFields($parent) {
93
    $associations = $this->getFieldAssociationMap();
94
    if (isset($associations[$parent])) {
95
      $map = $this->getFieldMap();
96
      return $this->processFields(array_map(function ($id) use ($map) {
97
        return $map[$id];
98
      }, $associations[$parent]));
99
    }
100
101
    return [];
102
  }
103
104
  /**
105
   * @return array
106
   */
107
  public function getMutations() {
108
    return $this->processMutations($this->getMutationMap());
0 ignored issues
show
Bug introduced by
The method processMutations() does not seem to exist on object<Drupal\graphql\Plugin\SchemaBuilder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
109
  }
110
111
  /**
112
   * @return array
113
   */
114
  public function getTypes() {
115
    return array_map(function ($name) {
116
      return $this->getType($name);
117
    }, array_keys($this->getTypeMap()));
118
  }
119
120
  /**
121
   * @param $name
122
   *
123
   * @return mixed
124
   */
125
  public function getType($name) {
126
    $types = $this->getTypeMap();
127
    if (isset($types[$name])) {
128
      return $this->buildType($types[$name]);
129
    }
130
131
    $references = $this->getTypeReferenceMap();
132
    do {
133
      if (isset($references[$name])) {
134
        return $this->buildType($types[$references[$name]]);
135
      }
136
    } while (($pos = strpos($name, ':')) !== FALSE && $name = substr($name, 0, $pos));
137
138
    throw new \LogicException(sprintf('Missing type %s.', $name));
139
  }
140
141
  /**
142
   * @param $fields
143
   *
144
   * @return array
145
   */
146
  public function processFields($fields) {
147
    return array_map([$this, 'buildField'], $fields);
148
  }
149
150
  /**
151
   * @param $args
152
   *
153
   * @return array
154
   */
155
  public function processArguments($args) {
156
    return array_map(function ($arg) {
157
      return [
158
        'type' => $this->processType($arg['type']),
159
      ] + $arg;
160
    }, $args);
161
  }
162
163
  /**
164
   * @param $type
165
   *
166
   * @return mixed
167
   */
168
  public function processType($type) {
169
    list($type, $decorators) = $type;
170
171
    return array_reduce($decorators, function ($type, $decorator) {
172
      return $decorator($type);
173
    }, $this->getType($type));
174
  }
175
176
  /**
177
   * @return array
178
   */
179
  protected function getTypeMap() {
180
    if (!isset($this->typeMap)) {
181
      $this->typeMap = $this->buildTypeMap();
0 ignored issues
show
Bug introduced by
The property typeMap does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
182
    }
183
184
    return $this->typeMap;
185
  }
186
187
  /**
188
   * @param $type
189
   *
190
   * @return mixed
191
   */
192 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...
193
    if (!isset($this->types[$type['id']])) {
194
      $creator = [$type['class'], 'createInstance'];
195
      $this->types[$type['id']] = $creator($this, $this->typeManagers[$type['type']], $type['definition'], $type['id']);
196
    }
197
198
    return $this->types[$type['id']];
199
  }
200
201
  /**
202
   * @param $field
203
   *
204
   * @return mixed
205
   */
206 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...
207
    if (!isset($this->fields[$field['id']])) {
208
      $creator = [$field['class'], 'createInstance'];
209
      $this->fields[$field['id']] = $creator($this, $this->fieldManager, $field['definition'], $field['id']);
210
    }
211
212
    return $this->fields[$field['id']];
213
  }
214
215
  /**
216
   * @param $mutation
217
   *
218
   * @return mixed
219
   */
220 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...
221
    if (!isset($this->mutations[$mutation['id']])) {
222
      $creator = [$mutation['class'], 'createInstance'];
223
      $this->fields[$mutation['id']] = $creator($this, $this->mutationManager, $mutation['definition'], $mutation['id']);
224
    }
225
226
    return $this->mutations[$mutation['id']];
227
  }
228
229
  /**
230
   * @return array
231
   */
232
  protected function buildTypeMap() {
233
    // First collect all definitions by their name, overwriting those with
234
    // lower weights by their higher weighted counterparts. We also collect
235
    // the class from the plugin definition to be able to statically create
236
    // the type instance without loading the plugin managers at all at
237
    // run-time.
238
    $types = array_reduce(array_keys($this->typeManagers), function ($carry, $type) {
239
      $manager = $this->typeManagers[$type];
240
      $definitions = $manager->getDefinitions();
241
242
      return array_reduce(array_keys($definitions), function ($carry, $id) use ($type, $definitions) {
243
        $current = $definitions[$id];
244
        $name = $current['name'];
245
246
        if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
247
          $carry[$name] = [
248
            'type' => $type,
249
            'id' => $id,
250
            'class' => $current['class'],
251
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
252
            'reference' => !empty($current['type']) ? $current['type'] : NULL,
253
          ];
254
        }
255
256
        return $carry;
257
      }, $carry);
258
    }, []);
259
260
    // Retrieve the plugins run-time definitions. These will help us to prevent
261
    // plugin instantiation at run-time unless a plugin is actually called from
262
    // the graphql query execution. Plugins should take care of not having to
263
    // instantiate their plugin instances during schema composition.
264 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...
265
      $manager = $this->typeManagers[$type['type']];
266
      /** @var \Drupal\graphql\Plugin\TypePluginInterface $instance */
267
      $instance = $manager->createInstance($type['id']);
268
269
      return [
270
        'definition' => $instance->getDefinition(),
271
      ] + $type;
272
    }, $types);
273
  }
274
275
  /**
276
   * @return array
277
   */
278
  protected function getTypeReferenceMap() {
279
    if (!isset($this->typeReferenceMap)) {
280
      $this->typeReferenceMap = $this->buildTypeReferenceMap();
0 ignored issues
show
Bug introduced by
The property typeReferenceMap does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
281
    }
282
283
    return $this->typeReferenceMap;
284
  }
285
286
  /**
287
   * @return array
288
   */
289
  protected function buildTypeReferenceMap() {
290
    $types = $this->getTypeMap();
291 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...
292
      $current = $types[$name];
293
      $reference = $current['reference'];
294
295
      if (!empty($reference) && (empty($references[$reference]) || $references[$reference]['weight'] < $current['weight'])) {
296
        $references[$reference] = [
297
          'name' => $name,
298
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
299
        ];
300
      }
301
302
      return $references;
303
    }, []);
304
305
    return array_map(function ($reference) {
306
      return $reference['name'];
307
    }, $references);
308
  }
309
310
  /**
311
   * @return array
312
   */
313
  protected function getFieldAssociationMap() {
314
    if (!isset($this->fieldAssociationMap)) {
315
      $this->fieldAssociationMap = $this->buildFieldAssociationMap();
0 ignored issues
show
Bug introduced by
The property fieldAssociationMap does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
316
    }
317
318
    return $this->fieldAssociationMap;
319
  }
320
321
  /**
322
   * @return array
323
   */
324
  protected function buildFieldAssociationMap() {
325
    $definitions = $this->fieldManager->getDefinitions();
326
327
    $fields = array_reduce(array_keys($definitions), function ($carry, $id) use ($definitions) {
328
      $current = $definitions[$id];
329
      $parents = $current['parents'] ?: ['Root'];
330
331
      return array_reduce($parents, function ($carry, $parent) use ($current, $id) {
332
        // Allow plugins to define a different name for each parent.
333
        if (strpos($parent, ':') !== FALSE) {
334
          list($parent, $name) = explode(':', $parent);
335
        }
336
337
        $name = isset($name) ? $name : $current['name'];
338
        if (empty($carry[$parent][$name]) || $carry[$parent][$name]['weight'] < $current['weight']) {
339
          $carry[$parent][$name] = [
340
            'id' => $id,
341
            'weight' => !empty($current['weight']) ? $current['weight'] : 0,
342
          ];
343
        }
344
345
        return $carry;
346
      }, $carry);
347
    }, []);
348
349
    // Only return fields for types that are actually fieldable.
350
    $fieldable = [GRAPHQL_TYPE_PLUGIN, GRAPHQL_INTERFACE_PLUGIN];
351
    $fields = array_intersect_key($fields, array_filter($this->getTypeMap(), function ($type) use ($fieldable) {
352
      return in_array($type['type'], $fieldable);
353
    }) + ['Root' => NULL]);
354
355
    // We only need the plugin ids in this map.
356
    return array_map(function ($fields) {
357
      return array_map(function ($field) {
358
        return $field['id'];
359
      }, $fields);
360
    }, $fields);
361
  }
362
363
  /**
364
   * @return array
365
   */
366
  protected function getFieldMap() {
367
    if (!isset($this->fieldMap)) {
368
      $this->fieldMap = $this->buildFieldMap();
0 ignored issues
show
Bug introduced by
The property fieldMap does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
369
    }
370
371
    return $this->fieldMap;
372
  }
373
374
  /**
375
   * @return array
376
   */
377
  protected function buildFieldMap() {
378
    $association = $this->getFieldAssociationMap();
379
    return array_reduce($association, function ($carry, $fields) {
380
      return array_reduce($fields, function ($carry, $id) {
381
        if (!isset($carry[$id])) {
382
          $instance = $this->fieldManager->createInstance($id);
383
          $definition = $this->fieldManager->getDefinition($id);
384
385
          $carry[$id] = [
386
            'id' => $id,
387
            'class' => $definition['class'],
388
            'definition' => $instance->getDefinition(),
389
          ];
390
        }
391
392
        return $carry;
393
      }, $carry);
394
    }, []);
395
  }
396
397
  /**
398
   * @return array
399
   */
400
  protected function getMutationMap() {
401
    if (!isset($this->mutationMap)) {
402
      $this->mutationMap = $this->buildMutationMap();
0 ignored issues
show
Bug introduced by
The property mutationMap does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
403
    }
404
405
    return $this->mutationMap;
406
  }
407
408
  /**
409
   * @return array
410
   */
411
  protected function buildMutationMap() {
412
    $mutations = $this->mutationManager->getDefinitions();
413 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...
414
      $current = $mutations[$id];
415
      $name = $current['name'];
416
417
      if (empty($carry[$name]) || $carry[$name]['weight'] < $current['weight']) {
418
        $carry[$name] = [
419
          'id' => $id,
420
          'class' => $current['class'],
421
          'weight' => !empty($current['weight']) ? $current['weight'] : 0,
422
        ];
423
      }
424
425
      return $carry;
426
    }, []);
427
428 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...
429
      $id = $definition['id'];
430
      $instance = $this->mutationManager->createInstance($id);
431
432
      $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...
433
        'id' => $id,
434
        'definition' => $instance->getDefinition(),
435
      ];
436
    }, $mutations);
437
  }
438
}
439