Completed
Pull Request — master (#184)
by
unknown
01:33
created

DrushDriver   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 391
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 4
dl 0
loc 391
rs 8.3157
c 0
b 0
f 0

24 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
B userCreate() 0 22 5
A userDelete() 0 8 1
A userAddRole() 0 7 1
A fetchWatchdog() 0 8 1
A clearCache() 0 4 1
A clearStaticCaches() 0 4 1
A decodeJsonObject() 0 7 1
A createNode() 0 4 1
A nodeDelete() 0 3 1
A createTerm() 0 4 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   Complexity   

Complex Class

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 = array(
160
      sprintf('"%s"', $user->name),
161
    );
162
    $options = array(
163
      'password' => $user->pass,
164
      'mail' => $user->mail,
165
    );
166
    $result = $this->drush('user-create', $arguments, $options);
167
    // Find the row containing "User ID : xxx".
168
    preg_match('/User ID\s+:\s+\d+/', $result, $matches);
169
    if (!empty($matches)) {
170
      // Extract the ID from the row.
171
      list(, $uid) = explode(':', $matches[0]);
172
      $user->uid = (int) $uid;
173
    }
174
    if (isset($user->roles) && is_array($user->roles)) {
175
      foreach ($user->roles as $role) {
176
        $this->userAddRole($user, $role);
177
      }
178
    }
179
  }
180
181
  /**
182
   * {@inheritdoc}
183
   */
184
  public function userDelete(\stdClass $user) {
185
    $arguments = array(sprintf('"%s"', $user->name));
186
    $options = array(
187
      'yes' => NULL,
188
      'delete-content' => NULL,
189
    );
190
    $this->drush('user-cancel', $arguments, $options);
191
  }
192
193
  /**
194
   * {@inheritdoc}
195
   */
196
  public function userAddRole(\stdClass $user, $role) {
197
    $arguments = array(
198
      sprintf('"%s"', $role),
199
      sprintf('"%s"', $user->name),
200
    );
201
    $this->drush('user-add-role', $arguments);
202
  }
203
204
  /**
205
   * {@inheritdoc}
206
   */
207
  public function fetchWatchdog($count = 10, $type = NULL, $severity = NULL) {
208
    $options = array(
209
      'count' => $count,
210
      'type' => $type,
211
      'severity' => $severity,
212
    );
213
    return $this->drush('watchdog-show', array(), $options);
214
  }
215
216
  /**
217
   * {@inheritdoc}
218
   */
219
  public function clearCache($type = 'all') {
220
    $type = array($type);
221
    return $this->drush('cache-clear', $type, array());
222
  }
223
224
  /**
225
   * {@inheritdoc}
226
   */
227
  public function clearStaticCaches() {
228
    // The drush driver does each operation as a separate request;
229
    // therefore, 'clearStaticCaches' can be a no-op.
230
  }
231
232
  /**
233
   * Decodes JSON object returned by Drush.
234
   *
235
   * It will clean up any junk that may have appeared before or after the
236
   * JSON object. This can happen with remote Drush aliases.
237
   *
238
   * @param string $output
239
   *   The output from Drush.
240
   *
241
   * @return object
242
   *   The decoded JSON object.
243
   */
244
  protected function decodeJsonObject($output) {
245
    // Remove anything before the first '{'.
246
    $output = preg_replace('/^[^\{]*/', '', $output);
247
    // Remove anything after the last '}'.
248
    $output = preg_replace('/[^\}]*$/s', '', $output);
249
    return json_decode($output);
250
  }
251
252
  /**
253
   * {@inheritdoc}
254
   */
255
  public function createNode($node) {
256
    $result = $this->drush('behat', array('create-node', escapeshellarg(json_encode($node))), array());
257
    return $this->decodeJsonObject($result);
258
  }
259
260
  /**
261
   * {@inheritdoc}
262
   */
263
  public function nodeDelete($node) {
264
    $this->drush('behat', array('delete-node', escapeshellarg(json_encode($node))), array());
265
  }
266
267
  /**
268
   * {@inheritdoc}
269
   */
