diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 171e9d77e3aa9efb7ea9d4a0586bed6de9a8312f..55bf095749b34842a4432414b21484ce6593993d 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -1406,18 +1406,7 @@ function drupal_function_exists($function) { return TRUE; } - $file = db_query("SELECT filename FROM {registry} WHERE name = :name AND type = :type", array( - ':name' => $function, - ':type' => 'function', - )) - ->fetchField(); - if ($file) { - require_once DRUPAL_ROOT . '/' . $file; - $checked[$function] = function_exists($function); - if ($checked[$function]) { - registry_mark_code('function', $function); - } - } + $checked[$function] = _registry_check_code('function', $function); return $checked[$function]; } @@ -1455,7 +1444,7 @@ function drupal_autoload_class($class) { } /** - * Helper for registry_check_{interface, class}. + * Helper to check for a resource in the registry. */ function _registry_check_code($type, $name) { $file = db_query("SELECT filename FROM {registry} WHERE name = :name AND type = :type", array( @@ -1468,6 +1457,7 @@ function _registry_check_code($type, $name) { registry_mark_code($type, $name); return TRUE; } + return FALSE; } /** @@ -1508,31 +1498,6 @@ function registry_rebuild() { _registry_rebuild(); } -/** - * Save hook implementations cache. - * - * @param $hook - * Array with the hook name and list of modules that implement it. - * @param $write_to_persistent_cache - * Whether to write to the persistent cache. - */ -function registry_cache_hook_implementations($hook, $write_to_persistent_cache = FALSE) { - static $implementations; - - if ($hook) { - // Newer is always better, so overwrite anything that's come before. - $implementations[$hook['hook']] = $hook['modules']; - } - - if ($write_to_persistent_cache === TRUE) { - // Only write this to cache if the implementations data we are going to cache - // is different to what we loaded earlier in the request. - if ($implementations != module_implements()) { - cache_set('hooks', $implementations, 'cache_registry'); - } - } -} - /** * Save the files required by the registry for this path. */ diff --git a/includes/common.inc b/includes/common.inc index 3600cdd9e265e65945921b33573e203d265d8437..6437d36ee6be73202036cc070bc217e111c718e6 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -1608,7 +1608,7 @@ function drupal_page_footer() { module_invoke_all('exit'); - registry_cache_hook_implementations(FALSE, TRUE); + module_implements(MODULE_IMPLEMENTS_WRITE_CACHE); registry_cache_path_files(); } @@ -2668,8 +2668,6 @@ function _drupal_bootstrap_full() { fix_gpc_magic(); // Load all enabled modules module_load_all(); - // Rebuild the module hook cache - module_implements('', NULL, TRUE); // Let all modules take action before menu system handles the request // We do not want this while running update.php. diff --git a/includes/menu.inc b/includes/menu.inc index c620228c03fe23cdb22f33328400d8b8fcf9da15..eb806b7d59bae664ccae8010b0b3c2602edf0f3c 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -1728,7 +1728,7 @@ function menu_router_build($reset = FALSE) { // We need to manually call each module so that we can know which module // a given item came from. $callbacks = array(); - foreach (module_implements('menu', NULL, TRUE) as $module) { + foreach (module_implements('menu', TRUE) as $module) { $router_items = call_user_func($module . '_menu'); if (isset($router_items) && is_array($router_items)) { foreach (array_keys($router_items) as $path) { diff --git a/includes/module.inc b/includes/module.inc index 9df64a66f29552f413f273e3e2350316afb2d4cd..c5c3686fc7baceb119f2c93654330ddf767297d1 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -6,6 +6,17 @@ * API for loading and interacting with Drupal modules. */ +/** + * Pass this to module_implements when its cache needs to be written. + */ +define('MODULE_IMPLEMENTS_WRITE_CACHE', -1); + +/** + * Pass this to module_implements when its cache needs to be cleared. + */ +define('MODULE_IMPLEMENTS_CLEAR_CACHE', -2); + + /** * Load all the modules that have been enabled in the system table. */ @@ -26,8 +37,8 @@ function module_load_all() { * Whether to return the reduced set of modules loaded in "bootstrap mode" * for cached pages. See bootstrap.inc. * @param $sort - * By default, modules are ordered by weight and filename, settings this option - * to TRUE, module list will be ordered by module name. + * By default, modules are ordered by weight and filename. Set this option to + * TRUE to return a module list ordered only by module name. * @param $fixed_list * (Optional) Override the module list with the given modules. Stays until the * next call with $refresh = TRUE. @@ -391,47 +402,102 @@ function module_hook($module, $hook) { * Determine which modules are implementing a hook. * * @param $hook - * The name of the hook (e.g. "help" or "menu"). - * @param $sort - * By default, modules are ordered by weight and filename, settings this option - * to TRUE, module list will be ordered by module name. - * @param $refresh - * For internal use only: Whether to force the stored list of hook + * The name of the hook (e.g. "help" or "menu"). Special cases: + * MODULE_IMPLEMENTS_CLEAR_CACHE: Force the stored list of hook * implementations to be regenerated (such as after enabling a new module, - * before processing hook_enable). Note that if $refresh is TRUE this function - * will always return NULL. + * before processing hook_enable). + * MODULE_IMPLEMENTS_WRITE_CACHE: Write the stored list of hook + * implementations into the cache_registry table. + * @param $sort + * By default, modules are ordered by weight and filename. By setting this + * option to TRUE, modules will be ordered by module name. * @return * An array with the names of the modules which are implementing this hook. - * If $hook is NULL then it will return the implementation cache. + * All enabled modules are taken into consideration and the files containing + * the implementations are loaded as necessary. */ -function module_implements($hook = NULL, $sort = FALSE, $refresh = FALSE) { - static $implementations = array(); +function module_implements($hook, $sort = FALSE) { + static $implementations = array(), $sorted_implementations = array(), $loaded = array(), $cached_hooks = 0; - if (!isset($hook)) { - return $implementations; + if (defined('MAINTENANCE_MODE')) { + return _module_implements_maintenance($hook, $sort); } - if ($refresh) { + if ($hook === MODULE_IMPLEMENTS_CLEAR_CACHE) { $implementations = array(); + $sorted_implementations = array(); + $loaded = array(); + $cached_hooks = 0; + cache_clear_all('hooks', 'cache_registry'); + return; } - if (!defined('MAINTENANCE_MODE') && empty($implementations) && ($cache = cache_get('hooks', 'cache_registry'))) { - $implementations = $cache->data; + if ($hook === MODULE_IMPLEMENTS_WRITE_CACHE) { + // Only write this to cache if we loaded new implementations. + if (count($implementations) > $cached_hooks) { + cache_set('hooks', $implementations, 'cache_registry'); + } + return; } - if ($hook) { + if (!isset($loaded[$hook])) { + if (empty($implementations) && ($cache = cache_get('hooks', 'cache_registry'))) { + $implementations = $cache->data; + $cached_hooks = count($implementations); + } if (!isset($implementations[$hook])) { - $implementations[$hook] = array(); - foreach (module_list() as $module) { - if (module_hook($module, $hook)) { - $implementations[$hook][] = $module; - } + $implementations[$hook] = db_query("SELECT module FROM {registry} WHERE type = 'function' AND suffix = :hook ORDER BY weight, module", array(':hook' => $hook))->fetchCol(); + } + foreach ($implementations[$hook] as $module) { + $function = $module . '_' . $hook; + if (!function_exists($function)) { + drupal_function_exists($function); } } - registry_cache_hook_implementations(array('hook' => $hook, 'modules' => $implementations[$hook])); + $loaded[$hook] = TRUE; + } + if ($sort) { + if (!isset($sorted_implementations[$hook])) { + $sorted_implementations[$hook] = $implementations[$hook]; + sort($sorted_implementations[$hook]); + } + return $sorted_implementations[$hook]; + } + else { return $implementations[$hook]; } } +/** + * This is the maintenance version of module_implements for internal use only. + * + * This function is called whenever MAINTENANCE_MODE is defined and is a + * safe code path for Drupal installation or upgrade because it does not use + * the database, instead it uses module_list. @see module_list $fixed_list on + * how to make module_list also DB independent. + * + * @param $hook + * The name of the hook (e.g. "help" or "menu"). + * @param $sort + * By default, modules are ordered by weight and filename, settings this + * option to TRUE, module list will be ordered by module name. + * @return + * An array with the names of the modules which are implementing this hook. + * Only enabled and already loaded modules are taken into consideration. + */ +function _module_implements_maintenance($hook, $sort = FALSE) { + $implementations = array(); + foreach (module_list() as $module) { + $function = $module . '_' . $hook; + if (function_exists($function)) { + $implementations[] = $module; + } + if ($sort) { + sort($implementations); + } + } + return $implementations; +} + /** * Invoke a hook in a particular module. * diff --git a/includes/registry.inc b/includes/registry.inc index d240caa9dae9f99a45bf232d931b0e351dd3e0c0..c02811182e11a6070f1bf51e197da489f7169ec1 100644 --- a/includes/registry.inc +++ b/includes/registry.inc @@ -45,12 +45,12 @@ function _registry_rebuild() { if ($module->status) { $dir = dirname($module->filename); foreach ($module->info['files'] as $file) { - $files["$dir/$file"] = array(); + $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight); } } } foreach (file_scan_directory('includes', '/\.inc$/') as $filename => $file) { - $files["$filename"] = array(); + $files["$filename"] = array('module' => '', 'weight' => 0); } foreach (registry_get_parsed_files() as $filename => $file) { @@ -71,6 +71,7 @@ function _registry_rebuild() { } _registry_parse_files($files); + module_implements(MODULE_IMPLEMENTS_CLEAR_CACHE); cache_clear_all('*', 'cache_registry', TRUE); } @@ -99,7 +100,7 @@ function _registry_parse_files($files) { if ($new_file || $md5 != $file['md5']) { // We update the md5 after we've saved the files resources rather than here, so if we // don't make it through this rebuild, the next run will reparse the file. - _registry_parse_file($filename, $contents); + _registry_parse_file($filename, $contents, $file['module'], $file['weight']); $file['md5'] = $md5; db_merge('registry_file') ->key(array('filename' => $filename)) @@ -116,8 +117,12 @@ function _registry_parse_files($files) { * Name of the file we are going to parse. * @param $contents * Contents of the file we are going to parse as a string. + * @param $module + * (optional) Name of the module this file belongs to. + * @param $weight + * (optional) Weight of the module. */ -function _registry_parse_file($filename, $contents) { +function _registry_parse_file($filename, $contents, $module = '', $weight = 0) { static $map = array(T_FUNCTION => 'function', T_CLASS => 'class', T_INTERFACE => 'interface'); // Delete registry entries for this file, so we can insert the new resources. db_delete('registry') @@ -129,6 +134,21 @@ function _registry_parse_file($filename, $contents) { if (is_array($token) && isset($map[$token[0]])) { $type = $map[$token[0]]; if ($resource_name = _registry_get_resource_name($tokens, $type)) { + $suffix = ''; + // Collect the part of the function name after the module name, + // so that we can query the registry for possible hook implementations. + if ($type == 'function' && !empty($module)) { + $n = strlen($module); + if (substr($resource_name, 0, $n) == $module) { + $suffix = substr($resource_name, $n + 1); + } + } + $fields = array( + 'filename' => $filename, + 'module' => $module, + 'suffix' => $suffix, + 'weight' => $weight, + ); // Because some systems, such as cache, currently use duplicate function // names in separate files an insert query cannot be used here as it // would cause a key constraint violation. Instead we use a merge query. @@ -140,7 +160,7 @@ function _registry_parse_file($filename, $contents) { // function names have been purged from Drupal. db_merge('registry') ->key(array('name' => $resource_name, 'type' => $type)) - ->fields(array('filename' => $filename)) + ->fields($fields) ->execute(); // We skip the body because classes may contain functions. diff --git a/modules/simpletest/tests/registry.test b/modules/simpletest/tests/registry.test index a167178b6a4ce9399cb18e31cf8eec45d07fadfe..794403dac27ff7a92f9f90262583074210bb797a 100644 --- a/modules/simpletest/tests/registry.test +++ b/modules/simpletest/tests/registry.test @@ -122,11 +122,9 @@ class RegistryParseFilesTestCase extends DrupalWebTestCase { function getFiles() { $files = array(); foreach ($this->fileTypes as $fileType) { + $files[$this->$fileType->fileName] = array('module' => '', 'weight' => 0); if ($fileType == 'existing_changed') { - $files[$this->$fileType->fileName] = array('md5' => $this->$fileType->fakeMD5); - } - else { - $files[$this->$fileType->fileName] = array(); + $files[$this->$fileType->fileName]['md5'] = $this->$fileType->fakeMD5; } } return $files; diff --git a/modules/system/system.install b/modules/system/system.install index dbd6c8f293e022e647b05698ac5c6bdc8d9694d3..f84bc570c4aa7a8f578f504f5f389ab10f2042b3 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -1092,8 +1092,31 @@ function system_schema() { 'length' => 255, 'not null' => TRUE, ), + 'module' => array( + 'description' => t('Name of the module the file belongs to.'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'suffix' => array( + 'description' => t("The part of the function name after the module, which is the hook this function implements, if any."), + 'type' => 'varchar', + 'length' => 68, + 'not null' => TRUE, + 'default' => '' + ), + 'weight' => array( + 'description' => t("The order in which this module's hooks should be invoked relative to other modules. Equal-weighted modules are ordered by name."), + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), ), 'primary key' => array('name', 'type'), + 'indexes' => array( + 'hook' => array('type', 'suffix', 'weight', 'module'), + ), ); $schema['registry_file'] = array( @@ -2931,8 +2954,14 @@ function system_update_7006() { 'name' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), 'type' => array('type' => 'varchar', 'length' => 9, 'not null' => TRUE, 'default' => ''), 'filename' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + 'module' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + 'suffix' => array('type' => 'varchar', 'length' => 69, 'not null' => TRUE, 'default' => ''), + 'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), ), 'primary key' => array('name', 'type'), + 'indexes' => array( + 'hook' => array('type', 'suffix', 'weight', 'module'), + ), ); $schema['registry_file'] = array( 'fields' => array( diff --git a/modules/system/system.module b/modules/system/system.module index c448a9143895b32413bf39b3aeaf1ca93c0b1305..48ac89a63b122e2bab2427ffd29d464b674c55a6 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -935,7 +935,7 @@ function system_check_directory($form_element) { */ function system_get_files_database(&$files, $type) { // Extract current files from database. - $result = db_query("SELECT filename, name, type, status, schema_version FROM {system} WHERE type = '%s'", $type); + $result = db_query("SELECT filename, name, type, status, schema_version, weight FROM {system} WHERE type = '%s'", $type); while ($file = db_fetch_object($result)) { if (isset($files[$file->name]) && is_object($files[$file->name])) { $file->old_filename = $file->filename;