DrushDriver   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 447
Duplicated Lines 3.36 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 4
dl 15
loc 447
rs 6.96
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 4
A getRandom() 0 3 1
A bootstrap() 0 15 4
A isLegacyDrush() 0 12 2
A isBootstrapped() 0 3 1
A userCreate() 0 18 5
A parseUserId() 0 9 2
A userDelete() 0 8 1
A userAddRole() 0 7 1
A fetchWatchdog() 0 8 1
A clearCache() 0 13 5
A clearStaticCaches() 0 4 1
A decodeJsonObject() 0 7 1
A createEntity() 8 9 1
A entityDelete() 7 8 1
A createNode() 0 13 3
A nodeDelete() 0 3 1
A createTerm() 0 8 1
A termDelete() 0 3 1
A isField() 0 16 2
A setArguments() 0 3 1
A getArguments() 0 3 1
A parseArguments() 0 12 3
B drush() 0 36 6
A processBatch() 0 4 1
A runCron() 0 3 1
A __call() 0 3 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 DrushDriver 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 DrushDriver, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Drupal\Driver;
4
5
use Drupal\Component\Utility\Random;
6
use Drupal\Driver\Exception\BootstrapException;
7
8
use Symfony\Component\Process\Process;
9
10
/**
11
 * Implements DriverInterface.
12
 */
