Drupal8   D
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 364
Duplicated Lines 10.99 %

Coupling/Cohesion

Components 1
Dependencies 21

Importance

Changes 0
Metric Value
wmc 44
lcom 1
cbo 21
dl 40
loc 364
rs 4.8474
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A entityLoad() 0 3 1
A entityDelete() 0 3 1
A convertLabelToNodeTypeId() 10 10 2
A convertLabelToTermTypeId() 10 10 2
A loadNodeByName() 10 10 1
A getEntityIdByLabel() 0 17 2
A loadUserByName() 0 5 1
A nodeAccess() 0 4 1
A getNodeId() 0 3 1
A loadTaxonomyTermByName() 10 10 1
A getTaxonomyTermId() 0 3 1
A loadMenuItemByTitle() 0 13 2
B createMenuStructure() 0 32 5
A clearMenuCache() 0 3 1
A invalidateCacheTags() 0 3 1
D entityCreate() 0 42 9
C entityAddTranslation() 0 48 7
A state() 0 3 1
A getEditableConfig() 0 3 1
A getFieldDefinition() 0 5 1
A getStubEntity() 0 3 1
A saveFile() 0 8 1

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 Drupal8 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 Drupal8, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace NuvoleWeb\Drupal\Driver\Cores;
4
5
use function bovigo\assert\assert;
6
use function bovigo\assert\predicate\hasKey;
7
use function bovigo\assert\predicate\isNotEmpty;
8
use function bovigo\assert\predicate\isNotEqualTo;
9
use Drupal\Core\Cache\Cache;
10
use Drupal\Driver\Cores\Drupal8 as OriginalDrupal8;
11
use Drupal\file\Entity\File;
12
use Drupal\menu_link_content\Entity\MenuLinkContent;
13
use Drupal\node\Entity\Node;
14
use Drupal\node\Entity\NodeType;
15
use Drupal\system\Entity\Menu;
16
use Drupal\taxonomy\Entity\Term;
17
use Drupal\taxonomy\Entity\Vocabulary;
18
use NuvoleWeb\Drupal\Driver\Objects\Drupal8\EditableConfig;
19
use NuvoleWeb\Drupal\Driver\Objects\Drupal8\State;
20
21
/**
22
 * Class Drupal8.
23
 *
24
 * @package NuvoleWeb\Drupal\Driver\Cores
25
 */
