Commit 0fcb80b9 authored by alexpott's avatar alexpott

Issue #2271363 by Berdir: Remove CacheDecorator and ProcessDecorator.

parent f2b6aa7a
<?php
/**
* @file
* Contains \Drupal\Component\Plugin\Discovery\ProcessDecorator.
*/
namespace Drupal\Component\Plugin\Discovery;
/**
* Allows custom processing of the discovered definition.
*
* Example use cases include adding in default values for a definition, or
* providing a backwards compatibility layer for renamed definition properties.
*/
class ProcessDecorator implements DiscoveryInterface {
use DiscoveryTrait;
/**
* The Discovery object being decorated.
*
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
*/
protected $decorated;
/**
* The processor callback to run on each discovered definition.
*
* @var callable
*/
protected $processCallback;
/**
* Constructs a \Drupal\Component\Plugin\Discovery\ProcessDecorator object.
*
* @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
* The discovery object that is being decorated.
* @param callable $process_callback
* The processor callback to run on each discovered definition. The
* callback will be called with the following arguments:
* - array $definition: the discovered definition, that the callback
* should accept by reference and modify in place.
* - string $plugin_id: the corresponding plugin_id.
*/
public function __construct(DiscoveryInterface $decorated, $process_callback) {
$this->decorated = $decorated;
$this->processCallback = $process_callback;
}
/**
* Implements \Drupal\Component\Plugin\Discovery\DicoveryInterface::getDefinitions().
*/
public function getDefinitions() {
$definitions = $this->decorated->getDefinitions();
foreach ($definitions as $plugin_id => &$definition) {
call_user_func_array($this->processCallback, array(&$definition, $plugin_id));
}
// Allow process callbacks to unset definitions.
return array_filter($definitions);
}
/**
* Passes through all unknown calls onto the decorated object.
*/
public function __call($method, $args) {
return call_user_func_array(array($this->decorated, $method), $args);
}
}
......@@ -7,13 +7,12 @@
namespace Drupal\Component\Plugin;
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
/**
* Base class for plugin managers.
*/
abstract class PluginManagerBase implements PluginManagerInterface, CachedDiscoveryInterface {
abstract class PluginManagerBase implements PluginManagerInterface {
use DiscoveryTrait;
......@@ -52,15 +51,6 @@ public function getDefinitions() {
return $this->discovery->getDefinitions();
}
/**
* {@inheritdoc}
*/
public function clearCachedDefinitions() {
if ($this->discovery instanceof CachedDiscoveryInterface) {
$this->discovery->clearCachedDefinitions();
}
}
/**
* {@inheritdoc}
*/
......
......@@ -10,11 +10,8 @@
use Drupal\Core\Access\AccessManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Menu\LocalActionInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Component\Plugin\Discovery\ProcessDecorator;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
use Drupal\Core\Plugin\Factory\ContainerFactory;
......
<?php
/**
* @file
* Definition of Drupal\Core\Plugin\Discovery\AlterDiscoveryDecorator.
*/
namespace Drupal\Core\Plugin\Discovery;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
/**
* Enables altering of discovered plugin definitions.
*/
class AlterDecorator implements DiscoveryInterface {
use DiscoveryTrait;
/**
* The name of the alter hook that will be implemented by this discovery instance.
*
* @var string
*/
protected $hook;
/**
* The Discovery object being decorated.
*
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
*/
protected $decorated;
/**
* Constructs a Drupal\Core\Plugin\Discovery\AlterDecorator object.
*
* It uses the DiscoveryInterface object it should decorate.
*
* @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
* The object implementing DiscoveryInterface that is being decorated.
* @param string $hook
* The name of the alter hook that will be used by this discovery instance.
*/
public function __construct(DiscoveryInterface $decorated, $hook) {
$this->decorated = $decorated;
$this->hook = $hook;
}
/**
* Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions().
*/
public function getDefinitions() {
$definitions = $this->decorated->getDefinitions();
\Drupal::moduleHandler()->alter($this->hook, $definitions);
return $definitions;
}
/**
* Passes through all unknown calls onto the decorated object.
*/
public function __call($method, $args) {
return call_user_func_array(array($this->decorated, $method), $args);
}
}
<?php
/**
* @file
* Definition of Drupal\Core\Plugin\Discovery\CacheDecorator.
*/
namespace Drupal\Core\Plugin\Discovery;
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
use Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use Drupal\Core\Cache\Cache;
/**
* Enables static and persistent caching of discovered plugin definitions.
*/
class CacheDecorator implements CachedDiscoveryInterface {
use DiscoveryCachedTrait;
/**
* The cache key used to store the definition list.
*
* @var string
*/
protected $cacheKey;
/**
* The cache bin used to store the definition list.
*
* @var string
*/
protected $cacheBin;
/**
* The timestamp indicating when the definition list cache expires.
*
* @var int
*/
protected $cacheExpire;
/**
* The cache tags associated with the definition list.
*
* @var array
*/
protected $cacheTags;
/**
* The Discovery object being decorated.
*
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
*/
protected $decorated;
/**
* Constructs a Drupal\Core\Plugin\Discovery\CacheDecorator object.
*
* It uses the DiscoveryInterface object it should decorate.
*
* @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
* The object implementing DiscoveryInterface that is being decorated.
* @param string $cache_key
* The cache identifier used for storage of the definition list.
* @param string $cache_bin
* The cache bin used for storage and retrieval of the definition list.
* @param int $cache_expire
* A Unix timestamp indicating that the definition list will be considered
* invalid after this time.
* @param array $cache_tags
* The cache tags associated with the definition list.
*/
public function __construct(DiscoveryInterface $decorated, $cache_key, $cache_bin = 'default', $cache_expire = Cache::PERMANENT, array $cache_tags = array()) {
$this->decorated = $decorated;
$this->cacheKey = $cache_key;
$this->cacheBin = $cache_bin;
$this->cacheExpire = $cache_expire;
$this->cacheTags = $cache_tags;
}
/**
* Implements Drupal\Component\Plugin\Discovery\DicoveryInterface::getDefinitions().
*/
public function getDefinitions() {
// Optimize for fast access to definitions if they are already in memory.
if (isset($this->definitions)) {
return $this->definitions;
}
$definitions = $this->getCachedDefinitions();
if (!isset($definitions)) {
$definitions = $this->decorated->getDefinitions();
$this->setCachedDefinitions($definitions);
}
return $definitions;
}
/**
* Returns the cached plugin definitions of the decorated discovery class.
*
* @return mixed
* On success this will return an array of plugin definitions. On failure
* this should return NULL, indicating to other methods that this has not
* yet been defined. Success with no values should return as an empty array
* and would actually be returned by the getDefinitions() method.
*/
protected function getCachedDefinitions() {
if (!isset($this->definitions) && isset($this->cacheKey) && $cache = $this->cache($this->cacheBin)->get($this->cacheKey)) {
$this->definitions = $cache->data;
}
return $this->definitions;
}
/**
* Sets a cache of plugin definitions for the decorated discovery class.
*
* @param array $definitions
* List of definitions to store in cache.
*/
protected function setCachedDefinitions($definitions) {
if (isset($this->cacheKey)) {
$this->cache($this->cacheBin)->set($this->cacheKey, $definitions, $this->cacheExpire, $this->cacheTags);
}
$this->definitions = $definitions;
}
/**
* Implements \Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface::clearCachedDefinitions().
*/
public function clearCachedDefinitions() {
// If there are any cache tags, clear cache based on those.
if (!empty($this->cacheTags)) {
Cache::deleteTags($this->cacheTags);
}
// Otherwise, just delete the specified cache key.
else if (isset($this->cacheKey)) {
$this->cache($this->cacheBin)->delete($this->cacheKey);
}
$this->definitions = NULL;
}
/**
* Passes through all unknown calls onto the decorated object.
*/
public function __call($method, $args) {
return call_user_func_array(array($this->decorated, $method), $args);
}
/**
* Wraps the \Drupal::cache() method.
*/
protected function cache($bin) {
return \Drupal::cache($bin);
}
}
......@@ -107,7 +107,7 @@ public function registerDefinitions() {
}
/**
* Process definition callback for the ProcessDecorator.
* {@inheritdoc}
*/
public function processDefinition(&$definition, $plugin_id) {
// Make sure 'type' is set and either an array or FALSE.
......
<?php
/**
* @file
* Contains \Drupal\system\Tests\Plugin\AlterDecoratorTest.
*/
namespace Drupal\system\Tests\Plugin;
use Drupal\plugin_test\Plugin\AlterDecoratorTestPluginManager;
use Drupal\simpletest\WebTestBase;
/**
* Tests that the AlterDecorator fires and respects the alter hook.
*
* @group Plugin
*/
class AlterDecoratorTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('plugin_test');
/**
* Stores a plugin manager which uses the AlterDecorator.
*
* @var \Drupal\plugin_test\Plugin\AlterDecoratorTestPluginManager;
*/
protected $alterTestPluginManager;
public function setUp() {
parent::setUp();
// Setup a plugin manager which uses the alter decorator.
$this->alterTestPluginManager = new AlterDecoratorTestPluginManager();
}
/**
* Tests getDefinitions() and getDefinition() of Drupal\Core\Plugin\Discovery\AlterDecorator.
*/
public function testAlterDecorator() {
// Ensure that getDefinitions() fires and changes the actual plugin definitions.
$definitions = $this->alterTestPluginManager->getDefinitions();
foreach ($definitions as &$definition) {
$this->assertTrue($definition['altered']);
}
// Ensure that getDefinitions() fires and changes the actual plugin definition.
$definition = $this->alterTestPluginManager->getDefinition('user_login');
$this->assertTrue($definition['altered_single']);
}
}
<?php
/**
* @file
* Contains \Drupal\system\Tests\Plugin\CacheDecoratorLanguageTest.
*/
namespace Drupal\system\Tests\Plugin;
use Drupal\Core\Language\Language;
use Drupal\plugin_test\Plugin\CachedMockBlockManager;
use Drupal\simpletest\WebTestBase;
/**
* Tests that the CacheDecorator stores definitions by language appropriately.
*
* @group Plugin
*/
class CacheDecoratorLanguageTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('plugin_test', 'locale', 'language');
public function setUp() {
parent::setUp();
// Populate sample definitions.
$this->mockBlockExpectedDefinitions = array(
'user_login' => array(
'label' => 'User login',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock',
),
'menu:main_menu' => array(
'label' => 'Main menu',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
),
'menu:navigation' => array(
'label' => 'Navigation',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
),
'layout' => array(
'label' => 'Layout',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock',
),
'layout:foo' => array(
'label' => 'Layout Foo',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock',
),
);
// Create two languages: Spanish and German.
$this->languages = array('de', 'es');
foreach ($this->languages as $langcode) {
$language = new Language(array('id' => $langcode));
$languages[$langcode] = language_save($language);
// Set up translations for each mock block label.
$custom_strings = array();
foreach ($this->mockBlockExpectedDefinitions as $definition) {
$custom_strings[$definition['label']] = $langcode . ' ' . $definition['label'];
}
$this->addCustomTranslations($langcode, array('' => $custom_strings));
$this->rebuildContainer();
}
// Write test settings.php with new translations.
$this->writeCustomTranslations();
}
/**
* Check the translations of the cached plugin definitions.
*/
public function testCacheDecoratorLanguage() {
$languages = $this->languages;
$this->drupalGet('plugin_definition_test');
foreach ($this->mockBlockExpectedDefinitions as $definition) {
// Find our source text.
$this->assertText($definition['label']);
}
foreach ($languages as $langcode) {
$url = $langcode . '/plugin_definition_test';
// For each language visit the language specific version of the page again.
$this->drupalGet($url);
foreach ($this->mockBlockExpectedDefinitions as $definition) {
// Find our provided translations.
$label = $langcode . ' ' . $definition['label'];
$this->assertText($label);
}
}
// Manually check that the expected cache keys are present.
$languages[] = 'en';
foreach ($languages as $langcode) {
$cache = \Drupal::cache()->get('mock_block:' . $langcode);
$this->assertEqual($cache->cid, 'mock_block:' . $langcode, format_string('The !cache cache exists.', array('!cache' => 'mock_block:' . $langcode)));
$this->assertEqual($cache->expire, 1542646800, format_string('The cache expiration was properly set.'));
}
// Clear the plugin definitions.
$manager = new CachedMockBlockManager();
$manager->clearCachedDefinitions();
foreach ($languages as $langcode) {
$cache = \Drupal::cache()->get('mock_block:' . $langcode);
$this->assertFalse($cache, format_string('The !cache cache was properly cleared through the cache::deleteTags() method.', array('!cache' => 'mock_block:' . $langcode)));
}
// Change the translations for the german language and recheck strings.
$custom_strings = array();
foreach ($this->mockBlockExpectedDefinitions as $definition) {
$custom_strings[$definition['label']] = $definition['label'] . ' de';
}
$this->addCustomTranslations('de', array('' => $custom_strings));
$this->writeCustomTranslations();
$this->drupalGet('de/plugin_definition_test');
foreach ($this->mockBlockExpectedDefinitions as $definition) {
// Find our provided translations.
$label = $definition['label'] . ' de';
$this->assertText($label);
}
}
}
<?php
/**
* @file
* Contains \Drupal\system\Tests\Plugin\CacheDecoratorTest.
*/
namespace Drupal\system\Tests\Plugin;
use Drupal\Core\Cache\MemoryBackend;
use Drupal\system\Tests\Plugin\Discovery\DiscoveryTestBase;
use Drupal\Component\Plugin\Discovery\StaticDiscovery;
use Drupal\Core\Plugin\Discovery\CacheDecorator;
/**
* Tests the CacheDecorator.
*
* @group Plugin
*/
class CacheDecoratorTest extends DiscoveryTestBase {
/**
* The cache bin.
*
* @var string
*/
protected $cacheBin = 'test_cacheDecorator';
/**
* The cache key.
*
* @var string
*/
protected $cacheKey = 'test_cacheDecorator';
public function setUp() {
parent::setUp();
// Use a non-db cache backend, so that we can use DiscoveryTestBase (which
// extends UnitTestBase).
// @todo switch to injecting the MemoryBackend http://drupal.org/node/1903346
\Drupal::getContainer()->set("cache.$this->cacheBin", new MemoryBackend($this->cacheBin));
// 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 = \Drupal::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 = \Drupal::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', FALSE);
$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.');
}
}
plugin_test.definition:
path: '/plugin_definition_test'
defaults:
_content: '\Drupal\plugin_test\Controller\PluginTest::testDefinitions'
requirements:
_access: 'TRUE'
<?php
/**
* @file
* Contains \Drupal\plugin_test\Controller\PluginTest.
*/
namespace Drupal\plugin_test\Controller;
use Drupal\plugin_test\Plugin\CachedMockBlockManager;
/**
* Returns a test page containing plugin labels.
*/
class PluginTest {
/**
* Prints plugin labels for testing.
*
* @return array
* A renderable array of plugin labels.
*/
public function testDefinitions() {
$manager = new CachedMockBlockManager();
$output = array();
foreach ($manager->getDefinitions() as $plugin_id => $definition) {
$output[$plugin_id] = array(
'#markup' => $definition['label'],
);
}
return $output;
}
}
<?php
/**
* @file
* Definition of Drupal\plugin_test\Plugin\plugin_test\AlterDecoratorTestPluginManager.
*/
namespace Drupal\plugin_test\Plugin;