function _drush_invoke_hooks

8.0.x _drush_invoke_hooks($command, $args)
6.x _drush_invoke_hooks($command, $args)
7.x _drush_invoke_hooks($command, $args)
5.x _drush_invoke_hooks($command, $args, $defined_in_commandfile = NULL)
master _drush_invoke_hooks($command, $args)

Invoke Drush API calls, including all hooks.

This is an internal function; it is called from drush_dispatch via drush_command, but only if the command does not specify a 'callback' function. If a callback function is specified, it will be called instead of drush_command + _drush_invoke_hooks.

Executes the specified command with the specified arguments on the currently bootstrapped site using the current option contexts. Note that _drush_invoke_hooks will not bootstrap any further than the current command has already bootstrapped; therefore, you should only invoke commands that have the same (or lower) bootstrap requirements.

Call the correct hook for all the modules that implement it. Additionally, the ability to rollback when an error has been encountered is also provided. If at any point during execution, the drush_get_error() function returns anything but 0, drush_invoke() will trigger $hook_rollback for each of the hooks that implement it, in reverse order from how they were executed. Rollbacks are also triggered any time a hook function returns FALSE.

This function will also trigger pre_$hook and post_$hook variants of the hook and its rollbacks automatically.


The name of the hook is composed from the name of the command and the name of the command file that the command definition is declared in. The general form for the hook filename is:


In many cases, drush commands that are functionally part of a common collection of similar commands will all be declared in the same file, and every command defined in that file will start with the same command prefix. For example, the command file "" defines commands such as "pm-enable" and "pm-disable". In the case of "pm-enable", the command file is "pm", and and command name is "pm-enable". When the command name starts with the same sequence of characters as the command file, then the repeated sequence is dropped; thus, the command hook for "pm-enable" is "drush_pm_enable", not "drush_pm_pm_enable".

There is also a special Drupal-version-specific naming convention that may be used. To hide a commandfile from all versions of Drupal except for the specific one named, add a ".dVERSION" after the command prefix. For example, the file "" defines a "views" commandfile that will only load with Drupal 8. This feature is not necessary and should not be used in contrib modules (any extension with a ".module" file), since these modules are already version-specific.


command: The drush command to execute.

args: An array of arguments to the command OR a single non-array argument.

Return value

The return value will be passed along to the caller if --backend option is present. A boolean FALSE indicates failure and rollback will be intitated.

This function should not be called directly.

See also

drush_invoke() and @see drush_invoke_process()

Related topics

1 call to _drush_invoke_hooks()
drush_command in includes/
Entry point for commands into the drush_invoke() API


includes/, line 292
The drush command engine.


