From a1b360ece162347faad011af7c6748f54c0b30c2 Mon Sep 17 00:00:00 2001 From: catch Date: Wed, 11 Jan 2012 00:25:36 +0900 Subject: [PATCH] Issue #1371484 by catch, makara, ArtistConk: Fixed Private properties in abstract class DrupalCacheArray. --- core/includes/bootstrap.inc | 18 +++---- core/includes/common.inc | 2 +- core/includes/theme.inc | 42 +++++++++------ core/modules/simpletest/tests/theme.test | 51 +++++++++++++++++++ .../simpletest/tests/theme_test.module | 3 ++ 5 files changed, 89 insertions(+), 27 deletions(-) diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 35c9e0c4bbc..7a2960ce52f 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -296,12 +296,12 @@ abstract class DrupalCacheArray implements ArrayAccess { /** * A cid to pass to cache()->set() and cache()->get(). */ - private $cid; + protected $cid; /** * A bin to pass to cache()->set() and cache()->get(). */ - private $bin; + protected $bin; /** * An array of keys to add to the cache at the end of the request. @@ -400,24 +400,20 @@ abstract class DrupalCacheArray implements ArrayAccess { /** * Writes a value to the persistent cache immediately. * - * @param $cid - * The cache ID. - * @param $bin - * The cache bin. * @param $data * The data to write to the persistent cache. * @param $lock * Whether to acquire a lock before writing to cache. */ - protected function set($cid, $data, $bin, $lock = TRUE) { + protected function set($data, $lock = TRUE) { // Lock cache writes to help avoid stampedes. // To implement locking for cache misses, override __construct(). - $lock_name = $cid . ':' . $bin; + $lock_name = $this->cid . ':' . $this->bin; if (!$lock || lock_acquire($lock_name)) { - if ($cached = cache($bin)->get($cid)) { + if ($cached = cache($this->bin)->get($this->cid)) { $data = $cached->data + $data; } - cache($bin)->set($cid, $data); + cache($this->bin)->set($this->cid, $data); if ($lock) { lock_release($lock_name); } @@ -435,7 +431,7 @@ abstract class DrupalCacheArray implements ArrayAccess { } } if (!empty($data)) { - $this->set($this->cid, $data, $this->bin); + $this->set($data); } } } diff --git a/core/includes/common.inc b/core/includes/common.inc index 668dc39ed7c..3bfe9ed8518 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2336,7 +2336,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(FALSE); // 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/core/includes/theme.inc b/core/includes/theme.inc index 94d69d228f2..060d487bfbf 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -372,23 +372,31 @@ class ThemeRegistry Extends DrupalCacheArray { $data = $cached->data; } else { - $complete_registry = theme_get_registry(); + // 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 = $this->initializeRegistry(); 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->set($data); } } $this->storage = $data; } + /** + * Initializes the full theme registry. + * + * @return + * An array with the keys of the full theme registry, but the values + * initialized to NULL. + */ + function initializeRegistry() { + $this->completeRegistry = theme_get_registry(); + + return array_fill_keys(array_keys($this->completeRegistry), NULL); + } + 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. @@ -420,15 +428,19 @@ class ThemeRegistry Extends DrupalCacheArray { return $this->storage[$offset]; } - public function set($cid, $data, $bin, $lock = TRUE) { - $lock_name = $cid . ':' . $bin; + public function set($data, $lock = TRUE) { + $lock_name = $this->cid . ':' . $this->bin; if (!$lock || lock_acquire($lock_name)) { - if ($cached = cache($bin)->get($cid)) { + if ($cached = cache($this->bin)->get($this->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); + else { + $registry = $this->initializeRegistry(); + $data = array_merge($registry, $data); + } + cache($this->bin)->set($this->cid, $data); if ($lock) { lock_release($lock_name); } diff --git a/core/modules/simpletest/tests/theme.test b/core/modules/simpletest/tests/theme.test index 6980b191b08..9870545b56c 100644 --- a/core/modules/simpletest/tests/theme.test +++ b/core/modules/simpletest/tests/theme.test @@ -541,3 +541,54 @@ class ThemeHtmlTplPhpAttributesTestCase extends DrupalWebTestCase { $this->assertTrue(count($attributes) == 1, t('Attribute set in the body element via hook_preprocess_html() found.')); } } + +/** + * Tests for the ThemeRegistry class. + */ +class ThemeRegistryTestCase extends DrupalWebTestCase { + protected $profile = 'testing'; + public static function getInfo() { + return array( + 'name' => 'ThemeRegistry', + 'description' => 'Tests the behavior of the ThemeRegistry class', + 'group' => 'Theme', + ); + } + function setUp() { + parent::setUp('theme_test'); + } + + /** + * Tests the behavior of the theme registry class. + */ + function testRaceCondition() { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $cid = 'test_theme_registry'; + + // Directly instantiate the theme registry, this will cause a base cache + // entry to be written in __construct(). + $registry = new ThemeRegistry($cid, 'cache'); + + $this->assertTrue(cache()->get($cid), 'Cache entry was created.'); + + // Trigger a cache miss for an offset. + $this->assertTrue($registry['theme_test_template_test'], 'Offset was returned correctly from the theme registry.'); + // This will cause the ThemeRegistry class to write an updated version of + // the cache entry when it is destroyed, usually at the end of the request. + // Before that happens, manually delete the cache entry we created earlier + // so that the new entry is written from scratch. + cache()->delete($cid); + + // Destroy the class so that it triggers a cache write for the offset. + unset($registry); + + $this->assertTrue(cache()->get($cid), 'Cache entry was created.'); + + // Create a new instance of the class. Confirm that both the offset + // requested previously, and one that has not yet been requested are both + // available. + $registry = new ThemeRegistry($cid, 'cache'); + $this->assertTrue($registry['theme_test_template_test'], 'Offset was returned correctly from the theme registry'); + $this->assertTrue($registry['theme_test_template_test_2'], 'Offset was returned correctly from the theme registry'); + } +} diff --git a/core/modules/simpletest/tests/theme_test.module b/core/modules/simpletest/tests/theme_test.module index 2a2552aede4..570b72ccadb 100644 --- a/core/modules/simpletest/tests/theme_test.module +++ b/core/modules/simpletest/tests/theme_test.module @@ -7,6 +7,9 @@ function theme_test_theme($existing, $type, $theme, $path) { $items['theme_test_template_test'] = array( 'template' => 'theme_test.template_test', ); + $items['theme_test_template_test_2'] = array( + 'template' => 'theme_test.template_test', + ); return $items; }