Completed
Pull Request — 8.x-3.x (#525)
by Sebastian
02:17 queued 12s
created

SchemaBuilder::addTypeManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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