270
  public function createTerm(\stdClass $term) {
271
    $result = $this->drush('behat', array('create-term', escapeshellarg(json_encode($term))), array());
272
    return $this->decodeJsonObject($result);
273
  }
274
275
  /**
276
   * {@inheritdoc}
277
   */
278
  public function termDelete(\stdClass $term) {
279
    $this->drush('behat', array('delete-term', escapeshellarg(json_encode($term))), array());
280
  }
281
282
  /**
283
   * {@inheritdoc}
284
   */
285
  public function isField($entity_type, $field_name) {
286
    // If the Behat Drush Endpoint is not installed on the site-under-test,
287
    // then the drush() method will throw an exception. In this instance, we
288
    // want to treat all potential fields as non-fields.  This allows the
289
    // Drush Driver to work with certain built-in Drush capabilities (e.g.
290
    // creating users) even if the Behat Drush Endpoint is not available.
291
    try {
292
      $value = array($entity_type, $field_name);
293
      $arguments = array('is-field', escapeshellarg(json_encode($value)));
294
      $result = $this->drush('behat', $arguments, array());
295
      return strpos($result, "true\n") !== FALSE;
296
    }
297
    catch (\Exception $e) {
298
      return FALSE;
299
    }
300
  }
301
302
  /**
303
   * Sets common drush arguments or options.
304
   *
305
   * @param string $arguments
306
   *   Global arguments to add to every drush command.
307
   */
308
  public function setArguments($arguments) {
309
    $this->arguments = $arguments;
310
  }
311
312
  /**
313
   * Get common drush arguments.
314
   */
315
  public function getArguments() {
316
    return $this->arguments;
317
  }
318
319
  /**
320
   * Parse arguments into a string.
321
   *
322
   * @param array $arguments
323
   *   An array of argument/option names to values.
324
   *
325
   * @return string
326
   *   The parsed arguments.
327
   */
328
  protected static function parseArguments(array $arguments) {
329
    $string = '';
330
    foreach ($arguments as $name => $value) {
331
      if (is_null($value)) {
332
        $string .= ' --' . $name;
333
      }
334
      else {
335
        $string .= ' --' . $name . '=' . $value;
336
      }
337
    }
338
    return $string;
339
  }
340
341
  /**
342
   * Execute a drush command.
343
   */
344
  public function drush($command, array $arguments = array(), array $options = array()) {
345
    $arguments = implode(' ', $arguments);
346
347
    // Disable colored output from drush.
348
    if (isset(static::$isLegacyDrush) && static::$isLegacyDrush) {
349
      $options['nocolor'] = TRUE;
350
    }
351
    else {
352
      $options['no-ansi'] = NULL;
353
    }
354
    $string_options = $this->parseArguments($options);
355
356
    $alias = isset($this->alias) ? "@{$this->alias}" : '--root=' . $this->root;
357
358
    // Add any global arguments.
359
    $global = $this->getArguments();
360
361
    $process = new Process("{$this->binary} {$alias} {$string_options} {$global} {$command} {$arguments}");
362
    $process->setTimeout(3600);
363
    $process->run();
364
365
    if (!$process->isSuccessful()) {
366
      throw new \RuntimeException($process->getErrorOutput());
367
    }
368
369
    // Some drush commands write to standard error output (for example enable
370
    // use drush_log which default to _drush_print_log) instead of returning a
371
    // string (drush status use drush_print_pipe).
372
    if (!$process->getOutput()) {
373
      return $process->getErrorOutput();
374
    }
375
    else {
376
      return $process->getOutput();
377
    }
378
379
  }
380
381
  /**
382
   * {@inheritdoc}
383
   */
384
  public function processBatch() {
385
    // Do nothing. Drush should internally handle any needs for processing
386
    // batch ops.
387
  }
388
389
  /**
390
   * {@inheritdoc}
391
   */
392
  public function runCron() {
393
    $this->drush('cron');
394
  }
395
396
  /**
397
   * Run Drush commands dynamically from a DrupalContext.
398
   */
399
  public function __call($name, $arguments) {
400
    return $this->drush($name, $arguments);
401
  }
402
403
}
404