drush icon indicating copy to clipboard operation
drush copied to clipboard

drush updatedb does not call hook_boot()

Open klausi opened this issue 6 years ago • 5 comments

Describe the bug We have this hook_boot() in a module to autoload our composer dependencies in Drupal 7

/**
 * Implements hook_boot().
 */
function recruiter_jobiqo_boot() {
  // include the composer autoload file.
  require_once DRUPAL_ROOT . '/profiles/recruiter/vendor/autoload.php';
}

If we now try to use a class from vendor in an update function like this

function recruiter_dev_update_7000() {
  $pdf = new Smalot\PdfParser\Parser();
}

then we get a fatal error with drush upodatedb because the autoloader was never registered because our hook_boot() was never called.

If we run the update with Drupal core's update.php in the browser then the update function runs normally. hook_boot() is called correctly there.

Expected behavior drush updatedb should invoke hook_boot() correctly.

Actual behavior

$ drush updatedb
WD php: Error: Class 'Smalot\PdfParser\Parser' not found in recruiter_dev_update_7000()

Workaround We could manually include our autoloader in every update function, but that is tedious.

System Configuration

Q A
Drush version? 8.1.18
Drupal version? 7.67
PHP version 7.2
OS? Linux

klausi avatar Jun 26 '19 15:06 klausi

We implemented the following workaround:

/**
 * Implements hook_batch_alter().
 *
 * "drush updatedb" runs the update in a separate process that doesn't invoke
 * hook_boot. As a result classes that are loaded via composer are missing.
 *
 * Because drush offers no hooks to alter the (internal) updatedb-batch-process
 * command we need to modify the batch set instead and inject
 * composer_manager_boot as the first item.
 */
function mymodule_batch_alter(&$batch) {
  $set = &$batch['sets'][$batch['current_set']];
  if(!empty($set['finished']) && $set['finished'] === 'drush_update_finished') {
    array_unshift($set['operations'], array('composer_manager_boot', array()));
    $set['count']++;
  }
}

mootari avatar Mar 09 '20 16:03 mootari

Instead of including the autoloader from hook_boot, perhaps it would be more reliable to include it from settings.php.

greg-1-anderson avatar Mar 11 '20 20:03 greg-1-anderson

We ended up having to switch to a broader approach since a batch might get split into multiple sets:

function mymodule_batch_alter(&$batch) {
  $set = &$batch['sets'][$batch['current_set']];
  if(!empty($set['finished']) && $set['finished'] === 'drush_update_finished') {
    foreach($set['operations'] as &$op) {
      if($op[0] === 'drush_update_do_one') {
        $op = ['_mymodule_batch_ensure_autoloader', $op];
      }
    }
  }
}

function _mymodule_batch_ensure_autoloader($function, $args, &$batch_context) {
  composer_manager_register_autoloader();
  return call_user_func_array($function, array_merge($args, array(&$batch_context)));
}

Btw, creating a watch for getmypid() proved useful to keep an eye out for new processes.

mootari avatar Apr 14 '20 17:04 mootari

Instead of including the autoloader from hook_boot, perhaps it would be more reliable to include it from settings.php.

I was looking for an approach that would work out of the box, without requiring additional per-project configuration. We added the fix to our base profile. I also noticed that direct inclusion via e.g. hook_drush_init wouldn't work. Didn't investigate further though.

mootari avatar Apr 14 '20 17:04 mootari

Our workaround is to directly include our composer autoloader in a module drush command file:

example.drush.inc

<?php

/**
 * @file
 * Drush command file for recruiter_jobiqo.
 */

// hook_boot() is not called during drush updb - work around this by including
// the autoloader here whenever drush includes module command files.
require_once __DIR__ . '/../../../vendor/autoload.php';

klausi avatar Jun 20 '24 16:06 klausi