diff --git a/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php b/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..27aa039c47f0d5160b5f0559a356d6d4d49c66cc --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php @@ -0,0 +1,20 @@ +<?php + +/** + * @file + * Contains \Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface. + */ + +namespace Drupal\Component\Plugin\Discovery; + +/** + * Interface for discovery compenents holding a cache of plugin definitions. + */ +interface CachedDiscoveryInterface extends DiscoveryInterface { + + /** + * Clears cached plugin definitions. + */ + public function clearCachedDefinitions(); + +} diff --git a/core/lib/Drupal/Component/Plugin/PluginManagerBase.php b/core/lib/Drupal/Component/Plugin/PluginManagerBase.php index e4131fcd805b5a2923050c0bab020962a6c8fa2b..c337f9f7bd84703d9be0374c70d20b6b03dd13b7 100644 --- a/core/lib/Drupal/Component/Plugin/PluginManagerBase.php +++ b/core/lib/Drupal/Component/Plugin/PluginManagerBase.php @@ -8,11 +8,12 @@ namespace Drupal\Component\Plugin; use Drupal\Component\Utility\NestedArray; +use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface; /** * Base class for plugin managers. */ -abstract class PluginManagerBase implements PluginManagerInterface { +abstract class PluginManagerBase implements PluginManagerInterface, CachedDiscoveryInterface { /** * The object that discovers plugins managed by this manager. @@ -67,6 +68,15 @@ public function getDefinitions() { return $definitions; } + /** + * Implements \Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface::clearCachedDefinitions(). + */ + public function clearCachedDefinitions() { + if ($this->discovery instanceof CachedDiscoveryInterface) { + $this->discovery->clearCachedDefinitions(); + } + } + /** * Implements Drupal\Component\Plugin\PluginManagerInterface::createInstance(). */ diff --git a/core/lib/Drupal/Core/Plugin/Discovery/CacheDecorator.php b/core/lib/Drupal/Core/Plugin/Discovery/CacheDecorator.php index 7a57395497f64ad25f25b009140b4ee41d342e8d..67d6cabe541181921039a8c2add48e06acd26594 100644 --- a/core/lib/Drupal/Core/Plugin/Discovery/CacheDecorator.php +++ b/core/lib/Drupal/Core/Plugin/Discovery/CacheDecorator.php @@ -7,12 +7,13 @@ namespace Drupal\Core\Plugin\Discovery; +use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface; use Drupal\Component\Plugin\Discovery\DiscoveryInterface; /** * Enables static and persistent caching of discovered plugin definitions. */ -class CacheDecorator implements DiscoveryInterface { +class CacheDecorator implements CachedDiscoveryInterface { /** * The cache key used to store the definition list. @@ -109,6 +110,16 @@ protected function setCachedDefinitions($definitions) { $this->definitions = $definitions; } + /** + * Implements \Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface::clearCachedDefinitions(). + */ + public function clearCachedDefinitions() { + if (isset($this->cacheKey)) { + cache($this->cacheBin)->delete($this->cacheKey); + } + $this->definitions = NULL; + } + /** * Passes through all unknown calls onto the decorated object. */ diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php index 6dc41636cbbc2c838f014427afb266b1bf554550..7f17d344c74bceff59a4c8a3cf9d0e7364841edd 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php @@ -26,41 +26,18 @@ class FormatterPluginManager extends PluginManagerBase { 'default_value' => TRUE, ); - /** - * The cache bin used for plugin definitions. - * - * @var string - */ - protected $cache_bin = 'field'; - - /** - * The cache id used for plugin definitions. - * - * @var string - */ - protected $cache_id = 'field_formatter_types'; - /** * Constructs a FormatterPluginManager object. */ public function __construct() { - $this->baseDiscovery = new AlterDecorator(new FormatterLegacyDiscoveryDecorator(new AnnotatedClassDiscovery('field', 'formatter')), 'field_formatter_info'); - $this->discovery = new CacheDecorator($this->baseDiscovery, $this->cache_id, $this->cache_bin); + $this->discovery = new AnnotatedClassDiscovery('field', 'formatter'); + $this->discovery = new FormatterLegacyDiscoveryDecorator($this->discovery); + $this->discovery = new AlterDecorator($this->discovery, 'field_formatter_info'); + $this->discovery = new CacheDecorator($this->discovery, 'field_formatter_types', 'field'); $this->factory = new FormatterFactory($this); } - /** - * Clears cached definitions. - * - * @todo Remove when http://drupal.org/node/1764232 is fixed. - */ - public function clearDefinitions() { - // Clear 'static' data by creating a new object. - $this->discovery = new CacheDecorator($this->baseDiscovery, $this->cache_id, $this->cache_bin); - cache($this->cache_bin)->delete($this->cache_id); - } - /** * Overrides PluginManagerBase::getInstance(). */ diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php index a6051297dc975e8fa0361019ab6a3435e5a7ed6e..c5407bd1f03b69816ac54c6dc59bd47bee57d667 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php @@ -27,41 +27,18 @@ class WidgetPluginManager extends PluginManagerBase { 'default_value' => TRUE, ); - /** - * The cache bin used for plugin definitions. - * - * @var string - */ - protected $cache_bin = 'field'; - - /** - * The cache id used for plugin definitions. - * - * @var string - */ - protected $cache_id = 'field_widget_types'; - /** * Constructs a WidgetPluginManager object. */ public function __construct() { - $this->baseDiscovery = new AlterDecorator(new WidgetLegacyDiscoveryDecorator(new AnnotatedClassDiscovery('field', 'widget')), 'field_widget_info'); - $this->discovery = new CacheDecorator($this->baseDiscovery, $this->cache_id, $this->cache_bin); + $this->discovery = new AnnotatedClassDiscovery('field', 'widget'); + $this->discovery = new WidgetLegacyDiscoveryDecorator($this->discovery); + $this->discovery = new AlterDecorator($this->discovery, 'field_widget_info'); + $this->discovery = new CacheDecorator($this->discovery, 'field_widget_types', 'field'); $this->factory = new WidgetFactory($this); } - /** - * Clears cached definitions. - * - * @todo Remove when http://drupal.org/node/1764232 is fixed. - */ - public function clearDefinitions() { - // Clear 'static' data by creating a new object. - $this->discovery = new CacheDecorator($this->baseDiscovery, $this->cache_id, $this->cache_bin); - cache($this->cache_bin)->delete($this->cache_id); - } - /** * Overrides Drupal\Component\Plugin\PluginManagerBase::getInstance(). */ diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/CacheDecoratorTest.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/CacheDecoratorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..327e46e18123aa554462bd3b732b754f4b838dfc --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/CacheDecoratorTest.php @@ -0,0 +1,136 @@ +<?php + +/** + * @file + * Contains \Drupal\system\Tests\Plugin\CacheDecoratorTest. + */ + +namespace Drupal\system\Tests\Plugin; + +use Drupal\system\Tests\Plugin\Discovery\DiscoveryTestBase; +use Drupal\Component\Plugin\Discovery\StaticDiscovery; +use Drupal\Core\Plugin\Discovery\CacheDecorator; + +/** + * Tests \Drupal\Core\Plugin\Discovery\CacheDecorator behavior. + */ +class CacheDecoratorTest extends DiscoveryTestBase { + + /** + * The cache bin. + * + * @var string + */ + protected $cacheBin = 'test_cacheDecorator'; + + /** + * The cache key. + * + * @var string + */ + protected $cacheKey = 'test_cacheDecorator'; + + public static function getInfo() { + return array( + 'name' => 'CacheDecorator', + 'description' => 'Tests the CacheDecorator.', + 'group' => 'Plugin API', + ); + } + + public function setUp() { + global $conf; + + parent::setUp(); + + // Use a non-db cache backend, so that we can use DiscoveryTestBase (which + // extends UnitTestBase). + $conf['cache_classes'][$this->cacheBin] = 'Drupal\Core\Cache\MemoryBackend'; + + // Create discovery objects to test. + $this->emptyDiscovery = new StaticDiscovery(); + $this->emptyDiscovery = new CacheDecorator($this->emptyDiscovery, $this->cacheKey . '_empty', $this->cacheBin); + + $this->discovery = new StaticDiscovery(); + $this->discovery = new CacheDecorator($this->discovery, $this->cacheKey, $this->cacheBin); + + // Populate sample definitions. + $this->expectedDefinitions = array( + 'apple' => array( + 'label' => 'Apple', + 'color' => 'green', + ), + 'cherry' => array( + 'label' => 'Cherry', + 'color' => 'red', + ), + 'orange' => array( + 'label' => 'Orange', + 'color' => 'orange', + ), + ); + foreach ($this->expectedDefinitions as $plugin_id => $definition) { + $this->discovery->setDefinition($plugin_id, $definition); + } + } + + /** + * Tests that discovered definitions are properly cached. + * + * This comes in addition to DiscoveryTestBase::testDiscoveryInterface(), + * that test the basic discovery behavior. + */ + public function testCachedDefinitions() { + $cache = cache($this->cacheBin); + + // Check that nothing is cached initially. + $cached = $cache->get($this->cacheKey); + $this->assertIdentical($cached, FALSE, 'Cache is empty.'); + + // Get the definitions once, and check that they are present in the cache. + $definitions = $this->discovery->getDefinitions(); + $this->assertIdentical($definitions, $this->expectedDefinitions, 'Definitions are correctly retrieved.'); + $cached = $cache->get($this->cacheKey); + $this->assertIdentical($cached->data, $this->expectedDefinitions, 'Definitions are cached.'); + + // Check that the definitions are also cached in memory. Since the + // CacheDecorator::definitions property is protected, this is tested "from + // the outside" by wiping the cache entry, getting the definitions, and + // checking that the cache entry was not regenerated (thus showing that + // defintions were not fetched from the decorated discovery). + $cache->delete($this->cacheKey); + $definitions = $this->discovery->getDefinitions(); + $cached = $cache->get($this->cacheKey); + $this->assertIdentical($cached, FALSE, 'Cache is empty.'); + $this->assertIdentical($definitions, $this->expectedDefinitions, 'Definitions are cached in memory.'); + } + + /** + * Tests CacheDecorator::clearCachedDefinitions(). + */ + public function testClearCachedDefinitions() { + $cache = cache($this->cacheBin); + + // Populate the caches by collecting definitions once. + $this->discovery->getDefinitions(); + + // Add a new definition. + $this->expectedDefinitions['banana'] = array( + 'label' => 'Banana', + 'color' => 'yellow', + ); + $this->discovery->setDefinition('banana', $this->expectedDefinitions['banana']); + + // Check that the new definition is not found. + $definition = $this->discovery->getDefinition('banana'); + $this->assertNull($definition, 'Newly added definition is not found.'); + + // Clear cached definitions, and check that the new definition is found. + $this->discovery->clearCachedDefinitions(); + $cached = $cache->get($this->cacheKey); + $this->assertIdentical($cached, FALSE, 'Cache is empty.'); + $definitions = $this->discovery->getDefinitions(); + $this->assertIdentical($definitions, $this->expectedDefinitions, 'Newly added definition is found.'); + } + +}