26
class Drupal8 extends OriginalDrupal8 implements CoreInterface {
27
28
  /**
29
   * {@inheritdoc}
30
   */
31 View Code Duplication
  public function convertLabelToNodeTypeId($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...
32
    // First suppose that the id has been passed.
33
    if (NodeType::load($type)) {
34
      return $type;
35
    }
36
    $storage = \Drupal::entityTypeManager()->getStorage('node_type');
37
    $result = $storage->loadByProperties(['name' => $type]);
38
    assert($result, isNotEmpty());
39
    return key($result);
40
  }
41
42
  /**
43
   * {@inheritdoc}
44
   */
45 View Code Duplication
  public function convertLabelToTermTypeId($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...
46
    // First suppose that the id has been passed.
47
    if (Vocabulary::load($type)) {
48
      return $type;
49
    }
50
    $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary');
51
    $result = $storage->loadByProperties(['name' => $type]);
52
    assert($result, isNotEmpty());
53
    return key($result);
54
  }
55
56
  /**
57
   * {@inheritdoc}
58
   */
59 View Code Duplication
  public function loadNodeByName($title) {
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...
60
    $result = \Drupal::entityQuery('node')
61
      ->condition('title', $title)
62
      ->condition('status', NODE_PUBLISHED)
63
      ->range(0, 1)
64
      ->execute();
65
    assert($result, isNotEmpty());
66
    $nid = current($result);
67
    return Node::load($nid);
68
  }
69
70
  /**
71
   * {@inheritdoc}
72
   */
73
  public function getEntityIdByLabel($entity_type, $bundle, $label) {
74
    /** @var \Drupal\node\NodeStorage $storage */
75
    $storage = \Drupal::entityTypeManager()->getStorage($entity_type);
76
    $bundle_key = $storage->getEntityType()->getKey('bundle');
77
    $label_key = $storage->getEntityType()->getKey('label');
78
79
    $query = \Drupal::entityQuery($entity_type);
80
    if ($bundle) {
81
      $query->condition($bundle_key, $bundle);
82
    }
83
    $query->condition($label_key, $label);
84
    $query->range(0, 1);
85
86
    $result = $query->execute();
87
    assert($result, isNotEmpty(), __METHOD__ . ": No Entity {$entity_type} with name {$label} found.");
88
    return current($result);
89
  }
90
91
  /**
92
   * {@inheritdoc}
93
   */
94
  public function loadUserByName($name) {
95
    $user = user_load_by_name($name);
96
    assert($user, isNotEqualTo(FALSE));
97
    return $user;
98
  }
99
100
  /**
101
   * {@inheritdoc}
102
   */
103
  public function nodeAccess($op, $name, $node) {
104
    $account = $this->loadUserByName($name);
105
    return $node->access($op, $account);
106
  }
107
108
  /**
109
   * {@inheritdoc}
110
   */
111
  public function getNodeId($node) {
112
    return $node->id();
113
  }
114
115
  /**
116
   * {@inheritdoc}
117
   */
118 View Code Duplication
  public function loadTaxonomyTermByName($type, $name) {
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...
119
    $result = \Drupal::entityQuery('taxonomy_term')
120
      ->condition('name', $name)
121
      ->condition('vid', $type)
122
      ->range(0, 1)
123
      ->execute();
124
    assert($result, isNotEmpty());
125
    $id = current($result);
126
    return Term::load($id);
127
  }
128
129
  /**
130
   * {@inheritdoc}
131
   */
132
  public function getTaxonomyTermId($term) {
133
    return $term->id();
134
  }
135
136
  /**
137
   * {@inheritdoc}
138
   */
139
  public function loadMenuItemByTitle($menu_name, $title) {
140
    $items = \Drupal::entityTypeManager()->getStorage('menu_link_content')
141
      ->loadByProperties([
142
        'menu_name' => $menu_name,
143
        'title' => $title,
144
      ]);
145
    if (!empty($items)) {
146
      return array_shift($items);
147
    }
148
    else {
149
      return NULL;
150
    }
151
  }
152
153
  /**
154
   * {@inheritdoc}
155
   */
156
  public function createMenuStructure($menu_name, $menu_items) {
157
    if (!Menu::load($menu_name)) {
158
      throw new \InvalidArgumentException("Menu '{$menu_name}' not found.");
159
    }
160
161
    $weight = 0;
162
    $menu_links = [];
163
    foreach ($menu_items as $menu_item) {
164
      $values = [
165
        'title' => $menu_item['title'],
166
        'link' => ['uri' => $menu_item['uri']],
167
        'menu_name' => $menu_name,
168
        'weight' => $weight++,
169
      ];
170
171
      // Assign parent item.
172
      if ($menu_item['parent']) {
173
        $values['parent'] = $menu_item['parent'];
174
        $parent = $this->loadMenuItemByTitle($menu_name, $menu_item['parent']);
175
        if ($parent) {
176
          $values['parent'] = $parent->getPluginId();
177
        }
178
      }
179
180
      // Create menu link.
181
      $menu_link = MenuLinkContent::create($values);
182
      $menu_link->save();
183
      $menu_links[] = $menu_link;
184
    }
185
186
    return $menu_links;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $menu_links; (array) is incompatible with the return type declared by the interface NuvoleWeb\Drupal\Driver\...ce::createMenuStructure of type object.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
187
  }
188
189
  /**
190
   * {@inheritdoc}
191
   */
192
  public function clearMenuCache() {
193
    \Drupal::cache('menu')->invalidateAll();
194
  }
195
196
  /**
197
   * {@inheritdoc}
198
   */
199
  public function invalidateCacheTags(array $tags) {
200
    Cache::invalidateTags($tags);
201
  }
202
203
  /**
204
   * Create an entity.
205
   *
206
   * @param string $entity_type
207
   *   Entity type.
208
   * @param array $values
209
   *   The Values to create the entity with.
210
   * @param bool $save
211
   *   Indicate whether to directly save the entity or not.
212
   *
213
   * @return EntityInterface
214
   *   Entity object.
215
   */
216
  public function entityCreate($entity_type, $values, $save = TRUE) {
217
    if (!is_array($values)) {
218
      // Cast an object to array to be compatible with nodeCreate().
219
      $values = (array) $values;
220
    }
221
222
    $entity = $this->getStubEntity($entity_type, $values);
223
224
    foreach ($values as $name => $value) {
225
      $definition = $this->getFieldDefinition($entity->getEntityTypeId(), $name);
226
      $settings = $definition->getSettings();
227
      switch ($definition->getType()) {
228
        case 'entity_reference':
229
          if (in_array($settings['target_type'], ['node', 'taxonomy_term'])) {
230
            // @todo: only supports single values for the moment.
231
            $id = $this->getEntityIdByLabel($settings['target_type'], NULL, $value);
232
            $entity->{$name}->setValue($id);
233
          }
234
          break;
235
236
        case 'entity_reference_revisions':
237
          $entities = [];
238
          foreach ($value as $target_values) {
239
            assert($target_values, hasKey('type'), __METHOD__ . ": Required fields 'type' not found.");
240
            $entities[] = $this->entityCreate($settings['target_type'], $target_values, FALSE);
241
          }
242
243
          $entity->{$name}->setValue($entities);
244
          break;
245
246
        case 'image':
247
          $entity->{$name}->setValue(['target_id' => $this->saveFile($value)->id()]);
248
          break;
249
      }
250
    }
251
252
    if ($save) {
253
      $entity->save();
254
    }
255
256
    return $entity;
257
  }
258
259
  /**
260
   * {@inheritdoc}
261
   */
262
  public function entityLoad($entity_type, $entity_id) {
263
    return \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id);
264
  }
