Commit 6b5c18da authored by webchick's avatar webchick

Issue #2303881 by alexpott, effulgentsia: Fixed Config entity static cache...

Issue #2303881 by alexpott, effulgentsia: Fixed Config entity static cache doesn't get reset and isn't override aware.
parent 9e11fdf9
......@@ -81,6 +81,17 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
*/
protected $languageManager;
/**
* 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
* states of config overrides.
*
* @var array
* @see \Drupal\Core\Config\ConfigFactoryInterface::getCacheKeys().
*/
protected $entities = array();
/**
* Constructs a ConfigEntityStorage object.
*
......@@ -270,6 +281,46 @@ protected function has($id, EntityInterface $entity) {
return !$config->isNew();
}
/**
* Gets entities from the static cache.
*
* @param array $ids
* If not empty, return entities that match these IDs.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* Array of entities from the entity cache.
*/
protected function getFromStaticCache(array $ids) {
$entities = array();
// Load any available entities from the internal cache.
if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) {
$config_overrides_key = implode(':', $this->configFactory->getCacheKeys());
foreach ($ids as $id) {
if (!empty($this->entities[$id])) {
if (isset($this->entities[$id][$config_overrides_key])) {
$entities[$id] = $this->entities[$id][$config_overrides_key];
}
}
}
}
return $entities;
}
/**
* Stores entities in the static entity cache.
*
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* Entities to store in the cache.
*/
protected function setStaticCache(array $entities) {
if ($this->entityType->isStaticallyCacheable()) {
$config_overrides_key = implode(':', $this->configFactory->getCacheKeys());
foreach ($entities as $id => $entity) {
$this->entities[$id][$config_overrides_key] = $entity;
}
}
}
/**
* Invokes a hook on behalf of the entity.
*
......
......@@ -804,9 +804,6 @@ protected function doDelete($entities) {
$this->invokeFieldMethod('delete', $entity);
$this->deleteFieldItems($entity);
}
// Reset the cache as soon as the changes have been applied.
$this->resetCache($ids);
}
/**
......@@ -865,7 +862,6 @@ protected function doSave($id, EntityInterface $entity) {
if ($this->revisionTable) {
$entity->setNewRevision(FALSE);
}
$cache_ids = array($entity->id());
}
else {
// Ensure the entity is still seen as new after assigning it an id,
......@@ -898,12 +894,9 @@ protected function doSave($id, EntityInterface $entity) {
if ($this->revisionTable) {
$entity->setNewRevision(FALSE);
}
// Reset general caches, but keep caches specific to certain entities.
$cache_ids = array();
}
$this->invokeFieldMethod($is_new ? 'insert' : 'update', $entity);
$this->saveFieldItems($entity, !$is_new);
$this->resetCache($cache_ids);
if (!$is_new && $this->dataTable) {
$this->invokeTranslationHooks($entity);
......
......@@ -339,14 +339,18 @@ public function delete(array $entities) {
return;
}
// Allow code to run before deleting.
$entity_class = $this->entityClass;
$entity_class::preDelete($this, $entities);
foreach ($entities as $entity) {
$this->invokeHook('predelete', $entity);
}
// Perform the delete and reset the static cache for the deleted entities.
$this->doDelete($entities);
$this->resetCache(array_keys($entities));
// Allow code to run after deleting.
$entity_class::postDelete($this, $entities);
foreach ($entities as $entity) {
$this->invokeHook('delete', $entity);
......@@ -391,8 +395,9 @@ public function save(EntityInterface $entity) {
$entity->preSave($this);
$this->invokeHook('presave', $entity);
// Perform the save.
// Perform the save and reset the static cache for the changed entity.
$return = $this->doSave($id, $entity);
$this->resetCache(array($id));
// The entity is no longer new.
$entity->enforceIsNew(FALSE);
......
<?php
/**
* @file
* Contains \Drupal\config\Tests\ConfigEntityStaticCacheTest.
*/
namespace Drupal\config\Tests;
use Drupal\config_entity_static_cache_test\ConfigOverrider;
use Drupal\simpletest\KernelTestBase;
/**
* Tests the entity static cache when used by config entities.
*
* @group config
*/
class ConfigEntityStaticCacheTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('config_test', 'config_entity_static_cache_test');
/**
* The type ID of the entity under test.
*
* @var string
*/
protected $entityTypeId;
/**
* The entity ID of the entity under test.
*
* @var string
*/
protected $entityId;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->entityTypeId = 'config_test';
$this->entityId = 'test_1';
entity_create($this->entityTypeId, array('id' => $this->entityId, 'label' => 'Original label'))->save();
}
/**
* Tests that the static cache is working.
*/
public function testCacheHit() {
$entity_1 = entity_load($this->entityTypeId, $this->entityId);
$entity_2 = entity_load($this->entityTypeId, $this->entityId);
// config_entity_static_cache_test_config_test_load() sets _loadStamp to a
// random string. If they match, it means $entity_2 was retrieved from the
// static cache rather than going through a separate load sequence.
$this->assertIdentical($entity_1->_loadStamp, $entity_2->_loadStamp);
}
/**
* Tests that the static cache is reset on entity save and delete.
*/
public function testReset() {
$entity = entity_load($this->entityTypeId, $this->entityId);
// Ensure loading after a save retrieves the updated entity rather than an
// obsolete cached one.
$entity->label = 'New label';
$entity->save();
$entity = entity_load($this->entityTypeId, $this->entityId);
$this->assertIdentical($entity->label, 'New label');
// Ensure loading after a delete retrieves NULL rather than an obsolete
// cached one.
$entity->delete();
$this->assertNull(entity_load($this->entityTypeId, $this->entityId));
}
/**
* Tests that the static cache is sensitive to config overrides.
*/
public function testConfigOverride() {
// Prime the cache prior to adding a config override.
entity_load($this->entityTypeId, $this->entityId);
// Add the config override, and ensure that what is loaded is correct
// despite the prior cache priming.
\Drupal::configFactory()->addOverride(new ConfigOverrider());
$entity_override = entity_load($this->entityTypeId, $this->entityId);
$this->assertIdentical($entity_override->label, 'Overridden label');
// Disable overrides to ensure that loading the config entity again does not
// return the overridden value.
\Drupal::configFactory()->setOverrideState(FALSE);
$entity_no_override = entity_load($this->entityTypeId, $this->entityId);
$this->assertNotIdentical($entity_no_override->label, 'Overridden label');
$this->assertNotIdentical($entity_override->_loadStamp, $entity_no_override->_loadStamp);
// Reload the entity and ensure the cache is used.
$this->assertIdentical(entity_load($this->entityTypeId, $this->entityId)->_loadStamp, $entity_no_override->_loadStamp);
// Enable overrides and reload the entity and ensure the cache is used.
\Drupal::configFactory()->setOverrideState(TRUE);
$this->assertIdentical(entity_load($this->entityTypeId, $this->entityId)->_loadStamp, $entity_override->_loadStamp);
}
}
name: 'Configuration entity static cache test'
type: module
package: Testing
version: VERSION
core: 8.x
<?php
/**
* @file
* Provides configuration entity static cache test helpers.
*/
use Drupal\Component\Utility\Random;
/**
* Implements hook_ENTITY_TYPE_load().
*/
function config_entity_static_cache_test_config_test_load($entities) {
static $random;
if (!$random) {
$random = new Random();
}
foreach ($entities as $entity) {
// Add a random stamp for every load(), so that during tests, we can tell
// if an entity was retrieved from cache (unchanged stamp) or reloaded.
$entity->_loadStamp = $random->string(8, TRUE);
}
}
/**
* Implements hook_entity_type_alter().
*/
function config_entity_static_cache_test_entity_type_alter(array &$entity_types) {
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
$entity_types['config_test']->set('static_cache', TRUE);
}
<?php
/**
* @file
* Contains \Drupal\config_entity_static_cache_test\ConfigOverrider.
*/
namespace Drupal\config_entity_static_cache_test;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
/**
* Tests module overrides for configuration.
*/
class ConfigOverrider implements ConfigFactoryOverrideInterface {
/**
* {@inheritdoc}
*/
public function loadOverrides($names) {
return array(
'config_test.dynamic.test_1' => array(
'label' => 'Overridden label',
)
);
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return 'config_entity_static_cache_test';
}
/**
* {@inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
return NULL;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment