3 namespace Drupal\Driver;
5 use Drupal\Component\Utility\Random;
6 use Drupal\Driver\Exception\BootstrapException;
8 use Symfony\Component\Process\Process;
11 * Implements DriverInterface.
13 class DrushDriver extends BaseDriver {
15 * Store a drush alias for tests requiring shell access.
22 * Stores the root path to a Drupal installation.
24 * This is an alternative to using drush aliases.
31 * Store the path to drush binary.
38 * Track bootstrapping.
40 private $bootstrapped = FALSE;
45 * @var \Drupal\Component\Utility\Random
50 * Global arguments or options for drush commands.
54 private $arguments = '';
57 * Set drush alias or root path.
59 * @param string $alias
61 * @param string $root_path
62 * The root path of the Drupal install. This is an alternative to using
64 * @param string $binary
65 * The path to the drush binary.
66 * @param \Drupal\Component\Utility\Random $random
69 * @throws \Drupal\Driver\Exception\BootstrapException
70 * Thrown when a required parameter is missing.
72 public function __construct($alias = NULL, $root_path = NULL, $binary = 'drush', Random $random = NULL) {
74 // Trim off the '@' symbol if it has been added.
75 $alias = ltrim($alias, '@');
77 $this->alias = $alias;
79 elseif (!empty($root_path)) {
80 $this->root = realpath($root_path);
83 throw new BootstrapException('A drush alias or root path is required.');
86 $this->binary = $binary;
88 if (!isset($random)) {
89 $random = new Random();
91 $this->random = $random;
97 public function getRandom() {
104 public function bootstrap() {
105 // Check that the given alias works.
106 // @todo check that this is a functioning alias.
107 // See http://drupal.org/node/1615450
108 if (!isset($this->alias) && !isset($this->root)) {
109 throw new BootstrapException('A drush alias or root path is required.');
111 $this->bootstrapped = TRUE;
117 public function isBootstrapped() {
118 return $this->bootstrapped;
124 public function userCreate(\stdClass $user) {
126 sprintf('"%s"', $user->name),
129 'password' => $user->pass,
130 'mail' => $user->mail,
132 $this->drush('user-create', $arguments, $options);
133 if (isset($user->roles) && is_array($user->roles)) {
134 foreach ($user->roles as $role) {
135 $this->userAddRole($user, $role);
143 public function userDelete(\stdClass $user) {
144 $arguments = array(sprintf('"%s"', $user->name));
147 'delete-content' => NULL,
149 $this->drush('user-cancel', $arguments, $options);
155 public function userAddRole(\stdClass $user, $role) {
157 sprintf('"%s"', $role),
158 sprintf('"%s"', $user->name),
160 $this->drush('user-add-role', $arguments);
166 public function fetchWatchdog($count = 10, $type = NULL, $severity = NULL) {
170 'severity' => $severity,
172 return $this->drush('watchdog-show', array(), $options);
178 public function clearCache($type = 'all') {
179 $type = array($type);
180 return $this->drush('cache-clear', $type, array());
186 public function clearStaticCaches() {
187 // The drush driver does each operation as a separate request;
188 // therefore, 'clearStaticCaches' can be a no-op.
192 * Decodes JSON object returned by Drush.
194 * It will clean up any junk that may have appeared before or after the
195 * JSON object. This can happen with remote Drush aliases.
197 * @param string $output
198 * The output from Drush.
200 * The decoded JSON object.
202 protected function decodeJsonObject($output) {
203 // Remove anything before the first '{'.
204 $output = preg_replace('/^[^\{]*/', '', $output);
205 // Remove anything after the last '}'.
206 $output = preg_replace('/[^\}]*$/s', '', $output);
207 return json_decode($output);
213 public function createNode($node) {
214 $result = $this->drush('behat', array('create-node', escapeshellarg(json_encode($node))), array());
215 return $this->decodeJsonObject($result);
221 public function nodeDelete($node) {
222 $this->drush('behat', array('delete-node', escapeshellarg(json_encode($node))), array());
228 public function createTerm(\stdClass $term) {
229 $result = $this->drush('behat', array('create-term', escapeshellarg(json_encode($term))), array());
230 return $this->decodeJsonObject($result);
236 public function termDelete(\stdClass $term) {
237 $this->drush('behat', array('delete-term', escapeshellarg(json_encode($term))), array());
243 public function isField($entity_type, $field_name) {
244 // If the Behat Drush Endpoint is not installed on the site-under-test,
245 // then the drush() method will throw an exception. In this instance, we
246 // want to treat all potential fields as non-fields. This allows the
247 // Drush Driver to work with certain built-in Drush capabilities (e.g.
248 // creating users) even if the Behat Drush Endpoint is not available.
250 $result = $this->drush('behat', array('is-field', escapeshellarg(json_encode(array($entity_type, $field_name)))), array());
251 return strpos($result, "true\n") !== FALSE;
253 catch (\Exception $e) {
259 * Sets common drush arguments or options.
261 * @param string $arguments
262 * Global arguments to add to every drush command.
264 public function setArguments($arguments) {
265 $this->arguments = $arguments;
269 * Get common drush arguments.
271 public function getArguments() {
272 return $this->arguments;
276 * Parse arguments into a string.
278 * @param array $arguments
279 * An array of argument/option names to values.
282 * The parsed arguments.
284 protected static function parseArguments(array $arguments) {
286 foreach ($arguments as $name => $value) {
287 if (is_null($value)) {
288 $string .= ' --' . $name;
291 $string .= ' --' . $name . '=' . $value;
298 * Execute a drush command.
300 public function drush($command, array $arguments = array(), array $options = array()) {
301 $arguments = implode(' ', $arguments);
303 // Disable colored output from drush.
304 $options['nocolor'] = TRUE;
305 $string_options = $this->parseArguments($options);
307 $alias = isset($this->alias) ? "@{$this->alias}" : '--root=' . $this->root;
309 // Add any global arguments.
310 $global = $this->getArguments();
312 $process = new Process("{$this->binary} {$alias} {$string_options} {$global} {$command} {$arguments}");
313 $process->setTimeout(3600);
316 if (!$process->isSuccessful()) {
317 throw new \RuntimeException($process->getErrorOutput());
320 // Some drush commands write to standard error output (for example enable
321 // use drush_log which default to _drush_print_log) instead of returning a
322 // string (drush status use drush_print_pipe).
323 if (!$process->getOutput()) {
324 return $process->getErrorOutput();
327 return $process->getOutput();
335 public function processBatch() {
336 // Do nothing. Drush should internally handle any needs for processing
343 public function runCron() {
344 $this->drush('cron');
348 * Run Drush commands dynamically from a DrupalContext.
350 public function __call($name, $arguments) {
351 return $this->drush($name, $arguments);