summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2008-05-06 12:18:54 +0000
committerDries Buytaert <dries@buytaert.net>2008-05-06 12:18:54 +0000
commit2e18cb8924eb9a83a0ec9857f405ed038a1d3ded (patch)
tree5159327c54df6625e8377db268e8b074b43ae79c /includes
parentc100468cf232d34b85534277d3fc01ee95f02256 (diff)
downloadbrdo-2e18cb8924eb9a83a0ec9857f405ed038a1d3ded.tar.gz
brdo-2e18cb8924eb9a83a0ec9857f405ed038a1d3ded.tar.bz2
- Patch #221964 by chx, dopry, webernet, moshe, webchick, justinrandall, flobruit
et al. Can you say 'registry'? Drupal now maintains an internal registry of all functions or classes in the system, allowing it to lazy-load code files as needed (reducing the amount of code that must be parsed on each request). The list of included files is cached per menu callback for subsequent loading by the menu router. This way, a given page request will have all the code it needs but little else, minimizing time spent parsing unneeded code.
Diffstat (limited to 'includes')
-rw-r--r--includes/bootstrap.inc182
-rw-r--r--includes/common.inc13
-rw-r--r--includes/form.inc13
-rw-r--r--includes/install.inc5
-rw-r--r--includes/mail.inc2
-rw-r--r--includes/menu.inc38
-rw-r--r--includes/module.inc55
-rw-r--r--includes/theme.inc6
-rw-r--r--includes/xmlrpcs.inc2
9 files changed, 258 insertions, 58 deletions
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 49659f8ca..c5a8ba404 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -949,6 +949,9 @@ function _drupal_bootstrap($phase) {
// Initialize the default database.
require_once './includes/database.inc';
db_set_active();
+ // Register autoload functions so that we can access classes and interfaces.
+ spl_autoload_register('drupal_autoload_class');
+ spl_autoload_register('drupal_autoload_interface');
break;
case DRUPAL_BOOTSTRAP_ACCESS:
@@ -1134,3 +1137,182 @@ function ip_address() {
return $ip_address;
}
+
+/**
+ * @ingroup registry
+ * @{
+ */
+
+/**
+ * Confirm that a function is available.
+ *
+ * If the function is already available, this function does nothing.
+ * If the function is not available, it tries to load the file where the
+ * function lives. If the file is not available, it returns false, so that it
+ * can be used as a drop-in replacement for function_exists().
+ *
+ * @param $function
+ * The name of the function to check or load.
+ * @return
+ * TRUE if the function is now available, FALSE otherwise.
+ */
+function drupal_function_exists($function) {
+ static $checked = array();
+
+ if (defined('MAINTENANCE_MODE')) {
+ return function_exists($function);
+ }
+
+ if (isset($checked[$function])) {
+ return $checked[$function];
+ }
+ $checked[$function] = FALSE;
+
+ if (function_exists($function)) {
+ registry_mark_code('function', $function);
+ $checked[$function] = TRUE;
+ return TRUE;
+ }
+
+ $file = db_result(db_query("SELECT filename FROM {registry} WHERE name = '%s' AND type = '%s'", $function, 'function'));
+ if ($file) {
+ require_once($file);
+ $checked[$function] = function_exists($function);
+ if ($checked[$function]) {
+ registry_mark_code('function', $function);
+ }
+ }
+
+ return $checked[$function];
+}
+
+/**
+ * Confirm that an interface is available.
+ *
+ * This function parallels drupal_function_exists(), but is rarely
+ * called directly. Instead, it is registered as an spl_autoload()
+ * handler, and PHP calls it for us when necessary.
+ *
+ * @param $interface
+ * The name of the interface to check or load.
+ * @return
+ * TRUE if the interface is currently available, FALSE otherwise.
+ */
+function drupal_autoload_interface($interface) {
+ return _registry_check_code('interface', $interface);
+}
+
+/**
+ * Confirm that a class is available.
+ *
+ * This function parallels drupal_function_exists(), but is rarely
+ * called directly. Instead, it is registered as an spl_autoload()
+ * handler, and PHP calls it for us when necessary.
+ *
+ * @param $class
+ * The name of the class to check or load.
+ * @return
+ * TRUE if the class is currently available, FALSE otherwise.
+ */
+function drupal_autoload_class($class) {
+ return _registry_check_code('class', $class);
+}
+
+/**
+ * Helper for registry_check_{interface, class}.
+ */
+function _registry_check_code($type, $name) {
+ $file = db_result(db_query("SELECT filename FROM {registry} WHERE name = '%s' AND type = '%s'", $name, $type));
+ if ($file) {
+ require_once($file);
+ registry_mark_code($type, $name);
+ return TRUE;
+ }
+}
+
+/**
+ * Collect the resources used for this request.
+ *
+ * @param $type
+ * The type of resource.
+ * @param $name
+ * The name of the resource.
+ * @param $return
+ * Boolean flag to indicate whether to return the resources.
+ */
+function registry_mark_code($type, $name, $return = FALSE) {
+ static $resources = array();
+
+ if ($type && $name) {
+ if (!isset($resources[$type])) {
+ $resources[$type] = array();
+ }
+ if (!in_array($name, $resources[$type])) {
+ $resources[$type][] = $name;
+ }
+ }
+
+ if ($return) {
+ return $resources;
+ }
+}
+
+/**
+ * Rescan all enabled modules and rebuild the registry.
+ *
+ * Rescans all code in modules or includes directory, storing a mapping of
+ * each function, file, and hook implementation in the database.
+ */
+function drupal_rebuild_code_registry() {
+ require_once './includes/registry.inc';
+ _drupal_rebuild_code_registry();
+}
+
+/**
+ * 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) {
+ cache_set('hooks', $implementations, 'cache_registry');
+ }
+}
+
+/**
+ * Save the files required by the registry for this path.
+ */
+function registry_cache_path_files() {
+ if ($used_code = registry_mark_code(NULL, NULL, TRUE)) {
+ $files = array();
+ $type_sql = array();
+ $params = array();
+ foreach ($used_code as $type => $names) {
+ $type_sql[] = "(name IN (" . db_placeholders($names, 'varchar') . ") AND type = '%s')";
+ $params = array_merge($params, $names);
+ $params[] = $type;
+ }
+ $res = db_query("SELECT DISTINCT filename FROM {registry} WHERE " . implode(' OR ', $type_sql), $params);
+ while ($row = db_fetch_object($res)) {
+ $files[] = $row->filename;
+ }
+ if ($files) {
+ $menu = menu_get_item();
+ cache_set('registry:' . $menu['path'], implode(';', $files), 'cache_registry');
+ }
+ }
+}
+
+/**
+ * @} End of "ingroup registry".
+ */
diff --git a/includes/common.inc b/includes/common.inc
index 371f3443f..710d2e3c2 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -1460,11 +1460,15 @@ function l($text, $path, $options = array()) {
* react to the closing of the page by calling hook_exit().
*/
function drupal_page_footer() {
+
if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) {
page_set_cache();
}
module_invoke_all('exit');
+
+ registry_cache_hook_implementations(FALSE, TRUE);
+ registry_cache_path_files();
}
/**
@@ -2700,7 +2704,7 @@ function drupal_render(&$elements) {
// element is rendered into the final text.
if (isset($elements['#pre_render'])) {
foreach ($elements['#pre_render'] as $function) {
- if (function_exists($function)) {
+ if (drupal_function_exists($function)) {
$elements = $function($elements);
}
}
@@ -2762,7 +2766,7 @@ function drupal_render(&$elements) {
// which allows the output'ed text to be filtered.
if (isset($elements['#post_render'])) {
foreach ($elements['#post_render'] as $function) {
- if (function_exists($function)) {
+ if (drupal_function_exists($function)) {
$content = $function($content, $elements);
}
}
@@ -3142,7 +3146,7 @@ function drupal_uninstall_schema($module) {
*/
function drupal_get_schema_unprocessed($module, $table = NULL) {
// Load the .install file to get hook_schema.
- module_load_include('install', $module);
+ module_load_install($module);
$schema = module_invoke($module, 'schema');
if (!is_null($table) && isset($schema[$table])) {
@@ -3528,6 +3532,7 @@ function drupal_flush_all_caches() {
// Change query-strings on css/js files to enforce reload for all users.
_drupal_flush_css_js();
+ drupal_rebuild_code_registry();
drupal_clear_css_cache();
drupal_clear_js_cache();
system_theme_data();
@@ -3536,7 +3541,7 @@ function drupal_flush_all_caches() {
node_types_rebuild();
// Don't clear cache_form - in-progress form submissions may break.
// Ordered so clearing the page cache will always be the last action.
- $core = array('cache', 'cache_block', 'cache_filter', 'cache_page');
+ $core = array('cache', 'cache_block', 'cache_filter', 'cache_registry', 'cache_page');
$cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
foreach ($cache_tables as $table) {
cache_clear_all('*', $table, TRUE);
diff --git a/includes/form.inc b/includes/form.inc
index bb02accab..94aa380b1 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -327,7 +327,7 @@ function drupal_retrieve_form($form_id, &$form_state) {
// We first check to see if there's a function named after the $form_id.
// If there is, we simply pass the arguments on to it to get the form.
- if (!function_exists($form_id)) {
+ if (!drupal_function_exists($form_id)) {
// In cases where many form_ids need to share a central constructor function,
// such as the node editing form, modules can implement hook_forms(). It
// maps one or more form_ids to the correct constructor functions.
@@ -348,6 +348,7 @@ function drupal_retrieve_form($form_id, &$form_state) {
}
if (isset($form_definition['callback'])) {
$callback = $form_definition['callback'];
+ drupal_function_exists($callback);
}
}
@@ -504,13 +505,13 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
$form += _element_info('form');
if (!isset($form['#validate'])) {
- if (function_exists($form_id . '_validate')) {
+ if (drupal_function_exists($form_id . '_validate')) {
$form['#validate'] = array($form_id . '_validate');
}
}
if (!isset($form['#submit'])) {
- if (function_exists($form_id . '_submit')) {
+ if (drupal_function_exists($form_id . '_submit')) {
// We set submit here so that it can be altered.
$form['#submit'] = array($form_id . '_submit');
}
@@ -712,7 +713,7 @@ function _form_validate($elements, &$form_state, $form_id = NULL) {
// #value data.
elseif (isset($elements['#element_validate'])) {
foreach ($elements['#element_validate'] as $function) {
- if (function_exists($function)) {
+ if (drupal_function_exists($function)) {
$function($elements, $form_state, $complete_form);
}
}
@@ -749,7 +750,7 @@ function form_execute_handlers($type, &$form, &$form_state) {
}
foreach ($handlers as $function) {
- if (function_exists($function)) {
+ if (drupal_function_exists($function)) {
if ($type == 'submit' && ($batch =& batch_get())) {
// Some previous _submit handler has set a batch. We store the call
// in a special 'control' batch set, for execution at the correct
@@ -1032,7 +1033,7 @@ function _form_builder_handle_input_element($form_id, &$form, &$form_state, $com
// checkboxes and files.
if (isset($form['#process']) && !$form['#processed']) {
foreach ($form['#process'] as $process) {
- if (function_exists($process)) {
+ if (drupal_function_exists($process)) {
$form = $process($form, isset($edit) ? $edit : NULL, $form_state, $complete_form);
}
}
diff --git a/includes/install.inc b/includes/install.inc
index 9398077a1..54bfd4bc4 100644
--- a/includes/install.inc
+++ b/includes/install.inc
@@ -687,8 +687,9 @@ function drupal_check_profile($profile) {
$requirements = array();
foreach ($installs as $install) {
require_once $install->filename;
- if (module_hook($install->name, 'requirements')) {
- $requirements = array_merge($requirements, module_invoke($install->name, 'requirements', 'install'));
+ $function = $install->name. '_requirements';
+ if (function_exists($function)) {
+ $requirements = array_merge($requirements, $function('install'));
}
}
return $requirements;
diff --git a/includes/mail.inc b/includes/mail.inc
index 56c765e7c..c2394a414 100644
--- a/includes/mail.inc
+++ b/includes/mail.inc
@@ -115,7 +115,7 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
// Build the e-mail (get subject and body, allow additional headers) by
// invoking hook_mail() on this module. We cannot use module_invoke() as
// we need to have $message by reference in hook_mail().
- if (function_exists($function = $module . '_mail')) {
+ if (drupal_function_exists($function = $module . '_mail')) {
$function($key, $message, $params);
}
diff --git a/includes/menu.inc b/includes/menu.inc
index 2e57234dd..1a52d3da4 100644
--- a/includes/menu.inc
+++ b/includes/menu.inc
@@ -339,11 +339,16 @@ function menu_execute_active_handler($path = NULL) {
menu_rebuild();
}
if ($router_item = menu_get_item($path)) {
+ $cache = cache_get('registry:' . $router_item['path'], 'cache_registry');
+ if (!empty($cache->data)) {
+ foreach(explode(';', $cache->data) as $file) {
+ require_once($file);
+ }
+ }
if ($router_item['access']) {
- if ($router_item['file']) {
- require_once($router_item['file']);
+ if (drupal_function_exists($router_item['page_callback'])) {
+ return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
}
- return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
}
else {
return MENU_ACCESS_DENIED;
@@ -1665,7 +1670,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') as $module) {
+ foreach (module_implements('menu', NULL, 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) {
@@ -2201,12 +2206,12 @@ function _menu_router_build($callbacks) {
$load_functions[$k] = NULL;
}
else {
- if (function_exists($matches[1] . '_to_arg')) {
+ if (drupal_function_exists($matches[1] . '_to_arg')) {
$to_arg_functions[$k] = $matches[1] . '_to_arg';
$load_functions[$k] = NULL;
$match = TRUE;
}
- if (function_exists($matches[1] . '_load')) {
+ if (drupal_function_exists($matches[1] . '_load')) {
$function = $matches[1] . '_load';
// Create an array of arguments that will be passed to the _load
// function when this menu path is checked, if 'load arguments'
@@ -2293,12 +2298,6 @@ function _menu_router_build($callbacks) {
if (!isset($item['page arguments']) && isset($parent['page arguments'])) {
$item['page arguments'] = $parent['page arguments'];
}
- if (!isset($item['file']) && isset($parent['file'])) {
- $item['file'] = $parent['file'];
- }
- if (!isset($item['file path']) && isset($parent['file path'])) {
- $item['file path'] = $parent['file path'];
- }
}
}
}
@@ -2326,34 +2325,25 @@ function _menu_router_build($callbacks) {
'tab_parent' => '',
'tab_root' => $path,
'path' => $path,
- 'file' => '',
- 'file path' => '',
- 'include file' => '',
);
- // Calculate out the file to be included for each callback, if any.
- if ($item['file']) {
- $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']);
- $item['include file'] = $file_path . '/' . $item['file'];
- }
-
$title_arguments = $item['title arguments'] ? serialize($item['title arguments']) : '';
db_query("INSERT INTO {menu_router}
(path, load_functions, to_arg_functions, access_callback,
access_arguments, page_callback, page_arguments, fit,
number_parts, tab_parent, tab_root,
title, title_callback, title_arguments,
- type, block_callback, description, position, weight, file)
+ type, block_callback, description, position, weight)
VALUES ('%s', '%s', '%s', '%s',
'%s', '%s', '%s', %d,
%d, '%s', '%s',
'%s', '%s', '%s',
- %d, '%s', '%s', '%s', %d, '%s')",
+ %d, '%s', '%s', '%s', %d)",
$path, $item['load_functions'], $item['to_arg_functions'], $item['access callback'],
serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['_fit'],
$item['_number_parts'], $item['tab_parent'], $item['tab_root'],
$item['title'], $item['title callback'], $title_arguments,
- $item['type'], $item['block callback'], $item['description'], $item['position'], $item['weight'], $item['include file']);
+ $item['type'], $item['block callback'], $item['description'], $item['position'], $item['weight']);
}
// Sort the masks so they are in order of descending fit, and store them.
$masks = array_keys($masks);
diff --git a/includes/module.inc b/includes/module.inc
index d0f100298..e62558af4 100644
--- a/includes/module.inc
+++ b/includes/module.inc
@@ -221,7 +221,7 @@ function _module_build_dependencies($files) {
*/
function module_exists($module) {
$list = module_list();
- return array_key_exists($module, $list);
+ return isset($list[$module]);
}
/**
@@ -231,7 +231,14 @@ function module_load_install($module) {
// Make sure the installation API is available
include_once './includes/install.inc';
- module_load_include('install', $module);
+ $file = module_load_include('install', $module);
+ // Ensure that you can module_invoke something inside the newly-loaded
+ // file during install.
+ $module_list = module_list();
+ if (!isset($module_list[$module])) {
+ $module_list[$module]['filename'] = $file;
+ module_list(TRUE, FALSE, FALSE, $module_list);
+ }
}
/**
@@ -253,6 +260,7 @@ function module_load_include($type, $module, $name = NULL) {
if (is_file($file)) {
require_once $file;
+ return $file;
}
else {
return FALSE;
@@ -292,7 +300,7 @@ function module_enable($module_list) {
// Refresh the module list to include the new enabled module.
module_list(TRUE, FALSE);
// Force to regenerate the stored list of hook implementations.
- module_implements('', FALSE, TRUE);
+ drupal_rebuild_code_registry();
}
foreach ($invoke_modules as $module) {
@@ -301,7 +309,7 @@ function module_enable($module_list) {
// We check for the existence of node_access_needs_rebuild() since
// at install time, module_enable() could be called while node.module
// is not enabled yet.
- if (function_exists('node_access_needs_rebuild') && !node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
+ if (drupal_function_exists('node_access_needs_rebuild') && !node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
node_access_needs_rebuild(TRUE);
}
}
@@ -333,7 +341,7 @@ function module_disable($module_list) {
// Refresh the module list to exclude the disabled modules.
module_list(TRUE, FALSE);
// Force to regenerate the stored list of hook implementations.
- module_implements('', FALSE, TRUE);
+ drupal_rebuild_code_registry();
}
// If there remains no more node_access module, rebuilding will be
@@ -376,7 +384,13 @@ function module_disable($module_list) {
* implemented in that module.
*/
function module_hook($module, $hook) {
- return function_exists($module . '_' . $hook);
+ $function = $module . '_' . $hook;
+ if (defined('MAINTENANCE_MODE')) {
+ return function_exists($function);
+ }
+ else {
+ return drupal_function_exists($function);
+ }
}
/**
@@ -395,22 +409,26 @@ function module_hook($module, $hook) {
* An array with the names of the modules which are implementing this hook.
*/
function module_implements($hook, $sort = FALSE, $refresh = FALSE) {
- static $implementations;
+ static $implementations = array();
if ($refresh) {
$implementations = array();
- return;
+ }
+ else if (!defined('MAINTENANCE_MODE') && empty($implementations)) {
+ if ($cache = cache_get('hooks', 'cache_registry')) {
+ $implementations = $cache->data;
+ }
}
if (!isset($implementations[$hook])) {
$implementations[$hook] = array();
- $list = module_list(FALSE, TRUE, $sort);
- foreach ($list as $module) {
+ foreach (module_list() as $module) {
if (module_hook($module, $hook)) {
$implementations[$hook][] = $module;
}
}
}
+ registry_cache_hook_implementations(array('hook' => $hook, 'modules' => $implementations[$hook]));
// The explicit cast forces a copy to be made. This is needed because
// $implementations[$hook] is only a reference to an element of
@@ -438,9 +456,8 @@ function module_invoke() {
$module = $args[0];
$hook = $args[1];
unset($args[0], $args[1]);
- $function = $module . '_' . $hook;
if (module_hook($module, $hook)) {
- return call_user_func_array($function, $args);
+ return call_user_func_array($module . '_' . $hook, $args);
}
}
/**
@@ -461,12 +478,14 @@ function module_invoke_all() {
$return = array();
foreach (module_implements($hook) as $module) {
$function = $module . '_' . $hook;
- $result = call_user_func_array($function, $args);
- if (isset($result) && is_array($result)) {
- $return = array_merge_recursive($return, $result);
- }
- else if (isset($result)) {
- $return[] = $result;
+ if (drupal_function_exists($function)) {
+ $result = call_user_func_array($function, $args);
+ if (isset($result) && is_array($result)) {
+ $return = array_merge_recursive($return, $result);
+ }
+ else if (isset($result)) {
+ $return[] = $result;
+ }
}
}
diff --git a/includes/theme.inc b/includes/theme.inc
index d675e1564..3b4d1dc1c 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -177,7 +177,9 @@ function _init_theme($theme, $base_theme = array(), $registry_callback = '_theme
}
}
- $registry_callback($theme, $base_theme, $theme_engine);
+ if (drupal_function_exists($registry_callback)) {
+ $registry_callback($theme, $base_theme, $theme_engine);
+ }
}
/**
@@ -628,7 +630,7 @@ function theme() {
// call_user_func_array.
$args = array(&$variables, $hook);
foreach ($info['preprocess functions'] as $preprocess_function) {
- if (function_exists($preprocess_function)) {
+ if (drupal_function_exists($preprocess_function)) {
call_user_func_array($preprocess_function, $args);
}
}
diff --git a/includes/xmlrpcs.inc b/includes/xmlrpcs.inc
index 21509d494..ffa5fd8c3 100644
--- a/includes/xmlrpcs.inc
+++ b/includes/xmlrpcs.inc
@@ -202,7 +202,7 @@ function xmlrpc_server_call($xmlrpc_server, $methodname, $args) {
}
}
- if (!function_exists($method)) {
+ if (!drupal_function_exists($method)) {
return xmlrpc_error(-32601, t('Server error. Requested function @method does not exist.', array("@method" => $method)));
}
// Call the mapped function