diff --git a/includes/cache.inc b/includes/cache.inc index 8666874..27e21bc 100644 --- a/includes/cache.inc +++ b/includes/cache.inc @@ -506,3 +506,140 @@ class DrupalDatabaseCache implements DrupalCacheInterface { return empty($result); } } + +/** + * Interface for allowing ArrayObject to be used as a caching wrapper. + * + * Extend CacheArrayObject rather than implementing this interface. This + * mainly exists to document/enforce methods for which there is no default + * implementation in CacheArrayObject. + */ +Interface CacheArrayObjectInterface { + + /** + * Resolve a cache miss. + * + * When an offset is not found in the object, ensure it is populated. A + * a value of FALSE should be used to indicate that the item does not exist + * at all, and is not considered a valid value for ArrayObjects implementing + * this method. + * + * @param $offset + * The offset that was requested. + */ + public function resolveCacheMiss($offset); + + /** + * Whether the array can be written to cache this request. + * + * @return + * Boolean to indicate cacheability of the arrayObject. + */ + public function cacheable(); +} + +/** + * Extends ArrayObject to enable it to be used as a caching wrapper. + * + * This class should be extended by systems that need to cache large amounts + * of data and have it represented as an array to calling functions. These + * arrays can become very large, so ArrayObject is used to allow different + * strategies to be used for caching internally (lazy loading, building caches + * over time etc.). This can dramatically reduce the amount of data that needs + * to be loaded from cache backends on each request, and memory usage from + * static caches of that same data. + * + * Note that array_key_exists() does not return the correct value on an + * arrayObject(). Additionally the methods provided here do not support the use + * of FALSE or NULL as values for array offsets. 0, '0', '' and array() or any + * other valid value for an array can be used without modification. + * + * Classes extending this must implement at least the __construct() and + * resolveCacheMiss() methods. + * + * @see ThemeRegistry + */ +class CacheArrayObject extends ArrayObject implements CacheArrayObjectInterface { + + /** + * The unique cid to pass to cache_set(). + */ + private $cid; + + /** + * The bin to cache the array in. + */ + private $bin; + + /** + * An array of keys and values to add to the existing cache item. + */ + protected $cache_merge; + + + /** + * Constructor. + * + * @param $cid + * The cid for the array being cached. + * @param $bin + * The bin to cache the array.a + */ + function __construct($cid, $bin) { + $this->cid = $cid; + $this->bin = $bin; + + if ($cached = cache_get($this->cid, $this->bin)) { + parent::__construct($cached->data); + } + else { + parent::__construct(array()); + } + } + + /** + * Check whether an array offset exists. + */ + public function offsetExists($offset) { + if (!parent::offsetExists($offset)) { + $this->resolveCacheMiss($offset); + } + return parent::offsetGet($offset) !== FALSE; + } + + public function offsetGet($offset) { + if (!parent::offsetExists($offset)) { + $this->resolveCacheMiss($offset); + } + return parent::offsetGet($offset); + } + + public function resolveCacheMiss($offset) { + // Setting the offset to FALSE causes offsetGet() and offsetSet() + // to act as if the item does not exist in the array. Most implementations + // will want to override this method to consult the structure being cached + // and set offset to the value if it exists there. + parent::offsetSet($offset, FALSE); + $this->cache_merge[$offset] = $this->$offset; + } + + /** + * Whether the array can written to cache this request. + */ + public function cacheable() { + return !empty($this->cache_merge); + } + + public function __destruct() { + if ($this->cacheable()) { + if ($cached = cache_get($this->cid, $this->bin)) { + $cache = array_merge($cached->data, $this->cache_merge); + } + else { + $cache = $this->cache_merge; + } + cache_set($this->cid, $cache, $this->bin); + } + } +} + diff --git a/includes/common.inc b/includes/common.inc index fbad974..d7af5ae 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -2333,7 +2333,7 @@ function l($text, $path, array $options = array()) { // rendering. if (variable_get('theme_link', TRUE)) { drupal_theme_initialize(); - $registry = theme_get_registry(); + $registry = theme_get_registry_runtime(); // We don't want to duplicate functionality that's in theme(), so any // hint of a module or theme doing anything at all special with the 'link' // theme hook should simply result in theme() being called. This includes diff --git a/includes/theme.inc b/includes/theme.inc index c211248..db219d2 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -304,6 +304,41 @@ function _theme_save_registry($theme, $registry) { } /** + * Get the theme registry. + * + * @return + * The theme registry array if it has been stored in memory, NULL otherwise. + */ +function theme_get_registry_runtime() { + static $theme_registry = NULL; + + if (!isset($theme_registry)) { + list($callback, $arguments) = _theme_registry_callback(); + if (function_exists($callback . '_runtime')) { + $callback = $callback . '_runtime'; + } + $theme_registry = call_user_func_array($callback, $arguments); + } + + return $theme_registry; +} + +/** + * Get the runtime theme registry. + * + * @param $theme + * The loaded $theme object as returned by list_themes(). + * @param $base_theme + * An array of loaded $theme objects representing the ancestor themes in + * oldest first order. + * @param theme_engine + * The name of the theme engine. + */ +function _theme_load_registry_runtime($theme, $base_theme = NULL, $theme_engine = NULL) { + return new ThemeRegistry($theme, $base_theme, $theme_engine); +} + +/** * Force the system to rebuild the theme registry; this should be called * when modules are added to the system, or when a dynamic system needs * to add more theme hooks. @@ -313,6 +348,47 @@ function drupal_theme_rebuild() { } /** + * Builds the run-time theme registry. + * + * This class extends the CacheArrayObject class to allow the theme registry to be + * accessed as a complete registry, while internally caching only the parts of + * the registry that are actually in use on the site. On cache misses the + * complete theme registry is loade and used to update the run-time cache. + */ +class ThemeRegistry Extends CacheArrayObject { + private $theme; + private $base_theme; + private $theme_engine; + + function __construct($theme, $base_theme = NULL, $theme_engine = NULL) { + // Make the arguments available to the rest of the class. + $this->theme = $theme; + $this->base_theme = $base_theme; + $this->theme_engine = $theme_engine; + $this->cid = 'theme_registry:runtime:' . $theme->name; + $this->bin = 'cache'; + $this->cache_merge = array(); + + return parent::__construct($this->cid, $this->bin); + } + + public function resolveCacheMiss($offset) { + $complete_registry = theme_get_registry(); + if (isset($complete_registry[$offset])) { + parent::offsetSet($offset, $complete_registry[$offset]); + } + else { + parent::offsetSet($offset, FALSE); + } + $this->cache_merge[$offset] = $this->$offset; + } + + public function cacheable() { + return !empty($this->cache_merge) && module_load_all(NULL); + } +} + +/** * Process a single implementation of hook_theme(). * * @param $cache @@ -760,7 +836,7 @@ function theme($hook, $variables = array()) { if (!isset($hooks)) { drupal_theme_initialize(); - $hooks = theme_get_registry(); + $hooks = theme_get_registry_runtime(); } // If an array of hook candidates were passed, use the first one that has an diff --git a/modules/contextual/contextual.module b/modules/contextual/contextual.module index 0d6b625..edab781 100644 --- a/modules/contextual/contextual.module +++ b/modules/contextual/contextual.module @@ -82,16 +82,12 @@ function contextual_element_info() { * @see contextual_pre_render_links() */ function contextual_preprocess(&$variables, $hook) { - static $hooks; - // Nothing to do here if the user is not permitted to access contextual links. if (!user_access('access contextual links')) { return; } - if (!isset($hooks)) { - $hooks = theme_get_registry(); - } + $hooks = theme_get_registry_runtime(); // Determine the primary theme function argument. if (!empty($hooks[$hook]['variables'])) {