From 2bf5dc11da9cbe3c116be4ad3e7e83c784ff4251 Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole Date: Wed, 4 Mar 2015 15:06:39 +0000 Subject: [PATCH] Issue #2443409 by Berdir: Add a way to entity manager to get fresh entity and field definitions without invalidating all caches --- .../Discovery/CachedDiscoveryInterface.php | 13 ++++++ .../Entity/EntityDefinitionUpdateManager.php | 12 ++++- core/lib/Drupal/Core/Entity/EntityManager.php | 41 +++++++++++------ .../Core/Entity/EntityManagerInterface.php | 3 +- .../Core/Plugin/DefaultPluginManager.php | 46 +++++++++++++++++-- .../Entity/EntityDefinitionTestTrait.php | 16 ------- ...sEntitySchemaSubscriberIntegrationTest.php | 7 +++ .../Core/Plugin/DefaultPluginManagerTest.php | 24 ++++++++++ 8 files changed, 125 insertions(+), 37 deletions(-) diff --git a/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php b/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php index a9d95fc2c53c..5f9aa10766cf 100644 --- a/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php +++ b/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php @@ -21,4 +21,17 @@ interface CachedDiscoveryInterface extends DiscoveryInterface { */ public function clearCachedDefinitions(); + /** + * Disable the use of caches. + * + * Can be used to ensure that uncached plugin definitions are returned, + * without invalidating all cached information. + * + * This will also remove all local/static caches. + * + * @param bool $use_caches + * FALSE to not use any caches. + */ + public function useCaches($use_caches = FALSE); + } diff --git a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php index 1db9e97564fa..99aa59d55002 100644 --- a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php +++ b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php @@ -95,7 +95,13 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte * {@inheritdoc} */ public function applyUpdates() { - foreach ($this->getChangeList() as $entity_type_id => $change_list) { + $change_list = $this->getChangeList(); + if ($change_list) { + // getChangeList() only disables the cache and does not invalidate. + // In case there are changes, explictly invalidate caches. + $this->entityManager->clearCachedDefinitions(); + } + foreach ($change_list as $entity_type_id => $change_list) { // Process entity type definition changes before storage definitions ones // this is necessary when you change an entity type from non-revisionable // to revisionable and at the same time add revisionable fields to the @@ -153,7 +159,7 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte * - DEFINITION_DELETED */ protected function getChangeList() { - $this->entityManager->clearCachedDefinitions(); + $this->entityManager->useCaches(FALSE); $change_list = array(); foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { @@ -208,6 +214,8 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte // @todo Support deleting entity definitions when we support base field // purging. See https://www.drupal.org/node/2282119. + $this->entityManager->useCaches(TRUE); + return array_filter($change_list); } diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index 5ba64f2d3a87..019e0a5fc9db 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -372,13 +372,13 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa if (!isset($this->baseFieldDefinitions[$entity_type_id])) { // Not prepared, try to load from cache. $cid = 'entity_base_field_definitions:' . $entity_type_id . ':' . $this->languageManager->getCurrentLanguage()->getId(); - if ($cache = $this->cacheBackend->get($cid)) { + if ($cache = $this->cacheGet($cid)) { $this->baseFieldDefinitions[$entity_type_id] = $cache->data; } else { // Rebuild the definitions and put it into the cache. $this->baseFieldDefinitions[$entity_type_id] = $this->buildBaseFieldDefinitions($entity_type_id); - $this->cacheBackend->set($cid, $this->baseFieldDefinitions[$entity_type_id], Cache::PERMANENT, array('entity_types', 'entity_field_info')); + $this->cacheSet($cid, $this->baseFieldDefinitions[$entity_type_id], Cache::PERMANENT, array('entity_types', 'entity_field_info')); } } return $this->baseFieldDefinitions[$entity_type_id]; @@ -470,13 +470,13 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa $base_field_definitions = $this->getBaseFieldDefinitions($entity_type_id); // Not prepared, try to load from cache. $cid = 'entity_bundle_field_definitions:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->getId(); - if ($cache = $this->cacheBackend->get($cid)) { + if ($cache = $this->cacheGet($cid)) { $bundle_field_definitions = $cache->data; } else { // Rebuild the definitions and put it into the cache. $bundle_field_definitions = $this->buildBundleFieldDefinitions($entity_type_id, $bundle, $base_field_definitions); - $this->cacheBackend->set($cid, $bundle_field_definitions, Cache::PERMANENT, array('entity_types', 'entity_field_info')); + $this->cacheSet($cid, $bundle_field_definitions, Cache::PERMANENT, array('entity_types', 'entity_field_info')); } // Field definitions consist of the bundle specific overrides and the // base fields, merge them together. Use array_replace() to replace base @@ -578,13 +578,13 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa } // Not prepared, try to load from cache. $cid = 'entity_field_storage_definitions:' . $entity_type_id . ':' . $this->languageManager->getCurrentLanguage()->getId(); - if ($cache = $this->cacheBackend->get($cid)) { + if ($cache = $this->cacheGet($cid)) { $field_storage_definitions = $cache->data; } else { // Rebuild the definitions and put it into the cache. $field_storage_definitions = $this->buildFieldStorageDefinitions($entity_type_id); - $this->cacheBackend->set($cid, $field_storage_definitions, Cache::PERMANENT, array('entity_types', 'entity_field_info')); + $this->cacheSet($cid, $field_storage_definitions, Cache::PERMANENT, array('entity_types', 'entity_field_info')); } $this->fieldStorageDefinitions[$entity_type_id] += $field_storage_definitions; } @@ -598,7 +598,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa if (!$this->fieldMap) { // Not prepared, try to load from cache. $cid = 'entity_field_map'; - if ($cache = $this->cacheBackend->get($cid)) { + if ($cache = $this->cacheGet($cid)) { $this->fieldMap = $cache->data; } else { @@ -614,7 +614,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa } } - $this->cacheBackend->set($cid, $this->fieldMap, Cache::PERMANENT, array('entity_types', 'entity_field_info')); + $this->cacheSet($cid, $this->fieldMap, Cache::PERMANENT, array('entity_types', 'entity_field_info')); } } return $this->fieldMap; @@ -717,7 +717,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa public function getAllBundleInfo() { if (empty($this->bundleInfo)) { $langcode = $this->languageManager->getCurrentLanguage()->getId(); - if ($cache = $this->cacheBackend->get("entity_bundle_info:$langcode")) { + if ($cache = $this->cacheGet("entity_bundle_info:$langcode")) { $this->bundleInfo = $cache->data; } else { @@ -738,7 +738,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa } } $this->moduleHandler->alter('entity_bundle_info', $this->bundleInfo); - $this->cacheBackend->set("entity_bundle_info:$langcode", $this->bundleInfo, Cache::PERMANENT, array('entity_types', 'entity_bundles')); + $this->cacheSet("entity_bundle_info:$langcode", $this->bundleInfo, Cache::PERMANENT, array('entity_types', 'entity_bundles')); } } @@ -758,7 +758,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa // hook_entity_extra_field_info_alter() might contain t() calls, we cache // per language. $cache_id = 'entity_bundle_extra_fields:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->getId(); - $cached = $this->cacheBackend->get($cache_id); + $cached = $this->cacheGet($cache_id); if ($cached) { $this->extraFields[$entity_type_id][$bundle] = $cached->data; return $this->extraFields[$entity_type_id][$bundle]; @@ -774,7 +774,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa // Store in the 'static' and persistent caches. $this->extraFields[$entity_type_id][$bundle] = $info; - $this->cacheBackend->set($cache_id, $info, Cache::PERMANENT, array( + $this->cacheSet($cache_id, $info, Cache::PERMANENT, array( 'entity_field_info', )); @@ -886,7 +886,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa $key = 'entity_' . $display_type . '_info'; $entity_type_id = 'entity_' . $display_type; $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE)->getId(); - if ($cache = $this->cacheBackend->get("$key:$langcode")) { + if ($cache = $this->cacheGet("$key:$langcode")) { $this->displayModeInfo[$display_type] = $cache->data; } else { @@ -896,7 +896,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa $this->displayModeInfo[$display_type][$display_mode_entity_type][$display_mode_name] = $display_mode->toArray(); } $this->moduleHandler->alter($key, $this->displayModeInfo[$display_type]); - $this->cacheBackend->set("$key:$langcode", $this->displayModeInfo[$display_type], CacheBackendInterface::CACHE_PERMANENT, array('entity_types', 'entity_field_info')); + $this->cacheSet("$key:$langcode", $this->displayModeInfo[$display_type], CacheBackendInterface::CACHE_PERMANENT, array('entity_types', 'entity_field_info')); } } @@ -1206,6 +1206,19 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa return $this->installedDefinitions->get($entity_type_id . '.entity_type'); } + /** + * {@inheritdoc} + */ + public function useCaches($use_caches = FALSE) { + parent::useCaches($use_caches); + if (!$use_caches) { + $this->handlers = []; + $this->fieldDefinitions = []; + $this->baseFieldDefinitions = []; + $this->fieldStorageDefinitions = []; + } + } + /** * Stores the entity type definition in the application state. * diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php index d1609900a0c8..b3bda8b24d04 100644 --- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php @@ -7,13 +7,14 @@ namespace Drupal\Core\Entity; +use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface; use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\Field\FieldStorageDefinitionListenerInterface; /** * Provides an interface for entity type managers. */ -interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface, EntityBundleListenerInterface, FieldStorageDefinitionListenerInterface { +interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface, EntityBundleListenerInterface, FieldStorageDefinitionListenerInterface, CachedDiscoveryInterface { /** * Builds a list of entity type labels suitable for a Form API options list. diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php index 5c37aa99d7bd..297f9715f6f4 100644 --- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php +++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php @@ -80,6 +80,13 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInt */ protected $defaults = array(); + /** + * Flag whether persistent caches should be used. + * + * @var bool + */ + protected $useCaches = TRUE; + /** * Creates the discovery object. * @@ -180,7 +187,7 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInt * and would actually be returned by the getDefinitions() method. */ protected function getCachedDefinitions() { - if (!isset($this->definitions) && $this->cacheBackend && $cache = $this->cacheBackend->get($this->cacheKey)) { + if (!isset($this->definitions) && $cache = $this->cacheGet($this->cacheKey)) { $this->definitions = $cache->data; } return $this->definitions; @@ -193,12 +200,43 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInt * List of definitions to store in cache. */ protected function setCachedDefinitions($definitions) { - if ($this->cacheBackend) { - $this->cacheBackend->set($this->cacheKey, $definitions, Cache::PERMANENT, $this->cacheTags); - } + $this->cacheSet($this->cacheKey, $definitions, Cache::PERMANENT, $this->cacheTags); $this->definitions = $definitions; } + /** + * {@inheritdoc} + */ + public function useCaches($use_caches = FALSE) { + $this->useCaches = $use_caches; + if (!$use_caches) { + $this->definitions = NULL; + } + } + + /** + * Fetches from the cache backend, respecting the use caches flag. + * + * @see \Drupal\Core\Cache\CacheBackendInterface::get() + */ + protected function cacheGet($cid) { + if ($this->useCaches && $this->cacheBackend) { + return $this->cacheBackend->get($cid); + } + return FALSE; + } + + /** + * Stores data in the persistent cache, respecting the use caches flag. + * + * @see \Drupal\Core\Cache\CacheBackendInterface::set() + */ + protected function cacheSet($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) { + if ($this->cacheBackend && $this->useCaches) { + $this->cacheBackend->set($cid, $data, $expire, $tags); + } + } + /** * Performs extra processing on plugin definitions. diff --git a/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php index 353dd4d8ca39..e5c8f4b3103a 100644 --- a/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php +++ b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php @@ -44,7 +44,6 @@ trait EntityDefinitionTestTrait { $entity_type->set('entity_keys', $keys); $this->state->set('entity_test_update.entity_type', $entity_type); - $this->entityManager->clearCachedDefinitions(); } /** @@ -58,7 +57,6 @@ trait EntityDefinitionTestTrait { $entity_type->set('entity_keys', $keys); $this->state->set('entity_test_update.entity_type', $entity_type); - $this->entityManager->clearCachedDefinitions(); } /** @@ -75,7 +73,6 @@ trait EntityDefinitionTestTrait { } $this->state->set('entity_test_update.entity_type', $entity_type); - $this->entityManager->clearCachedDefinitions(); } /** @@ -92,7 +89,6 @@ trait EntityDefinitionTestTrait { } $this->state->set('entity_test_update.entity_type', $entity_type); - $this->entityManager->clearCachedDefinitions(); } /** @@ -106,7 +102,6 @@ trait EntityDefinitionTestTrait { ->setName('new_base_field') ->setLabel(t('A new base field')); $this->state->set('entity_test_update.additional_base_field_definitions', $definitions); - $this->entityManager->clearCachedDefinitions(); } /** @@ -121,7 +116,6 @@ trait EntityDefinitionTestTrait { ->setLabel(t('A new revisionable base field')) ->setRevisionable(TRUE); $this->state->set('entity_test_update.additional_base_field_definitions', $definitions); - $this->entityManager->clearCachedDefinitions(); } /** @@ -136,7 +130,6 @@ trait EntityDefinitionTestTrait { */ protected function removeBaseField() { $this->state->delete('entity_test_update.additional_base_field_definitions'); - $this->entityManager->clearCachedDefinitions(); } /** @@ -144,7 +137,6 @@ trait EntityDefinitionTestTrait { */ protected function addBaseFieldIndex() { $this->state->set('entity_test_update.additional_field_index.entity_test_update.new_base_field', TRUE); - $this->entityManager->clearCachedDefinitions(); } /** @@ -152,7 +144,6 @@ trait EntityDefinitionTestTrait { */ protected function removeBaseFieldIndex() { $this->state->delete('entity_test_update.additional_field_index.entity_test_update.new_base_field'); - $this->entityManager->clearCachedDefinitions(); } /** @@ -168,7 +159,6 @@ trait EntityDefinitionTestTrait { ->setTargetEntityTypeId('entity_test_update'); $this->state->set('entity_test_update.additional_field_storage_definitions', $definitions); $this->state->set('entity_test_update.additional_bundle_field_definitions.test_bundle', $definitions); - $this->entityManager->clearCachedDefinitions(); } /** @@ -184,7 +174,6 @@ trait EntityDefinitionTestTrait { protected function removeBundleField() { $this->state->delete('entity_test_update.additional_field_storage_definitions'); $this->state->delete('entity_test_update.additional_bundle_field_definitions.test_bundle'); - $this->entityManager->clearCachedDefinitions(); } /** @@ -215,7 +204,6 @@ trait EntityDefinitionTestTrait { $entity_type->set('base_table', 'entity_test_update_new'); $this->state->set('entity_test_update.entity_type', $entity_type); - $this->entityManager->clearCachedDefinitions(); } /** @@ -227,7 +215,6 @@ trait EntityDefinitionTestTrait { $entity_type->set('data_table', 'entity_test_update_data_new'); $this->state->set('entity_test_update.entity_type', $entity_type); - $this->entityManager->clearCachedDefinitions(); } /** @@ -239,7 +226,6 @@ trait EntityDefinitionTestTrait { $entity_type->set('revision_table', 'entity_test_update_revision_new'); $this->state->set('entity_test_update.entity_type', $entity_type); - $this->entityManager->clearCachedDefinitions(); } /** @@ -251,7 +237,6 @@ trait EntityDefinitionTestTrait { $entity_type->set('revision_data_table', 'entity_test_update_revision_data_new'); $this->state->set('entity_test_update.entity_type', $entity_type); - $this->entityManager->clearCachedDefinitions(); } /** @@ -259,7 +244,6 @@ trait EntityDefinitionTestTrait { */ protected function deleteEntityType() { $this->state->set('entity_test_update.entity_type', 'null'); - $this->entityManager->clearCachedDefinitions(); } } diff --git a/core/modules/views/src/Tests/EventSubscriber/ViewsEntitySchemaSubscriberIntegrationTest.php b/core/modules/views/src/Tests/EventSubscriber/ViewsEntitySchemaSubscriberIntegrationTest.php index e69078fd57ba..dba5b2a91285 100644 --- a/core/modules/views/src/Tests/EventSubscriber/ViewsEntitySchemaSubscriberIntegrationTest.php +++ b/core/modules/views/src/Tests/EventSubscriber/ViewsEntitySchemaSubscriberIntegrationTest.php @@ -208,6 +208,9 @@ class ViewsEntitySchemaSubscriberIntegrationTest extends ViewUnitTestBase { */ public function testRevisionDataTableRename() { $this->updateEntityTypeToRevisionable(); + // Multiple changes, so we have to invalidate the caches, otherwise + // the second update will revert the first. + $this->entityManager->clearCachedDefinitions(); $this->updateEntityTypeToTranslatable(); $this->entityDefinitionUpdateManager->applyUpdates(); @@ -414,6 +417,10 @@ class ViewsEntitySchemaSubscriberIntegrationTest extends ViewUnitTestBase { public function testVariousTableUpdatesForRevisionView() { // base + revision <-> base + translation + revision $this->updateEntityTypeToRevisionable(); + // Multiple changes, so we have to invalidate the caches, otherwise + // the second update will revert the first. + $this->entityManager->clearCachedDefinitions(); + list($view, $display) = $this->getUpdatedViewAndDisplay(TRUE); $this->assertEqual('entity_test_update_revision', $view->get('base_table')); diff --git a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php index 9cd9375b3859..350359714911 100644 --- a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php @@ -185,6 +185,30 @@ class DefaultPluginManagerTest extends UnitTestCase { $this->assertEquals($this->expectedDefinitions, $plugin_manager->getDefinitions()); } + /** + * Tests the plugin manager with caching disabled. + */ + public function testDefaultPluginManagerNoCache() { + $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, NULL, NULL, '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface'); + + $cid = $this->randomMachineName(); + $cache_backend = $this->getMockBuilder('Drupal\Core\Cache\MemoryBackend') + ->disableOriginalConstructor() + ->getMock(); + $cache_backend + ->expects($this->never()) + ->method('get'); + $cache_backend + ->expects($this->never()) + ->method('set'); + $plugin_manager->setCacheBackend($cache_backend, $cid); + + $plugin_manager->useCaches(FALSE); + + $this->assertEquals($this->expectedDefinitions, $plugin_manager->getDefinitions()); + $this->assertEquals($this->expectedDefinitions['banana'], $plugin_manager->getDefinition('banana')); + } + /** * Tests the plugin manager cache clear with tags. */