function _drush_invoke_hooks($command, $args) {
  // If someone passed a standalone arg, convert it to a single-element array
  if (!is_array($args)) {
    $args = array($args);
  // Include the external command file used by this command, if there is one.
  // Generate the base name for the hook by converting all
  // dashes in the command name to underscores.
  $hook = str_replace("-", "_", $command['command-hook']);

  // Call the hook init function, if it exists.
  // If a command needs to bootstrap, it is advisable
  // to do so in _init; otherwise, new commandfiles
  // will miss out on participating in any stage that
  // has passed or started at the time it was discovered.
  $func = 'drush_' . $hook . '_init';
  if (function_exists($func)) {
    drush_log(dt("Calling drush command init function: !func", array('!func' => $func)), LogLevel::BOOTSTRAP);
    call_user_func_array($func, $args);
    if (drush_get_error()) {
      drush_log(dt('The command @command could not be initialized.', array('@command' => $command['command-hook'])), LogLevel::ERROR);
      return FALSE;

  $rollback = FALSE;
  $completed = array();
  $available_rollbacks = array();
  $all_available_hooks = array();

  // Iterate through the different hook variations
  $variations = array(
    $hook . "_pre_validate",
    $hook . "_validate",
  foreach ($variations as $var_hook) {
    // Get the list of command files.
    // We re-fetch the list every time through
    // the loop in case one of the hook function
    // does something that will add additional
    // commandfiles to the list (i.e. bootstrapping
    // to a higher phase will do this).
    $list = drush_commandfile_list();

    // Make a list of function callbacks to call.  If
    // there is a 'primary function' mentioned, make sure
    // that it appears first in the list, but only if
    // we are running the main hook ("$hook").  After that,
    // make sure that any callback associated with this commandfile
    // executes before any other hooks defined in any other
    // commandfiles.
    $callback_list = array();
    if (($var_hook == $hook) && ($command['primary function'])) {
      $callback_list[$command['primary function']] = $list[$command['commandfile']];
    else {
      $primary_func = ($command['commandfile'] . "_" == substr($var_hook . "_", 0, strlen($command['commandfile']) + 1)) ? sprintf("drush_%s", $var_hook) : sprintf("drush_%s_%s", $command['commandfile'], $var_hook);
      $callback_list[$primary_func] = $list[$command['commandfile']];
    // We've got the callback for the primary function in the
    // callback list; now add all of the other callback functions.
    foreach ($list as $commandfile => $filename) {
      $func = sprintf("drush_%s_%s", $commandfile, $var_hook);
      $callback_list[$func] = $filename;
    // Run all of the functions available for this variation
    $accumulated_result = NULL;
    foreach ($callback_list as $func => $filename) {
      if (function_exists($func)) {
        $all_available_hooks[] = $func . ' [* Defined in ' . $filename . ']';
        $available_rollbacks[] = $func . '_rollback';
        $completed[] = $func;
        drush_log(dt("Calling hook !hook", array('!hook' => $func)), LogLevel::DEBUG);
        try {
          $result = call_user_func_array($func, $args);
          drush_log(dt("Returned from hook !hook", array('!hook' => $func)), LogLevel::DEBUG);
        catch (Exception $e) {
          drush_set_error('DRUSH_EXECUTION_EXCEPTION', (string) $e);
        // If there is an error, break out of the foreach
        // $variations and foreach $callback_list
        if (drush_get_error() || ($result === FALSE)) {
          $rollback = TRUE;
          break 2;
        // If result values are arrays, then combine them all together.
        // Later results overwrite earlier results.
        if (isset($result) && is_array($accumulated_result) && is_array($result)) {
          $accumulated_result = array_merge($accumulated_result, $result);
        else {
          $accumulated_result = $result;
      else {
        $all_available_hooks[] = $func;
    // Process the result value from the 'main' callback hook only.
    if ($var_hook == $hook) {
      $return = $accumulated_result;
      if (isset($return)) {
        drush_handle_command_output($command, $return);

  // If no hook functions were found, print a warning.
  if (empty($completed)) {
    $default_command_hook = sprintf("drush_%s_%s", $command['commandfile'], $hook);
    if (($command['commandfile'] . "_" == substr($hook . "_", 0, strlen($command['commandfile']) + 1))) {
      $default_command_hook = sprintf("drush_%s", $hook);
    $dt_args = array(
      '!command' => $command['command-hook'],
      '!default_func' => $default_command_hook,
    $message = "No hook functions were found for !command. The primary hook function is !default_func(). Please implement this function. Run with --show-invoke to see all available hooks.";
    $return = drush_set_error('DRUSH_FUNCTION_NOT_FOUND', dt($message, $dt_args));
  if (drush_get_option('show-invoke')) {
    // We show all available hooks up to and including the one that failed (or all, if there were no failures)
    drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command['command-hook'], '!available' => "\n" . implode("\n", $all_available_hooks))), LogLevel::OK);
  if (drush_get_option('show-invoke') && !empty($available_rollbacks)) {
    drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command['command-hook'], '!rollback' => "\n" . implode("\n", $available_rollbacks))), LogLevel::OK);

  // Something went wrong, we need to undo.
  if ($rollback) {
    if (drush_get_option('confirm-rollback', FALSE)) {
      // Optionally ask for confirmation, --yes and --no are ignored from here on as we are about to finish this process.
      drush_set_context('DRUSH_AFFIRMATIVE', FALSE);
      drush_set_context('DRUSH_NEGATIVE', FALSE);
      $rollback = drush_confirm(dt('Do you want to rollback? (manual cleanup might be required otherwise)'));

    if ($rollback) {
      foreach (array_reverse($completed) as $func) {
        $rb_func = $func . '_rollback';
        if (function_exists($rb_func)) {
          call_user_func_array($rb_func, $args);
          drush_log(dt("Changes made in !func have been rolled back.", array('!func' => $func)), LogLevel::DEBUG);
    $return = FALSE;

  if (isset($return)) {
    return $return;