diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 9d6b8d6..8306196 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -21,6 +21,13 @@ protected $bundleKey = FALSE; /** + * Static cache of entity revisions, keyed by entity revision ID. + * + * @var array + */ + protected $entityRevisions = []; + + /** * The entity manager. * * @var \Drupal\Core\Entity\EntityManagerInterface @@ -35,6 +42,16 @@ protected $cacheBackend; /** + * Whether the static cache should be ignored when loading an entity. + * + * This property will be set by ::loadRevisionUnchanged() in order for + * ::loadRevision() to load the entity without using the static entity cache. + * + * @var bool + */ + protected $ignoreStaticCache; + + /** * Constructs a ContentEntityStorageBase object. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type @@ -215,13 +232,81 @@ public function finalizePurge(FieldStorageDefinitionInterface $storage_definitio /** * {@inheritdoc} */ + public function load($id) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = parent::load($id); + + // If there is already an entry in the static entity revisions cache we + // return a reference to the already loaded entity object. + // + // @todo find a better way to determine the default revision id without + // having to load the entity object from the storage, then check if there + // is already an entry in the static entity revisions cache for it and if + // not load the entity from the storage. + if ($entity && $this->entityType->isStaticallyCacheable() && $this->entityType->isRevisionable()) { + // If the default revision is not present in the static entity revision + // cache we add it and return the entity. + if (!$this->getFromStaticEntityRevisionCache($entity->getRevisionId())) { + $this->setStaticEntityRevisionCache($entity->getRevisionId(), $entity); + } + return $this->getFromStaticEntityRevisionCache($entity->getRevisionId()); + } + + return $entity; + } + + /** + * {@inheritdoc} + */ public function loadRevision($revision_id) { - $revision = $this->doLoadRevisionFieldItems($revision_id); + $revision = NULL; - if ($revision) { + if (!isset($this->ignoreStaticCache)) { + if ($this->entityType->isStaticallyCacheable()) { + $revision = $this->getFromStaticEntityRevisionCache($revision_id); + } + + if (!$revision && $this->entityType->isPersistentlyCacheable()) { + $revision = $this->getRevisionFromPersistentCache($revision_id); + if ($revision) { + $entities = [$revision->id() => $revision]; + $this->postLoad($entities); + $this->setStaticEntityRevisionCache($revision_id, $revision); + if ($revision->isDefaultRevision()) { + $this->setStaticCache($entities); + } + } + } + } + + return $revision ?: $this->doLoadRevision($revision_id); + } + + /** + * Load a specific entity revision. + * + * @param int|string $revision_id + * The revision id. + * + * @return \Drupal\Core\Entity\ContentEntityInterface|null + * The specified entity revision or NULL if not found. + */ + protected function doLoadRevision($revision_id) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */ + if ($revision = $this->doLoadRevisionFieldItems($revision_id)) { $entities = [$revision->id() => $revision]; $this->invokeStorageLoadHook($entities); + if ($revision->isDefaultRevision()) { + $this->setPersistentCache($entities); + } $this->postLoad($entities); + + if (!isset($this->ignoreStaticCache)) { + if ($revision->isDefaultRevision()) { + $this->setStaticCache($entities); + } + $this->setStaticEntityRevisionCache($revision_id, $revision); + } } return $revision; @@ -323,6 +408,19 @@ protected function doPostSave(EntityInterface $entity, $update) { $this->invokeTranslationHooks($entity); } + if ($this->entityType->isRevisionable()) { + // If a non-default revision has been loaded it has been statically only + // in the entity revision cache, which means that the parent class call + // in ::doPostSave to ::resetCache will not be able to reset the static + // revision cache as it is only calling the function with the entity id, + // but as for this entity id there will be no entity cache entry we'll + // not be able to retrieve the revision id and remove its static cache + // there. + // @todo consider adding an optional entity parameter to ::resetCache, as + // in this case we'll not need this logic here. + $this->resetRevisionCache([$entity->getRevisionId(), $entity->getLoadedRevisionId()]); + } + parent::doPostSave($entity, $update); // The revision is stored, it should no longer be marked as new now. @@ -628,6 +726,44 @@ protected function getFromPersistentCache(array &$ids = NULL) { } /** + * Gets entity from the persistent cache backend by revision id. + * + * @param int|null $revision_id + * If not empty, return the entity that match these revision ID. + * + * @return \Drupal\Core\Entity\ContentEntityInterface|null + * The entity from the persistent cache or NULL if not present. + */ + protected function getRevisionFromPersistentCache($revision_id = NULL) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */ + $revision = NULL; + if (!$this->entityType->isPersistentlyCacheable() || empty($revision_id)) { + return $revision; + } + // Build the list of cache entries to retrieve. + $cid = $this->buildRevisionCacheId($revision_id); + if ($cache = $this->cacheBackend->get($cid)) { + // Get the entity that was found in the cache. + $revision = $cache->data; + } + + // We might have saved already a new entity revision as the default one so + // on fetching from the persistent cache we have to update the + // $isDefaultRevision property of the entity revision. + if ($revision && $revision->isDefaultRevision() && ($revision_id != $this->getDefaultRevisionId($revision->id()))) { + $revision->isDefaultRevision(FALSE); + } + + if ($revision) { + $entities = [$revision->id() => $revision]; + $this->invokeStorageLoadHook($entities); + $this->postLoad($entities); + } + + return $revision; + } + + /** * Stores entities in the persistent cache backend. * * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities @@ -644,6 +780,9 @@ protected function setPersistentCache($entities) { ]; foreach ($entities as $id => $entity) { $this->cacheBackend->set($this->buildCacheId($id), $entity, CacheBackendInterface::CACHE_PERMANENT, $cache_tags); + if ($this->entityType->isRevisionable() && $entity->isDefaultRevision()) { + $this->cacheBackend->set($this->buildRevisionCacheId($entity->getRevisionId()), $entity, CacheBackendInterface::CACHE_PERMANENT, $cache_tags); + } } } @@ -653,6 +792,9 @@ protected function setPersistentCache($entities) { public function loadUnchanged($id) { $ids = [$id]; + // Reset the static entity revision cache. + $revision_ids = $this->getRevisionIDsOfStaticallyCachedEntities($ids); + $this->resetStaticRevisionCache($revision_ids); // The cache invalidation in the parent has the side effect that loading the // same entity again during the save process (for example in // hook_entity_presave()) will load the unchanged entity. Simulate this @@ -693,16 +835,19 @@ public function loadUnchanged($id) { public function resetCache(array $ids = NULL) { if ($ids) { $cids = []; + $revision_ids = $this->getRevisionIDsOfStaticallyCachedEntities($ids); foreach ($ids as $id) { unset($this->entities[$id]); $cids[] = $this->buildCacheId($id); } + $this->resetRevisionCache($revision_ids); if ($this->entityType->isPersistentlyCacheable()) { $this->cacheBackend->deleteMultiple($cids); } } else { $this->entities = []; + $this->entityRevisions = []; if ($this->entityType->isPersistentlyCacheable()) { Cache::invalidateTags([$this->entityTypeId . '_values']); } @@ -710,6 +855,129 @@ public function resetCache(array $ids = NULL) { } /** + * Returns the revision ids of statically cached entities. + * + * @param $ids + * (optional) If specified, the revision ids will be retrieved of the + * statically cached entities with the given ids only, otherwise the + * revision ids of all statically cached entities will be retrieved. + * + * @return array + * The revision ids. + */ + protected function getRevisionIDsOfStaticallyCachedEntities(array $ids = NULL) { + $revision_ids = []; + if (!$this->entityType->isRevisionable()) { + return $revision_ids; + } + $entities = isset($ids) ? array_intersect_key($this->entities, array_flip($ids)) : $this->entities; + foreach ($entities as $entity) { + $original_revision_id = $entity->getLoadedRevisionId(); + $revision_id = $entity->getRevisionId(); + $revision_ids[$original_revision_id] = $original_revision_id; + $revision_ids[$revision_id] = $revision_id; + } + return $revision_ids; + } + + /** + * Resets the static and persistent entity revision cache. + * + * @param $revision_ids + * The revision ids to reset the revision cache for. + */ + protected function resetRevisionCache(array $revision_ids) { + $this->resetStaticRevisionCache($revision_ids); + $this->resetPersistentRevisionCache($revision_ids); + } + + /** + * Resets the static entity revision cache. + * + * @param $revision_ids + * The revision ids to reset the static revision cache for. + */ + protected function resetStaticRevisionCache(array $revision_ids) { + if (!$this->entityType->isStaticallyCacheable()) { + return; + } + foreach ($revision_ids as $revision_id) { + unset($this->entityRevisions[$revision_id]); + } + } + + /** + * Resets the persistent entity revision cache. + * + * @param $revision_ids + * The revision ids to reset the persistent revision cache for. + */ + protected function resetPersistentRevisionCache(array $revision_ids) { + if (!$this->entityType->isPersistentlyCacheable()) { + return; + } + $cids = []; + foreach ($revision_ids as $revision_id) { + $cids[] = $this->buildRevisionCacheId($revision_id); + } + $this->cacheBackend->deleteMultiple($cids); + } + + /** + * Gets entities from the static entity revision cache. + * + * @param int $revision_id + * The entity revision id, for which the entity should be loaded from the + * static entity revision cache. + * + * @return \Drupal\Core\Entity\ContentEntityInterface|NULL + * The entity from the entity revision cache or NULL if it is not present. + */ + protected function getFromStaticEntityRevisionCache($revision_id) { + $entity = $this->entityType->isStaticallyCacheable() && isset($this->entityRevisions[$revision_id]) ? $this->entityRevisions[$revision_id] : NULL; + // Update the default revision + if ($entity) { + + } + return $entity; + } + + /** + * Stores entities in the static entity revision cache. + * + * @param int $revision_id + * The entity revision id, for which the entity should be stored in the + * static entity revision cache. + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity to store in the static entity revision cache. + */ + protected function setStaticEntityRevisionCache($revision_id, ContentEntityInterface $entity) { + if ($this->entityType->isStaticallyCacheable()) { + $this->entityRevisions[$revision_id] = $entity; + } + } + + /** + * {@inheritdoc} + */ + public function loadRevisionUnchanged($revision_id) { + // Load the revision by ignoring the static entity and entity revision + // cache. + /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */ + if ($revision = $this->getRevisionFromPersistentCache($revision_id)) { + $entities = [$revision->id() => $revision]; + $this->postLoad($entities); + } + else { + $this->ignoreStaticCache = TRUE; + $revision = $this->loadRevision($revision_id); + unset($this->ignoreStaticCache); + } + + return $revision; + } + + /** * Builds the cache ID for the passed in entity ID. * * @param int $id @@ -722,4 +990,36 @@ protected function buildCacheId($id) { return "values:{$this->entityTypeId}:$id"; } + /** + * Builds the cache ID for the passed in entity revision ID. + * + * @param int $revision_id + * Entity revision ID for which the cache ID should be built. + * + * @return string + * Cache ID that can be passed to the cache backend. + */ + protected function buildRevisionCacheId($revision_id) { + return "values:{$this->entityTypeId}:revision:$revision_id"; + } + + /** + * Fetches the current default revision id. + * + * @param int $id + * The entity id. + * + * @return int|null + * The default revision id. + */ + protected function getDefaultRevisionId($id) { + $result = NULL; + if ($this->entityType->isRevisionable()) { + $result = $this->getQuery() + ->condition($this->entityType->getKey('id'), $id) + ->execute(); + } + return $result ? key($result) : NULL; + } + } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php index 47e058d..436ea67 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php @@ -8,6 +8,52 @@ interface ContentEntityStorageInterface extends EntityStorageInterface { /** + * Loads one entity. + * + * @param mixed $id + * The ID of the entity to load. + * + * @return \Drupal\Core\Entity\ContentEntityInterface|null + * An entity object. NULL if no matching entity is found. + */ + public function load($id); + + /** + * Load a specific entity revision. + * + * @param int $revision_id + * The revision id. + * + * @return \Drupal\Core\Entity\ContentEntityInterface|null + * The specified entity revision or NULL if not found. + */ + public function loadRevision($revision_id); + + /** + * Loads an unchanged entity by revision id from the database. + * + * @param int $revision_id + * The revision ID of the entity to load. + * + * @return \Drupal\Core\Entity\ContentEntityInterface|null + * The unchanged entity, or NULL if the entity cannot be loaded. + * + * @todo Remove this method once we have a reliable way to retrieve the + * unchanged entity from the entity object. + */ + public function loadRevisionUnchanged($revision_id); + + /** + * Delete a specific entity revision. + * + * A revision can only be deleted if it's not the currently active one. + * + * @param int $revision_id + * The revision id. + */ + public function deleteRevision($revision_id); + + /** * Constructs a new entity translation object, without permanently saving it. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php index c42e7c0..731b46a 100644 --- a/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php @@ -13,6 +13,30 @@ class KeyValueContentEntityStorage extends KeyValueEntityStorage implements Cont /** * {@inheritdoc} */ + public function loadRevision($revision_id) { + // @todo Complete the content entity storage implementation in + // https://www.drupal.org/node/2618436. + } + + /** + * {@inheritdoc} + */ + public function loadRevisionUnchanged($revision_id) { + // @todo Complete the content entity storage implementation in + // https://www.drupal.org/node/2618436. + } + + /** + * {@inheritdoc} + */ + public function deleteRevision($revision_id) { + // @todo Complete the content entity storage implementation in + // https://www.drupal.org/node/2618436. + } + + /** + * {@inheritdoc} + */ public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []) { // @todo Complete the content entity storage implementation in // https://www.drupal.org/node/2618436. diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php index cb1bc1f..c02ff0e 100644 --- a/core/modules/content_moderation/src/Entity/ContentModerationState.php +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php @@ -157,7 +157,7 @@ public static function getCurrentUserId() { public function save() { $related_entity = \Drupal::entityTypeManager() ->getStorage($this->content_entity_type_id->value) - ->loadRevision($this->content_entity_revision_id->value); + ->loadRevisionUnchanged($this->content_entity_revision_id->value); if ($related_entity instanceof TranslatableInterface) { $related_entity = $related_entity->getTranslation($this->activeLangcode); } diff --git a/core/modules/content_moderation/src/EntityOperations.php b/core/modules/content_moderation/src/EntityOperations.php index af87f6f..14c60d2 100644 --- a/core/modules/content_moderation/src/EntityOperations.php +++ b/core/modules/content_moderation/src/EntityOperations.php @@ -262,8 +262,11 @@ public function entityView(array &$build, EntityInterface $entity, EntityViewDis * TRUE if the default revision is published. FALSE otherwise. */ protected function isDefaultRevisionPublished(EntityInterface $entity, WorkflowInterface $workflow) { + // We have to load the unchanged entity for the default revision to ensure + // that we are not retrieving a modified entity from the static entity + // cache. $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId()); - $default_revision = $storage->load($entity->id()); + $default_revision = $storage->loadUnchanged($entity->id()); // Ensure we are comparing the same translation as the current entity. if ($default_revision instanceof TranslatableInterface && $default_revision->isTranslatable()) { diff --git a/core/modules/content_moderation/src/ModerationInformation.php b/core/modules/content_moderation/src/ModerationInformation.php index c20be52..ac9724b 100644 --- a/core/modules/content_moderation/src/ModerationInformation.php +++ b/core/modules/content_moderation/src/ModerationInformation.php @@ -74,7 +74,7 @@ public function shouldModerateEntitiesOfBundle(EntityTypeInterface $entity_type, */ public function getLatestRevision($entity_type_id, $entity_id) { if ($latest_revision_id = $this->getLatestRevisionId($entity_type_id, $entity_id)) { - return $this->entityTypeManager->getStorage($entity_type_id)->loadRevision($latest_revision_id); + return $this->entityTypeManager->getStorage($entity_type_id)->loadRevisionUnchanged($latest_revision_id); } } diff --git a/core/modules/content_moderation/src/ModerationInformationInterface.php b/core/modules/content_moderation/src/ModerationInformationInterface.php index 862987f..ae9a24d 100644 --- a/core/modules/content_moderation/src/ModerationInformationInterface.php +++ b/core/modules/content_moderation/src/ModerationInformationInterface.php @@ -48,7 +48,7 @@ public function canModerateEntitiesOfEntityType(EntityTypeInterface $entity_type public function shouldModerateEntitiesOfBundle(EntityTypeInterface $entity_type, $bundle); /** - * Loads the latest revision of a specific entity. + * Loads the unmodified latest revision of a specific entity. * * @param string $entity_type_id * The entity type ID. diff --git a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php index 0444644..efcbf31 100644 --- a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php +++ b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php @@ -69,7 +69,12 @@ protected function loadContentModerationStateRevision(ContentEntityInterface $en } /** @var \Drupal\content_moderation\ContentModerationStateInterface $content_moderation_state */ - $content_moderation_state = $content_moderation_storage->loadRevision(key($revisions)); + // In the case the content moderation state entity is edited and saved + // separately from the related entity it will trigger saving of the related + // entity and therefor the content moderation state will be loaded here + // again and we have to make sure we will load the unchanged entity and not + // load the already modified one from the static entity cache. + $content_moderation_state = $content_moderation_storage->loadRevisionUnchanged(key($revisions)); if ($entity->getEntityType()->hasKey('langcode')) { $langcode = $entity->language()->getId(); if (!$content_moderation_state->hasTranslation($langcode)) { diff --git a/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php b/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php index f68c2be..920f0fe 100644 --- a/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php +++ b/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php @@ -8,6 +8,7 @@ namespace Drupal\Tests\migrate\Unit\destination; use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\ContentEntityStorageInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Plugin\migrate\destination\EntityRevision as RealEntityRevision; @@ -47,7 +48,7 @@ protected function setUp() { // Setup mocks to be used when creating a revision destination. $this->migration = $this->prophesize(MigrationInterface::class); - $this->storage = $this->prophesize('\Drupal\Core\Entity\EntityStorageInterface'); + $this->storage = $this->prophesize(ContentEntityStorageInterface::class); $this->entityManager = $this->prophesize('\Drupal\Core\Entity\EntityManagerInterface'); $this->fieldTypeManager = $this->prophesize('\Drupal\Core\Field\FieldTypePluginManagerInterface'); } @@ -60,7 +61,7 @@ protected function setUp() { public function testGetEntityDestinationValues() { $destination = $this->getEntityRevisionDestination([]); // Return a dummy because we don't care what gets called. - $entity = $this->prophesize('\Drupal\Core\Entity\EntityInterface') + $entity = $this->prophesize(ContentEntityInterface::class) ->willImplement('\Drupal\Core\Entity\RevisionableInterface'); // Assert that the first ID from the destination values is used to load the // entity. diff --git a/core/modules/views/tests/src/Unit/Plugin/query/SqlTest.php b/core/modules/views/tests/src/Unit/Plugin/query/SqlTest.php index 02a5ce6..c2c5069 100644 --- a/core/modules/views/tests/src/Unit/Plugin/query/SqlTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/query/SqlTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\views\Unit\Plugin\query; +use Drupal\Core\Entity\ContentEntityStorageInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityType; @@ -212,8 +213,8 @@ protected function setupEntityTypes($entities_by_type = [], $entity_revisions_by // Setup the loading of entities and entity revisions. $entity_storages = [ - 'first' => $this->prophesize(EntityStorageInterface::class), - 'second' => $this->prophesize(EntityStorageInterface::class), + 'first' => $this->prophesize(ContentEntityStorageInterface::class), + 'second' => $this->prophesize(ContentEntityStorageInterface::class), ]; foreach ($entities_by_type as $entity_type_id => $entities) { diff --git a/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityStaticCacheTest.php b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityStaticCacheTest.php new file mode 100644 index 0000000..f1f81d7 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityStaticCacheTest.php @@ -0,0 +1,102 @@ +installEntitySchema('user'); + $this->installEntitySchema($this->nonRevEntityTypeId); + $this->installEntitySchema($this->revEntityTypeId); + } + + /** + * Tests that loadUnchanged will not return the same entity object reference. + */ + public function testLoadUnchanged() { + /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */ + $entity_type_manager = $this->container->get('entity_type.manager'); + + $non_rev_entity_type = $entity_type_manager->getDefinition($this->nonRevEntityTypeId); + $this->assertTrue($non_rev_entity_type->isStaticallyCacheable()); + $this->assertTrue($non_rev_entity_type->isPersistentlyCacheable()); + $this->doTestLoadUnchanged($this->nonRevEntityTypeId); + + $rev_entity_type = $entity_type_manager->getDefinition($this->revEntityTypeId); + $this->assertTrue($rev_entity_type->isStaticallyCacheable()); + $this->assertTrue($rev_entity_type->isPersistentlyCacheable()); + $this->assertTrue($rev_entity_type->isRevisionable()); + $this->doTestLoadUnchanged($this->revEntityTypeId); + } + + /** + * Tests Storage::loadUnchanged returning different entity object reference. + * + * @param string $entity_type_id + * The entity type ID to test Storage::loadUnchanged() with. + */ + protected function doTestLoadUnchanged($entity_type_id) { + foreach ([FALSE, TRUE] as $invalidate_entity_cache) { + /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ + $storage = $this->container->get('entity_type.manager') + ->getStorage($entity_type_id); + + $entity = $storage->create(); + $entity->save(); + + $entity = $storage->load($entity->id()); + // Invalidating the entity cache will lead to not retrieving the entity + // from the persistent entity cache. This simulates e.g. a behavior where + // in an entity insert hook a field config is created and saved and then + // the cache tag "entity_field_info" will be invalidated leading to + // invalidating the entities in the entity cache, which will prevent + // loadUnchanged from retrieving the entity from the persistent cache, + // which will test that the static entity cache has been reset properly, + // otherwise if not then the same entity object reference will be + // returned. + if ($invalidate_entity_cache) { + Cache::invalidateTags(['entity_field_info']); + } + $unchanged = $storage->loadUnchanged($entity->id()); + $message = $invalidate_entity_cache ? 'loadUnchanged returns a different entity object reference when the entity cache is invalidated before that.' : 'loadUnchanged returns a different entity object reference when the entity cache is not invalidated before that.'; + $this->assertNotSame($entity, $unchanged, $message); + } + } + +} diff --git a/core/tests/Drupal/Tests/Core/ParamConverter/EntityRevisionParamConverterTest.php b/core/tests/Drupal/Tests/Core/ParamConverter/EntityRevisionParamConverterTest.php index 23ac69f..e55f258 100644 --- a/core/tests/Drupal/Tests/Core/ParamConverter/EntityRevisionParamConverterTest.php +++ b/core/tests/Drupal/Tests/Core/ParamConverter/EntityRevisionParamConverterTest.php @@ -2,9 +2,9 @@ namespace Drupal\Tests\Core\ParamConverter; -use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityRepositoryInterface; -use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\ContentEntityStorageInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\ParamConverter\EntityRevisionParamConverter; use Drupal\Tests\UnitTestCase; @@ -65,8 +65,8 @@ public function testApplyingRoute() { * @covers ::convert */ public function testConvert() { - $entity = $this->prophesize(EntityInterface::class)->reveal(); - $storage = $this->prophesize(EntityStorageInterface::class); + $entity = $this->prophesize(ContentEntityInterface::class)->reveal(); + $storage = $this->prophesize(ContentEntityStorageInterface::class); $storage->loadRevision(1)->willReturn($entity); $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);