13
class DrushDriver extends BaseDriver {
14
  /**
15
   * Store a drush alias for tests requiring shell access.
16
   *
17
   * @var string
18
   */
19
  public $alias;
20
21
  /**
22
   * Stores the root path to a Drupal installation.
23
   *
24
   * This is an alternative to using drush aliases.
25
   *
26
   * @var string
27
   */
28
  public $root;
29
30
  /**
31
   * Store the path to drush binary.
32
   *
33
   * @var string
34
   */
35
  public $binary;
36
37
  /**
38
   * Track bootstrapping.
39
   *
40
   * @var bool
41
   */
42
  private $bootstrapped = FALSE;
43
44
  /**
45
   * Random generator.
46
   *
47
   * @var \Drupal\Component\Utility\Random
48
   */
49
  private $random;
50
51
  /**
52
   * Global arguments or options for drush commands.
53
   *
54
   * @var string
55
   */
56
  private $arguments = '';
57
58
  /**
59
   * Tracks legacy drush.
60
   *
61
   * @var bool
62
   */
63
  protected static $isLegacyDrush;
64
65
  /**
66
   * Set drush alias or root path.
67
   *
68
   * @param string $alias
69
   *   A drush alias.
70
   * @param string $root_path
71
   *   The root path of the Drupal install. This is an alternative to using
72
   *   aliases.
73
   * @param string $binary
74
   *   The path to the drush binary.
75
   * @param \Drupal\Component\Utility\Random $random
76
   *   Random generator.
77
   *
78
   * @throws \Drupal\Driver\Exception\BootstrapException
79
   *   Thrown when a required parameter is missing.
80
   */
81
  public function __construct($alias = NULL, $root_path = NULL, $binary = 'drush', Random $random = NULL) {
82
    if (!empty($alias)) {
83
      // Trim off the '@' symbol if it has been added.
84
      $alias = ltrim($alias, '@');
85
86
      $this->alias = $alias;
87
    }
88
    elseif (!empty($root_path)) {
89
      $this->root = realpath($root_path);
90
    }
91
    else {
92
      throw new BootstrapException('A drush alias or root path is required.');
93
    }
94
95
    $this->binary = $binary;
96
97
    if (!isset($random)) {
98
      $random = new Random();
99
    }
100
    $this->random = $random;
101
  }
102
103
  /**
104
   * {@inheritdoc}
105
   */
106
  public function getRandom() {
107
    return $this->random;
108
  }
109
110
  /**
111
   * {@inheritdoc}
112
   */
113
  public function bootstrap() {
114
    // Check that the given alias works.
115
    // @todo check that this is a functioning alias.
116
    // See http://drupal.org/node/1615450
117
    if (!isset($this->alias) && !isset($this->root)) {
118
      throw new BootstrapException('A drush alias or root path is required.');
119
    }
120
121
    // Determine if drush version is legacy.
122
    if (!isset(self::$isLegacyDrush)) {
123
      self::$isLegacyDrush = $this->isLegacyDrush();
124
    }
125
126
    $this->bootstrapped = TRUE;
127
  }
128
129
  /**
130
   * Determine if drush is a legacy version.
131
   *
132
   * @return bool
133
   *   Returns TRUE if drush is older than drush 9.
134
   */
135
  protected function isLegacyDrush() {
136
    try {
137
      // Try for a drush 9 version.
138
      $version = trim($this->drush('version', [], ['format' => 'string']));
139
      return version_compare($version, '9', '<=');
140
    }
141
    catch (\RuntimeException $e) {
142
      // The version of drush is old enough that only `--version` was available,
143
      // so this is a legacy version.
144
      return TRUE;
145
    }
146
  }
147
148
  /**
149
   * {@inheritdoc}
150
   */
151
  public function isBootstrapped() {
152
    return $this->bootstrapped;
153
  }
154
155
  /**
156
   * {@inheritdoc}
157
   */
158
  public function userCreate(\stdClass $user) {
159
    $arguments = [
160
      sprintf('"%s"', $user->name),
161
    ];
162
    $options = [
163
      'password' => $user->pass,
164
      'mail' => $user->mail,
165
    ];
166
    $result = $this->drush('user-create', $arguments, $options);
167
    if ($uid = $this->parseUserId($result)) {
168
      $user->uid = $uid;
169
    }
170
    if (isset($user->roles) && is_array($user->roles)) {
171
      foreach ($user->roles as $role) {
172
        $this->userAddRole($user, $role);
173
      }
174
    }
175
  }
176
177
  /**
178
   * Parse user id from drush user-information output.
179
   */
180
  protected function parseUserId($info) {
181
    // Find the row containing "User ID : xxx".
182
    preg_match('/User ID\s+:\s+\d+/', $info, $matches);
183
    if (!empty($matches)) {
184
      // Extract the ID from the row.
185
      list(, $uid) = explode(':', $matches[0]);
186
      return (int) $uid;
187
    }
188
  }
189
190
  /**
191
   * {@inheritdoc}
192
   */
193
  public function userDelete(\stdClass $user) {
194
    $arguments = [sprintf('"%s"', $user->name)];
195
    $options = [
196
      'yes' => NULL,
197
      'delete-content' => NULL,
198
    ];
199
    $this->drush('user-cancel', $arguments, $options);
200
  }
201
202
  /**
203
   * {@inheritdoc}
204
   */
205
  public function userAddRole(\stdClass $user, $role) {
206
    $arguments = [
207
      sprintf('"%s"', $role),
208
      sprintf('"%s"', $user->name),
209
    ];
210
    $this->drush('user-add-role', $arguments);
211
  }
212
213
  /**
214
   * {@inheritdoc}
215
   */
216
  public function fetchWatchdog($count = 10, $type = NULL, $severity = NULL) {
217
    $options = [
218
      'count' => $count,
219
      'type' => $type,
220
      'severity' => $severity,
221
    ];
222
    return $this->drush('watchdog-show', [], $options);
223
  }
224
225
  /**
226
   * {@inheritdoc}
227
   */
228
  public function clearCache($type = 'all') {
229
    if (self::$isLegacyDrush) {
230
      $type = [$type];
231
      return $this->drush('cache-clear', $type, []);
232
    }
233
    if (($type == 'all') || ($type == 'drush')) {
234
      $this->drush('cache-clear', ['drush'], []);
235
      if ($type == 'drush') {
236
        return;
237
      }
238
    }
239
    return $this->drush('cache:rebuild');
240
  }
241
242
  /**
243
   * {@inheritdoc}
244
   */
245
  public function clearStaticCaches() {
246
    // The drush driver does each operation as a separate request;
247
    // therefore, 'clearStaticCaches' can be a no-op.
248
  }
249
250
  /**
251
   * Decodes JSON object returned by Drush.
252
   *
253
   * It will clean up any junk that may have appeared before or after the
254
   * JSON object. This can happen with remote Drush aliases.
255
   *
256
   * @param string $output
257
   *   The output from Drush.
258
   *
259
   * @return object
260
   *   The decoded JSON object.
261
   */
262
  protected function decodeJsonObject($output) {
263
    // Remove anything before the first '{'.
264
    $output = preg_replace('/^[^\{]*/', '', $output);
265
    // Remove anything after the last '}'.
266
    $output = preg_replace('/[^\}]*$/s', '', $output);
267
    return json_decode($output);
268
  }
269
270
  /**
271
   * {@inheritdoc}
272
   */
273 View Code Duplication
  public function createEntity($entity_type, \StdClass $entity) {
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...
274
    $options = [
275
      'entity_type' => $entity_type,
276
      'entity' => $entity,
277
    ];
278
    $result = $this->drush('behat',
279
      ['create-entity', escapeshellarg(json_encode($options))], []);
280
    return $this->decodeJsonObject($result);
281
  }
282
283
  /**
284
   * {@inheritdoc}
285
   */
286 View Code Duplication
  public function entityDelete($entity_type, \StdClass $entity) {
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...
287
    $options = [
288
      'entity_type' => $entity_type,
289
      'entity' => $entity,
290
    ];
291
    $this->drush('behat',
292
      ['delete-entity', escapeshellarg(json_encode($options))], []);
293
  }
294
295
  /**
296
   * {@inheritdoc}
297
   */
298
  public function createNode($node) {
299
    // Look up author by name.
300
    if (isset($node->author)) {
301
      $user_info = $this->drush('user-information', [sprintf('"%s"', $node->author)]);
302
      if ($uid = $this->parseUserId($user_info)) {
303
        $node->uid = $uid;
304
      }
305
    }
306
    $result = $this->drush('behat',
307
      ['create-node', escapeshellarg(json_encode($node))],
308
      []);
309
    return $this->decodeJsonObject($result);
310
  }
311
312
  /**
313
   * {@inheritdoc}
314
   */
315
  public function nodeDelete($node) {
316
    $this->drush('behat', ['delete-node', escapeshellarg(json_encode($node))], []);
317
  }
318
319
  /**
320
   * {@inheritdoc}
321
   */
322
  public function createTerm(\stdClass $term) {
323
    $result = $this->drush('behat',
324
      [
325
        'create-term',
326
        escapeshellarg(json_encode($term)),
327
      ], []);
328
    return $this->decodeJsonObject($result);
329
  }
330
331
  /**
332
   * {@inheritdoc}
333
   */
334
  public function termDelete(\stdClass $term) {
335
    $this->drush('behat', ['delete-term', escapeshellarg(json_encode($term))], []);
336
  }
337
338
  /**
339
   * {@inheritdoc}
340
   */
341
  public function isField($entity_type, $field_name) {
342
    // If the Behat Drush Endpoint is not installed on the site-under-test,
343
    // then the drush() method will throw an exception. In this instance, we
344
    // want to treat all potential fields as non-fields.  This allows the
345
    // Drush Driver to work with certain built-in Drush capabilities (e.g.
346
    // creating users) even if the Behat Drush Endpoint is not available.
347
    try {
348
      $value = [$entity_type, $field_name];
349
      $arguments = ['is-field', escapeshellarg(json_encode($value))];
350
      $result = $this->drush('behat', $arguments, []);
351
      return strpos($result, "true\n") !== FALSE;
352
    }
353
    catch (\Exception $e) {
354
      return FALSE;
355
    }
356
  }
357
358
  /**
359
   * Sets common drush arguments or options.
360
   *
361
   * @param string $arguments
362
   *   Global arguments to add to every drush command.
363
   */
364
  public function setArguments($arguments) {
365
    $this->arguments = $arguments;
366
  }
367
368
  /**
369
   * Get common drush arguments.
370
   */
371
  public function getArguments() {
372
    return $this->arguments;
373
  }
374
375
  /**
376
   * Parse arguments into a string.
377
   *
378
   * @param array $arguments
379
   *   An array of argument/option names to values.
380
   *
381
   * @return string
382
   *   The parsed arguments.
383
   */
384
  protected static function parseArguments(array $arguments) {
385
    $string = '';
386
    foreach ($arguments as $name => $value) {
387
      if (is_null($value)) {
388
        $string .= ' --' . $name;
389
      }
390
      else {
391
        $string .= ' --' . $name . '=' . $value;
392
      }
393
    }
394
    return $string;
395
  }
396
397
  /**
398
   * Execute a drush command.
399
   */
400
  public function drush($command, array $arguments = [], array $options = []) {
401
    $arguments = implode(' ', $arguments);
402
403
    // Disable colored output from drush.
404
    if (isset(static::$isLegacyDrush) && static::$isLegacyDrush) {
405
      $options['nocolor'] = TRUE;
406
    }
407
    else {
408
      $options['no-ansi'] = NULL;
409
    }
410
    $string_options = $this->parseArguments($options);
411
412
    $alias = isset($this->alias) ? "@{$this->alias}" : '--root=' . $this->root;
413
414
    // Add any global arguments.
415
    $global = $this->getArguments();
416
417
    $process = new Process("{$this->binary} {$alias} {$string_options} {$global} {$command} {$arguments}");
418
    $process->setTimeout(3600);
419
    $process->run();
420
421
    if (!$process->isSuccessful()) {
422
      throw new \RuntimeException($process->getErrorOutput());
423
    }
424
425
    // Some drush commands write to standard error output (for example enable
426
    // use drush_log which default to _drush_print_log) instead of returning a
427
    // string (drush status use drush_print_pipe).
428
    if (!$process->getOutput()) {
429
      return $process->getErrorOutput();
430
    }
431
    else {
432
      return $process->getOutput();
433
    }
434
435
  }
436
437
  /**
438
   * {@inheritdoc}
439
   */
440
  public function processBatch() {
441
    // Do nothing. Drush should internally handle any needs for processing
442
    // batch ops.
443
  }
444
445
  /**
446
   * {@inheritdoc}
447
   */
448
  public function runCron() {
449
    $this->drush('cron');
450
  }
451
452
  /**
453
   * Run Drush commands dynamically from a DrupalContext.
454
   */
455
  public function __call($name, $arguments) {
456
    return $this->drush($name, $arguments);
457
  }
458
459
}
460