summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2008-10-31 02:18:22 +0000
committerDries Buytaert <dries@buytaert.net>2008-10-31 02:18:22 +0000
commite2a6a3ed61a405bcda47cf997b06c3cb1cb1c0e0 (patch)
treea1800065aa98d7b480259839b370dbe4b758ce4e
parente6a57469694fea2d576efc9c1d8b46fb6c1cb6c3 (diff)
downloadbrdo-e2a6a3ed61a405bcda47cf997b06c3cb1cb1c0e0.tar.gz
brdo-e2a6a3ed61a405bcda47cf997b06c3cb1cb1c0e0.tar.bz2
- Patch #298600 by chx, justinrandell, Damien, et al: make module_implements work regardless of bootstrap phase.
-rw-r--r--includes/bootstrap.inc41
-rw-r--r--includes/common.inc4
-rw-r--r--includes/menu.inc2
-rw-r--r--includes/module.inc116
-rw-r--r--includes/registry.inc30
-rw-r--r--modules/simpletest/tests/registry.test6
-rw-r--r--modules/system/system.install29
-rw-r--r--modules/system/system.module2
8 files changed, 153 insertions, 77 deletions
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 171e9d77e..55bf09574 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;
}
/**
@@ -1509,31 +1499,6 @@ function 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.
*/
function registry_cache_path_files() {
diff --git a/includes/common.inc b/includes/common.inc
index 3600cdd9e..6437d36ee 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 c620228c0..eb806b7d5 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 9df64a66f..c5c3686fc 100644
--- a/includes/module.inc
+++ b/includes/module.inc
@@ -7,6 +7,17 @@
*/
/**
+ * 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.
*/
function module_load_all() {
@@ -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,48 +402,103 @@ 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.
*
* @param $module
diff --git a/includes/registry.inc b/includes/registry.inc
index d240caa9d..c02811182 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 a167178b6..794403dac 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 dbd6c8f29..f84bc570c 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 c448a9143..48ac89a63 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;