265
266
  /**
267
   * {@inheritdoc}
268
   */
269
  public function entityDelete($entity) {
270
    $entity->delete();
271
  }
272
273
  /**
274
   * {@inheritdoc}
275
   */
276
  public function entityAddTranslation($entity, $language, array $values) {
277
    /** @var ContentEntityInterface $translation */
278
    $translation = $this->getStubEntity($entity->getEntityTypeId(), $values);
279
280
    foreach ($values as $name => $value) {
281
      $definition = $this->getFieldDefinition($translation->getEntityTypeId(), $name);
282
      $settings = $definition->getSettings();
283
      $source_values = $entity->get($name)->getValue();
284
      switch ($definition->getType()) {
285
        case 'entity_reference':
286
          if (in_array($settings['target_type'], ['node', 'taxonomy_term'])) {
287
            // @todo: only supports single values for the moment.
288
            $translation->{$name}->setValue($source_values);
289
          }
290
          break;
291
292
        case 'entity_reference_revisions':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
293
294
          // When reference field is translatable then we will need to create
295
          // new entities and reference them.
296
          // @link https://www.drupal.org/node/2461695
297
          if ($definition->isTranslatable()) {
298
            $target_values = [];
299
            foreach ($source_values as $key => $item) {
300
              $_entity = $this->entityCreate($settings['target_type'], $value[$key]);
301
              $target_values[] = [
302
                'target_id' => $_entity->id(),
303
                'target_revision_id' => $_entity->getRevisionId(),
304
              ];
305
            }
306
            $translation->{$name}->setValue($target_values);
307
          }
308
          else {
309
            // Recurse over the referenced entities.
310
            $source = $this->entityLoad($settings['target_type'], $item['target_id']);
0 ignored issues
show
Bug introduced by
The variable $item seems to be defined by a foreach iteration on line 299. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
311
            $this->entityAddTranslation($source, $language, $value[$key]);
0 ignored issues
show
Bug introduced by
The variable $key seems to be defined by a foreach iteration on line 299. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
Bug introduced by
It seems like $source defined by $this->entityLoad($setti...'], $item['target_id']) on line 310 can be null; however, NuvoleWeb\Drupal\Driver\...:entityAddTranslation() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
312
          }
313
          break;
314
      }
315
    }
316
317
    // Add the translation to the entity.
318
    $translation = $entity->addTranslation($language, $translation->toArray());
319
320
    $translation->save();
321
322
    return $translation;
323
  }
324
325
  /**
326
   * {@inheritdoc}
327
   */
328
  public function state() {
329
    return new State();
330
  }
331
332
  /**
333
   * {@inheritdoc}
334
   */
335
  public function getEditableConfig($name) {
336
    return new EditableConfig($name);
337
  }
338
339
  /**
340
   * Get field definition.
341
   *
342
   * @param string $entity_type
343
   *    Entity type machine name.
344
   * @param string $field_name
345
   *    Field machine name.
346
   *
347
   * @return \Drupal\Core\Field\FieldStorageDefinitionInterface
348
   *    Field definition.
349
   */
350
  protected function getFieldDefinition($entity_type, $field_name) {
351
    $definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type);
352
    assert($definitions, hasKey($field_name), __METHOD__ . ": Field '{$field_name}' not found for entity type '{$entity_type}'.");
353
    return $definitions[$field_name];
354
  }
355
356
  /**
357
   * Get stub entity.
358
   *
359
   * @param string $entity_type
360
   *    Entity type.
361
   * @param array $values
362
   *    Entity values.
363
   *
364
   * @return \Drupal\Core\Entity\EntityInterface
365
   *    Entity object.
366
   */
367
  protected function getStubEntity($entity_type, array $values) {
368
    return \Drupal::entityTypeManager()->getStorage($entity_type)->create($values);
369
  }
370
371
  /**
372
   * Save a file and return its id.
373
   *
374
   * @param string $source
375
   *    Source path relative to Drupal installation root.
376
   *
377
   * @return \Drupal\Core\Entity\EntityInterface
378
   *    Saved file object.
379
   */
380
  protected function saveFile($source) {
381
    $name = basename($source);
382
    $path = realpath(DRUPAL_ROOT . '/' . $source);
383
    $uri = file_unmanaged_copy($path, 'public://' . $name, FILE_EXISTS_REPLACE);
384
    $file = File::create(['uri' => $uri]);
385
    $file->save();
386
    return $file;
387
  }
388
389
}
390