diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index afdce40..adb627e 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -4,6 +4,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Entity\Plugin\DataType\EntityReference; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -627,6 +628,37 @@ protected function setPersistentCache($entities) { /** * {@inheritdoc} */ + public function loadUnchanged($id) { + $ids = [$id]; + $entities = $this->getFromPersistentCache($ids); + + if (!$entities) { + // loadUnchanged should not destroy the existent entity object reference. + $static_cache_entities = $this->getFromStaticCache($ids); + $this->removeFromStaticCache($id); + $entities[$id] = $this->load($id); + + // Put back the previous entity object reference into the static cache. + $this->removeFromStaticCache($id); + $this->setStaticCache($static_cache_entities); + } + else { + // As the entities are put into the persistent cache before the post load + // has been executed we have to execute it if we have retrieved the + // entity directly from the persistent cache. + $this->postLoad($entities); + } + + if ($entities[$id]) { + $this->loadUnchangedReferences($entities[$id]); + } + + return $entities[$id]; + } + + /** + * {@inheritdoc} + */ public function resetCache(array $ids = NULL) { if ($ids) { $cids = array(); @@ -659,4 +691,27 @@ protected function buildCacheId($id) { return "values:{$this->entityTypeId}:$id"; } + /** + * {@inheritdoc} + */ + protected function loadUnchangedReferences(EntityInterface $entity) { + /** @var ContentEntityInterface $entity */ + // The referenced entities have to be loaded unchanged as well. + foreach (array_keys($entity->getTranslationLanguages()) as $langcode) { + $translation = $entity->getTranslation($langcode); + + foreach ($translation->getFields() as $field_name => $field_items) { + foreach ($field_items as $field_item) { + // Loop over all properties of a field item. + foreach ($field_item->getProperties(TRUE) as $property) { + if ($property instanceof EntityReference && $entity_reference = $property->getValue()) { + $entity_reference_unchanged = $this->entityManager->getStorage($entity_reference->getEntityTypeId())->loadUnchanged($entity_reference->id()); + $property->setValue($entity_reference_unchanged); + } + } + } + } + } + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php index f583121..8741657 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php @@ -106,7 +106,11 @@ public function getEntityType() { */ public function loadUnchanged($id) { $this->resetCache(array($id)); - return $this->load($id); + $entity = $this->load($id); + if ($entity) { + $this->loadUnchangedReferences($entity); + } + return $entity; } /** @@ -154,6 +158,16 @@ protected function setStaticCache(array $entities) { } /** + * Removes an entity from the static cache. + * + * @param mixed $id + * The ID of the entity to remove from the static cache. + */ + protected function removeFromStaticCache($id) { + unset($this->entities[$id]); + } + + /** * Invokes a hook on behalf of the entity. * * @param string $hook @@ -530,4 +544,12 @@ public function getAggregateQuery($conjunction = 'AND') { */ abstract protected function getQueryServiceName(); + /** + * Loads the unchanged entity references from the database. + * + * @param EntityInterface $entity + * The entity for which to load the unchanged referenced entities. + */ + protected function loadUnchangedReferences(EntityInterface $entity) {} + } 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..0de17f8 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityStaticCacheTest.php @@ -0,0 +1,71 @@ +installEntitySchema('entity_test_rev'); + + $entity = EntityTestRev::create([ + 'name' => 'test' + ]); + $entity->save(); + $this->entityId = $entity->id(); + } + + /** + * Tests the static entity cache. + */ + public function testLoadUnchangedNotDestroyingPreviousEntityReferences() { + /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ + $storage = \Drupal::entityTypeManager()->getStorage('entity_test_rev'); + + // Test load. + $entity_load_first = $storage->load($this->entityId); + $entity_load_second = $storage->load($this->entityId); + $this->assertSame($entity_load_first, $entity_load_second, 'Calling ContentEntityStorageBase::load multiple times returns a reference to the same entity object.'); + + // Test loading unchanged entity + $entity_unchanged_first = $storage->loadUnchanged($this->entityId); + $entity_unchanged_second = $storage->loadUnchanged($this->entityId); + $this->assertNotSame($entity_unchanged_first, $entity_unchanged_second, 'Calling ContentEntityStorageBase::loadUnchanged multiple times returns each time a new entity object.'); + $this->assertNotSame($entity_load_first, $entity_unchanged_first, 'ContentEntityStorageBase::load and ContentEntityStorageBase::loadUnchanged return different entity objects.'); + + // Test load. + $entity_load_third = $storage->load($this->entityId); + $this->assertSame($entity_load_second, $entity_load_third, 'Calling ContentEntityStorageBase::load again after having called ContentEntityStorageBase::loadUnchanged returns a reference to the same entity object as the previous calls to ContentEntityStorageBase::load.'); + } + +}