Commit 256d8504 authored by webchick's avatar webchick

Issue #1011614 by catch: Fixed Theme registry can grow too large for MySQL...

Issue #1011614 by catch: Fixed Theme registry can grow too large for MySQL max_allowed_packet() and memcache default slab size.
parent a14ac6a9
......@@ -237,18 +237,33 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb
/**
* Get the theme registry.
*
* @param $complete
* Optional boolean to indicate whether to return the complete theme registry
* array or an instance of the ThemeRegistry class. If TRUE, the complete
* theme registry array will be returned. This is useful if you want to
* foreach over the whole registry, use array_* functions or inspect it in a
* debugger. If FALSE, an instance of the ThemeRegistry class will be
* returned, this provides an ArrayObject which allows it to be accessed
* with array syntax and isset(), and should be more lightweight
* than the full registry. Defaults to TRUE.
*
* @return
* The theme registry array if it has been stored in memory, NULL otherwise.
* The complete theme registry array, or an instance of the ThemeRegistry
* class.
*/
function theme_get_registry() {
static $theme_registry = NULL;
function theme_get_registry($complete = TRUE) {
static $theme_registry = array();
$key = (int) $complete;
if (!isset($theme_registry)) {
if (!isset($theme_registry[$key])) {
list($callback, $arguments) = _theme_registry_callback();
$theme_registry = call_user_func_array($callback, $arguments);
if (!$complete) {
$arguments[] = FALSE;
}
$theme_registry[$key] = call_user_func_array($callback, $arguments);
}
return $theme_registry;
return $theme_registry[$key];
}
/**
......@@ -268,7 +283,7 @@ function _theme_registry_callback($callback = NULL, array $arguments = array())
}
/**
* Get the theme_registry cache from the database; if it doesn't exist, build it.
* Get the theme_registry cache; if it doesn't exist, build it.
*
* @param $theme
* The loaded $theme object as returned by list_themes().
......@@ -277,23 +292,34 @@ function _theme_registry_callback($callback = NULL, array $arguments = array())
* oldest first order.
* @param $theme_engine
* The name of the theme engine.
* @param $complete
* Whether to load the complete theme registry or an instance of the
* ThemeRegistry class.
*
* @return
* The theme registry array, or an instance of the ThemeRegistry class.
*/
function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL) {
// Check the theme registry cache; if it exists, use it.
$cache = cache()->get("theme_registry:$theme->name");
if (isset($cache->data)) {
$registry = $cache->data;
function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, $complete = TRUE) {
if ($complete) {
// Check the theme registry cache; if it exists, use it.
$cached = cache()->get("theme_registry:$theme->name");
if (isset($cached->data)) {
$registry = $cached->data;
}
else {
// If not, build one and cache it.
$registry = _theme_build_registry($theme, $base_theme, $theme_engine);
// Only persist this registry if all modules are loaded. This assures a
// complete set of theme hooks.
if (module_load_all(NULL)) {
_theme_save_registry($theme, $registry);
}
}
return $registry;
}
else {
// If not, build one and cache it.
$registry = _theme_build_registry($theme, $base_theme, $theme_engine);
// Only persist this registry if all modules are loaded. This assures a
// complete set of theme hooks.
if (module_load_all(NULL)) {
_theme_save_registry($theme, $registry);
}
return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache');
}
return $registry;
}
/**
......@@ -312,6 +338,104 @@ function drupal_theme_rebuild() {
cache()->deletePrefix('theme_registry');
}
/**
* Builds the run-time theme registry.
*
* Extends DrupalCacheArray 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 loaded and used to update the run-time cache.
*/
class ThemeRegistry Extends DrupalCacheArray {
/**
* Whether the partial registry can be persisted to the cache.
*
* This is only allowed if all modules and the request method is GET. theme()
* should be very rarely called on POST requests and this avoids polluting
* the runtime cache.
*/
protected $persistable;
/**
* The complete theme registry array.
*/
protected $completeRegistry;
function __construct($cid, $bin) {
$this->cid = $cid;
$this->bin = $bin;
$this->persistable = module_load_all(NULL) && $_SERVER['REQUEST_METHOD'] == 'GET';
$data = array();
if ($this->persistable && $cached = cache($this->bin)->get($this->cid)) {
$data = $cached->data;
}
else {
$complete_registry = theme_get_registry();
if ($this->persistable) {
// If there is no runtime cache stored, fetch the full theme registry,
// but then initialize each value to NULL. This allows
// offsetExists() to function correctly on non-registered theme hooks
// without triggering a call to resolveCacheMiss().
$data = array_fill_keys(array_keys($complete_registry), NULL);
$this->set($this->cid, $data, $this->bin);
$this->completeRegistry = $complete_registry;
}
else {
$data = $complete_registry;
}
}
$this->storage = $data;
}
public function offsetExists($offset) {
// Since the theme registry allows for theme hooks to be requested that
// are not registered, just check the existence of the key in the registry.
// Use array_key_exists() here since a NULL value indicates that the theme
// hook exists but has not yet been requested.
return array_key_exists($offset, $this->storage);
}
public function offsetGet($offset) {
// If the offset is set but empty, it is a registered theme hook that has
// not yet been requested. Offsets that do not exist at all were not
// registered in hook_theme().
if (isset($this->storage[$offset])) {
return $this->storage[$offset];
}
elseif (array_key_exists($offset, $this->storage)) {
return $this->resolveCacheMiss($offset);
}
}
public function resolveCacheMiss($offset) {
if (!isset($this->completeRegistry)) {
$this->completeRegistry = theme_get_registry();
}
$this->storage[$offset] = $this->completeRegistry[$offset];
if ($this->persistable) {
$this->persist($offset);
}
return $this->storage[$offset];
}
public function set($cid, $data, $bin, $lock = TRUE) {
$lock_name = $cid . ':' . $bin;
if (!$lock || lock_acquire($lock_name)) {
if ($cached = cache($bin)->get($cid)) {
// Use array merge instead of union so that filled in values in $data
// overwrite empty values in the current cache.
$data = array_merge($cached->data, $data);
}
cache($bin)->set($cid, $data);
if ($lock) {
lock_release($lock_name);
}
}
}
}
/**
* Process a single implementation of hook_theme().
*
......@@ -771,7 +895,7 @@ function theme($hook, $variables = array()) {
if (!isset($hooks)) {
drupal_theme_initialize();
$hooks = theme_get_registry();
$hooks = theme_get_registry(FALSE);
}
// If an array of hook candidates were passed, use the first one that has an
......
......@@ -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(FALSE);
// Determine the primary theme function argument.
if (!empty($hooks[$hook]['variables'])) {
......
......@@ -265,7 +265,7 @@ class ThemeFunctionsTestCase extends DrupalWebTestCase {
/**
* Unit tests for theme_links().
*/
class ThemeLinksUnitTest extends DrupalUnitTestCase {
class ThemeLinksTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Links',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment