I've been working on several custom Drush commands recently, and found that I could clean up the control flow if I could exit from a method, instead of returning from the function body. This would prevent many repetitive control flow structures in my code. Here is an example of a check I performed many times

$quiet = drush_get_option('quiet');

if (empty($fileUris)) {
        if (!$quiet) {
            drush_print('No files were found to process');
        }
        return;
    }

What I wanted instead was

exitIfEmpty($fileUris, 'No files were found to process');

Of course, I could not return from the caller inside my function, so I needed some way to exit. But I found a few roadblocks.

Can't use exit() or die()

Drush uses register_shutdown_function as a way to control the execution flow of a custom Drush command body, and calling exit() or die() directly will produce an error and display this annoying message on the CLI, which I was trying to avoid

Drush command terminated abnormally due to an unrecoverable error.        [error]

GOTO is discouraged

I thought (briefly) that I could potentially use GOTO as a control flow structure in a pinch, but it is highly discouraged, and I tend to agree it should only be used in the direst of spaghetti code circumstances, if ever at all. I don't think I've ever really run into a circumstance yet where GOTO was the only choice I had, except when I was working on a project once to migrate some legacy Visual Basic code that was riddled with them.

I have to wonder, though, why the PHP folks would introduce GOTO in version 5.3 after it was not available for all versions prior? Was there a strong outcry from somewhere?

Refactoring would be time-consuming and not portable

I considered modularizing all my code into a class, but then I wouldn't have a solution that could work for those small one-off utilities that makes custom Drush commands so easy to implement.

Once I did a little more digging, I found a clean way to exit without errors.

The code review

I looked a little deeper into the source code of Drush itself and found what I was looking for in preflight.inc

I started by searching the code for that annoying "Drush command terminated abnormally..." error described earlier.

The method drush_shutdown is registered as a shutdown function. There's a check for Drush context values, which give the first clue of what to work with

function drush_shutdown() {
 ...
  if (!drush_get_context('DRUSH_EXECUTION_COMPLETED', FALSE) ...
   ...
    // We did not reach the end of the drush_main function,
    // this generally means somewhere in the code a call to exit(),
    // was made. We catch this, so that we can trigger an error in
    // those cases.
      $php_error_message = "\n" . dt('Error: !message in !file, line !line', array('!message' => $error['message'], '!file' => $error['file'], '!line' => $error['line']));
      drush_set_error("DRUSH_NOT_COMPLETED", dt("Drush command terminated abnormally due to an unrecoverable error.!message", array('!message' => $php_error_message)));
...

The second parameter of drush_get_context is an optional default value, so if the value of DRUSH_EXECUTION_COMPLETED is not set, it's assumed to be false. And here we see where the annoying error is set.

The last shutdown function in the chain, drush_return_status, is looking for another context value which, if not set, prompts a look at any Drush errors that may have been caught

function drush_return_status() {
  // If a specific exit code was set, then use it.
  $exit_code = drush_get_context('DRUSH_EXIT_CODE');
  if (empty($exit_code)) {
    $exit_code = (drush_get_error()) ? DRUSH_FRAMEWORK_ERROR : DRUSH_SUCCESS;
  }
  exit($exit_code);
}

If we want to be safe, better to set DRUSH_EXIT_CODE using the provided constant DRUSH_SUCCESS

The final Drush clean exit method

To safely exit with no Drush errors, we need to set the context values for DRUSH_EXECUTION_COMPLETED and DRUSH_EXIT_CODE. This can be done anywhere within the Drush method body.

drush_set_context('DRUSH_EXECUTION_COMPLETED', TRUE);
drush_set_context('DRUSH_EXIT_CODE', DRUSH_SUCCESS);
exit(0);

I'd optionally like to set a friendly information message in some cases.

The final method allows for this, and the exitClean method is still highly portable

/**
 * Clean Drush exit
 */
function exitClean() {
    drush_set_context('DRUSH_EXECUTION_COMPLETED', TRUE);
    drush_set_context('DRUSH_EXIT_CODE', DRUSH_SUCCESS);
    exit(0);
}

/**
 * Tests subject for emptiness, and exits if true
 * 
 * @param mixed $subject
 * @param null $message
 */
function exitIfEmpty($subject, $message=null) {
    $quiet = drush_get_option('quiet');
    if (empty($subject)) {
        if ($message) {
            if (!$quiet) {
                drush_print($message);    
            }
        }
        exitClean();
    }
}
Last Updated October 14th, 2019