diff --git a/core/core.services.yml b/core/core.services.yml index 589c5f6..1e4f5d3 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -152,6 +152,12 @@ services: - { name: cache.bin, default_backend: cache.backend.chainedfast } factory: cache_factory:get arguments: [config] + cache.config_entity_storage: + class: Drupal\Core\Cache\CacheBackendInterface + tags: + - { name: cache.bin, default_backend: cache.backend.chainedfast } + factory: cache_factory:get + arguments: [config_entity_storage] cache.default: class: Drupal\Core\Cache\CacheBackendInterface tags: diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php index 4802173..b11693b 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php @@ -7,7 +7,9 @@ namespace Drupal\Core\Config\Entity; +use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigImporterException; use Drupal\Core\Entity\EntityInterface; @@ -82,6 +84,13 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora protected $languageManager; /** + * The config_entity_query cache backend. + * + * @var \Drupal\Core\Cache\CacheBackendInterface + */ + protected $cacheBackend; + + /** * Static cache of entities, keyed first by entity ID, then by an extra key. * * The additional cache key is to maintain separate caches for different @@ -111,12 +120,13 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. */ - public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) { + public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, CacheBackendInterface $cache_backend) { parent::__construct($entity_type); $this->configFactory = $config_factory; $this->uuidService = $uuid_service; $this->languageManager = $language_manager; + $this->cacheBackend = $cache_backend; } /** @@ -127,7 +137,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $entity_type, $container->get('config.factory'), $container->get('uuid'), - $container->get('language_manager') + $container->get('language_manager'), + $container->get('cache.config_entity_storage') ); } @@ -455,4 +466,21 @@ public function loadMultipleOverrideFree(array $ids = NULL) { return $entities; } + /** + * {@inheritdoc} + */ + public function cachedLoadByProperties(array $values = array()) { + $cid = $this->entityTypeId . Crypt::hashBase64(serialize($values)); + if ($cached = $this->cacheBackend->get($cid)) { + $result = $cached->data; + } + else { + $result = $this->loadByProperties($values); + // Add the config entity list cache tag, so that this is invalidated + // when config entities change. + $this->cacheBackend->set($cid, $result, CacheBackendInterface::CACHE_PERMANENT, $this->entityType->getListCacheTags()); + } + return $result; + } + } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php index 8bb9e43..9e4bac4 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php @@ -88,4 +88,33 @@ public function loadOverrideFree($id); */ public function loadMultipleOverrideFree(array $ids = NULL); + /** + * Load entities by their property values from cache, if available. + * + * If there is no cache entry available the method falls back to + * self::loadByProperties(). + * + * Querying configuration entities is expensive. If config entities need to be + * queried by property during regular run-time (for example, on every non + * cached page request) then this method is preferred over + * self::loadByProperties(), especially if the $values array is not highly + * dynamic. If $values is highly dynamic consider using config entity lookup + * keys. + * + * Over-use of this method is discouraged since, by default, it stores entries + * in APC. + * + * @param array $values + * An associative array where the keys are the property names and the + * values are the values those properties must have. + * + * @return \Drupal\Core\Entity\EntityInterface[] + * An array of entity objects indexed by their ids. + * + * @see \Drupal\Core\Config\Entity\ConfigEntityStorageInterface::loadByProperties() + * @see \Drupal\Core\Config\Entity\ConfigEntityType::$lookup_keys + * @see \Drupal\block\BlockRepository::getVisibleBlocksPerRegion() + */ + public function cachedLoadByProperties(array $values = array()); + } diff --git a/core/lib/Drupal/Core/Field/BaseFieldOverrideStorage.php b/core/lib/Drupal/Core/Field/BaseFieldOverrideStorage.php index 0b559ba..ebdba47 100644 --- a/core/lib/Drupal/Core/Field/BaseFieldOverrideStorage.php +++ b/core/lib/Drupal/Core/Field/BaseFieldOverrideStorage.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Field; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Language\LanguageManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -32,8 +33,8 @@ class BaseFieldOverrideStorage extends FieldConfigStorageBase { * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager * The field type plugin manager. */ - public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, FieldTypePluginManagerInterface $field_type_manager) { - parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager); + public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, CacheBackendInterface $cache_backend, FieldTypePluginManagerInterface $field_type_manager) { + parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager, $cache_backend); $this->fieldTypeManager = $field_type_manager; } @@ -46,6 +47,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('config.factory'), $container->get('uuid'), $container->get('language_manager'), + $container->get('cache.config_entity_storage'), $container->get('plugin.manager.field.field_type') ); } diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml index 95117f5..df4d0d7 100644 --- a/core/modules/block/block.services.yml +++ b/core/modules/block/block.services.yml @@ -24,4 +24,4 @@ services: - { name: 'event_subscriber' } block.repository: class: Drupal\block\BlockRepository - arguments: ['@entity.manager', '@theme.manager', '@context.handler', '@cache.bootstrap'] + arguments: ['@entity.manager', '@theme.manager', '@context.handler'] diff --git a/core/modules/block/src/BlockRepository.php b/core/modules/block/src/BlockRepository.php index e6cda40..bea8478 100644 --- a/core/modules/block/src/BlockRepository.php +++ b/core/modules/block/src/BlockRepository.php @@ -7,11 +7,9 @@ namespace Drupal\block; -use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Plugin\Context\ContextHandlerInterface; use Drupal\Core\Theme\ThemeManagerInterface; -use Drupal\Core\Cache\CacheBackendInterface; /** * Provides a repository for Block config entities. @@ -21,7 +19,7 @@ class BlockRepository implements BlockRepositoryInterface { /** * The block storage. * - * @var \Drupal\Core\Entity\EntityStorageInterface + * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface */ protected $blockStorage; @@ -33,13 +31,6 @@ class BlockRepository implements BlockRepositoryInterface { protected $themeManager; /** - * The cache backend. - * - * @var \Drupal\Core\Cache\CacheBackendInterface - */ - protected $cacheBackend; - - /** * Constructs a new BlockRepository. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager @@ -48,13 +39,11 @@ class BlockRepository implements BlockRepositoryInterface { * The theme manager. * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler * The plugin context handler. - * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend */ - public function __construct(EntityManagerInterface $entity_manager, ThemeManagerInterface $theme_manager, ContextHandlerInterface $context_handler, CacheBackendInterface $cache_backend) { + public function __construct(EntityManagerInterface $entity_manager, ThemeManagerInterface $theme_manager, ContextHandlerInterface $context_handler) { $this->blockStorage = $entity_manager->getStorage('block'); $this->themeManager = $theme_manager; $this->contextHandler = $context_handler; - $this->cacheBackend = $cache_backend; } /** @@ -66,20 +55,7 @@ public function getVisibleBlocksPerRegion(array $contexts) { $empty = array_fill_keys($active_theme->getRegions(), array()); $full = array(); - - // Cache the block list per-theme using the chained fast backend. This - // avoids the overhead of a config entity query on each request. - $cid = 'block_list:' . $active_theme->getName(); - if ($cached = $this->cacheBackend->get($cid)) { - $blocks = $cached->data; - } - else { - $blocks = $this->blockStorage->loadByProperties(['theme' => $active_theme->getName()]); - // Add the block config entity list cache tag, so that this is invalidated - // when blocks change. - $this->cacheBackend->set($cid, $blocks, Cache::PERMANENT, ['config:block_list']); - } - foreach ($blocks as $block_id => $block) { + foreach ($this->blockStorage->cachedLoadByProperties(array('theme' => $active_theme->getName())) as $block_id => $block) { /** @var \Drupal\block\BlockInterface $block */ // Set the contexts on the block before checking access. if ($block->setContexts($contexts)->access('view')) { diff --git a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php index 5245396..586d473 100644 --- a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php +++ b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php @@ -9,8 +9,6 @@ use Drupal\block\BlockRepository; use Drupal\Core\Block\BlockPluginInterface; -use Drupal\Core\Plugin\Context\Context; -use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\ContextAwarePluginInterface; use Drupal\Tests\UnitTestCase; @@ -26,7 +24,7 @@ class BlockRepositoryTest extends UnitTestCase { protected $blockRepository; /** - * @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $blockStorage; @@ -41,13 +39,6 @@ class BlockRepositoryTest extends UnitTestCase { protected $contextHandler; /** - * The cache backend. - * - * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $cacheBackend; - - /** * {@inheritdoc} */ protected function setUp() { @@ -73,17 +64,13 @@ protected function setUp() { ->will($this->returnValue($active_theme)); $this->contextHandler = $this->getMock('Drupal\Core\Plugin\Context\ContextHandlerInterface'); - $this->blockStorage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $this->blockStorage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); $entity_manager->expects($this->any()) ->method('getStorage') ->willReturn($this->blockStorage); - $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); - $this->blockRepository = $this->getMockBuilder('Drupal\block\BlockRepository') - ->setConstructorArgs([$entity_manager, $theme_manager, $this->contextHandler, $this->cacheBackend]) - ->setMethods(['getRegionNames']) - ->getMock(); + $this->blockRepository = new BlockRepository($entity_manager, $theme_manager, $this->contextHandler); } /** @@ -110,11 +97,16 @@ public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expec } $this->blockStorage->expects($this->once()) - ->method('loadByProperties') + ->method('cachedLoadByProperties') ->with(['theme' => $this->theme]) ->willReturn($blocks); - - $result = $this->getAllVisibleBlocks([]); + $result = []; + foreach ($this->blockRepository->getVisibleBlocksPerRegion([]) as $region => $resulting_blocks) { + $result[$region] = []; + foreach ($resulting_blocks as $plugin_id => $block) { + $result[$region][] = $plugin_id; + } + } $this->assertSame($result, $expected_blocks); } @@ -153,13 +145,9 @@ public function providerBlocksConfig() { * @covers ::getVisibleBlocksPerRegion */ public function testGetVisibleBlocksPerRegionWithContext() { - $contexts = []; - $contexts[] = new Context(new ContextDefinition('entity:user', 'Current user')); - $block = $this->getMock('Drupal\block\BlockInterface'); $block->expects($this->once()) ->method('setContexts') - ->with($contexts) ->willReturnSelf(); $block->expects($this->once()) ->method('access') @@ -169,47 +157,18 @@ public function testGetVisibleBlocksPerRegionWithContext() { ->willReturn('top'); $blocks['block_id'] = $block; + $contexts = []; $this->blockStorage->expects($this->once()) - ->method('loadByProperties') + ->method('cachedLoadByProperties') ->with(['theme' => $this->theme]) ->willReturn($blocks); - - $result = $this->getAllVisibleBlocks($contexts); - $expected = [ - 'top' => [ - 'block_id', - ], - 'center' => [], - 'bottom' => [], - ]; - $this->assertSame($expected, $result); - } - - /** - * @covers ::getVisibleBlocksPerRegion - */ - public function testGetVisibleBlocksPerRegionFromCache() { - $this->blockStorage->expects($this->never()) - ->method('loadByProperties'); - - $block = $this->getMock('Drupal\block\BlockInterface'); - $block->expects($this->once()) - ->method('setContexts') - ->willReturnSelf(); - $block->expects($this->once()) - ->method('access') - ->willReturn(TRUE); - $block->expects($this->once()) - ->method('getRegion') - ->willReturn('top'); - $blocks['block_id'] = $block; - - $this->cacheBackend->expects($this->once()) - ->method('get') - ->with('block_list:' . $this->theme) - ->willReturn((object) ['data' => $blocks]); - - $result = $this->getAllVisibleBlocks([]); + $result = []; + foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $resulting_blocks) { + $result[$region] = []; + foreach ($resulting_blocks as $plugin_id => $block) { + $result[$region][] = $plugin_id; + } + } $expected = [ 'top' => [ 'block_id', @@ -220,20 +179,6 @@ public function testGetVisibleBlocksPerRegionFromCache() { $this->assertSame($expected, $result); } - /** - * Calls getVisibleBlocksPerRegion() for a set of contexts. - */ - protected function getAllVisibleBlocks(array $contexts) { - $result = []; - foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $resulting_blocks) { - $result[$region] = []; - foreach ($resulting_blocks as $plugin_id => $block) { - $result[$region][] = $plugin_id; - } - } - return $result; - } - } interface TestContextAwareBlockInterface extends BlockPluginInterface, ContextAwarePluginInterface { diff --git a/core/modules/field/src/FieldConfigStorage.php b/core/modules/field/src/FieldConfigStorage.php index bb6a257..62859d3 100644 --- a/core/modules/field/src/FieldConfigStorage.php +++ b/core/modules/field/src/FieldConfigStorage.php @@ -7,6 +7,7 @@ namespace Drupal\field; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Config\Config; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; @@ -62,8 +63,8 @@ class FieldConfigStorage extends FieldConfigStorageBase { * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager * The field type plugin manager. */ - public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, EntityManagerInterface $entity_manager, StateInterface $state, FieldTypePluginManagerInterface $field_type_manager) { - parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager); + public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, CacheBackendInterface $cache_backend, EntityManagerInterface $entity_manager, StateInterface $state, FieldTypePluginManagerInterface $field_type_manager) { + parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager, $cache_backend); $this->entityManager = $entity_manager; $this->state = $state; $this->fieldTypeManager = $field_type_manager; @@ -78,6 +79,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('config.factory'), $container->get('uuid'), $container->get('language_manager'), + $container->get('cache.config_entity_storage'), $container->get('entity.manager'), $container->get('state'), $container->get('plugin.manager.field.field_type') diff --git a/core/modules/field/src/FieldStorageConfigStorage.php b/core/modules/field/src/FieldStorageConfigStorage.php index 4e7a280..f79097b 100644 --- a/core/modules/field/src/FieldStorageConfigStorage.php +++ b/core/modules/field/src/FieldStorageConfigStorage.php @@ -8,6 +8,7 @@ namespace Drupal\field; use Drupal\Component\Uuid\UuidInterface; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Config\Entity\ConfigEntityStorage; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; @@ -72,8 +73,8 @@ class FieldStorageConfigStorage extends ConfigEntityStorage { * @param \Drupal\Component\Plugin\PluginManagerInterface\FieldTypePluginManagerInterface * The field type plugin manager. */ - public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, StateInterface $state, FieldTypePluginManagerInterface $field_type_manager) { - parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager); + public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, CacheBackendInterface $cache_backend, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, StateInterface $state, FieldTypePluginManagerInterface $field_type_manager) { + parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager, $cache_backend); $this->entityManager = $entity_manager; $this->moduleHandler = $module_handler; $this->state = $state; @@ -89,6 +90,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('config.factory'), $container->get('uuid'), $container->get('language_manager'), + $container->get('cache.config_entity_storage'), $container->get('entity.manager'), $container->get('module_handler'), $container->get('state'), diff --git a/core/modules/migrate_drupal/src/MigrationStorage.php b/core/modules/migrate_drupal/src/MigrationStorage.php index eb0349b..c1d3815 100644 --- a/core/modules/migrate_drupal/src/MigrationStorage.php +++ b/core/modules/migrate_drupal/src/MigrationStorage.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Uuid\UuidInterface; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageException; @@ -51,8 +52,8 @@ class MigrationStorage extends BaseMigrationStorage { * @param \Drupal\migrate_drupal\Plugin\MigratePluginManager * The cckfield plugin manager. */ - public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, MigratePluginManager $cck_plugin_manager) { - parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager); + public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, CacheBackendInterface $cache_backend, MigratePluginManager $cck_plugin_manager) { + parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager, $cache_backend); $this->cckPluginManager = $cck_plugin_manager; } @@ -65,6 +66,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('config.factory'), $container->get('uuid'), $container->get('language_manager'), + $container->get('cache.config_entity_storage'), $container->get('plugin.manager.migrate.cckfield') ); } diff --git a/core/modules/shortcut/src/ShortcutSetStorage.php b/core/modules/shortcut/src/ShortcutSetStorage.php index f575856..00693ea 100644 --- a/core/modules/shortcut/src/ShortcutSetStorage.php +++ b/core/modules/shortcut/src/ShortcutSetStorage.php @@ -8,6 +8,7 @@ namespace Drupal\shortcut; use Drupal\Component\Uuid\UuidInterface; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\Entity\ConfigEntityStorage; use Drupal\Core\Entity\EntityTypeInterface; @@ -44,8 +45,8 @@ class ShortcutSetStorage extends ConfigEntityStorage implements ShortcutSetStora * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. */ - public function __construct(EntityTypeInterface $entity_info, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager) { - parent::__construct($entity_info, $config_factory, $uuid_service, $language_manager); + public function __construct(EntityTypeInterface $entity_info, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, CacheBackendInterface $cache_backend) { + parent::__construct($entity_info, $config_factory, $uuid_service, $language_manager, $cache_backend); $this->moduleHandler = $module_handler; } @@ -59,7 +60,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('config.factory'), $container->get('uuid'), $container->get('module_handler'), - $container->get('language_manager') + $container->get('language_manager'), + $container->get('cache.config_entity_storage') ); } diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index c4413eb..c11a9a5 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\Core\Config\Entity { +use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Language\Language; @@ -104,6 +105,13 @@ class ConfigEntityStorageTest extends UnitTestCase { protected $configManager; /** + * The config_entity_query cache backend. + * + * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheBackend; + + /** * {@inheritdoc} * * @covers ::__construct @@ -146,9 +154,11 @@ protected function setUp() { $this->entityQuery = $this->getMock('Drupal\Core\Entity\Query\QueryInterface'); + $this->cacheBackend = new MemoryBackend('config_entity_storage'); + $this->entityStorage = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityStorage') - ->setConstructorArgs(array($this->entityType, $this->configFactory, $this->uuidService, $this->languageManager)) - ->setMethods(array('getQuery')) + ->setConstructorArgs(array($this->entityType, $this->configFactory, $this->uuidService, $this->languageManager, $this->cacheBackend)) + ->setMethods(array('getQuery', 'loadByProperties')) ->getMock(); $this->entityStorage->expects($this->any()) ->method('getQuery') @@ -856,6 +866,34 @@ public function testDeleteNothing() { } /** + * @covers ::cachedLoadByProperties + */ + public function testCachedLoadByProperties() { + + $values = ['test' => 'blah']; + $result = [ + 'foo' => $this->getMockEntity(['id' => 'foo', 'test' => 'blah']) + ]; + + // Ensure that we only call the storage's loadByProperties() method twice. + $this->entityStorage->expects($this->exactly(2)) + ->method('loadByProperties') + ->with($values) + ->willReturn($result); + + // This to cachedLoadByProperties() will fall through to loadByProperties(). + $this->assertEquals($result, $this->entityStorage->cachedLoadByProperties($values)); + // This call to cachedLoadByProperties() should use the cache. + $this->assertEquals($result, $this->entityStorage->cachedLoadByProperties($values)); + + $this->cacheBackend->invalidateTags($this->entityType->getListCacheTags()); + // This to cachedLoadByProperties() will fall through to loadByProperties(). + $this->assertEquals($result, $this->entityStorage->cachedLoadByProperties($values)); + // This call to cachedLoadByProperties() should use the cache. + $this->assertEquals($result, $this->entityStorage->cachedLoadByProperties($values)); + } + + /** * Creates an entity with specific methods mocked. * * @param array $values