diff --git a/core/core.services.yml b/core/core.services.yml index 459503e..c96d97e 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -532,6 +532,8 @@ services: # @todo Remove this tag in https://www.drupal.org/node/2549143. tags: - { name: plugin_manager_cache_clear } + entity.static_cache: + class: Drupal\Core\Cache\StaticCache\StaticCache entity_type.manager: class: Drupal\Core\Entity\EntityTypeManager arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@string_translation', '@class_resolver'] diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php index 723ba53..9d9eb10 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Config\Entity; use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Cache\StaticCache\StaticCacheInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigImporterException; use Drupal\Core\Entity\EntityInterface; @@ -104,9 +105,13 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora * The UUID service. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. + * @param \Drupal\Core\Cache\StaticCache\StaticCacheInterface $static_cache + * The static cache backend. */ - public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) { - parent::__construct($entity_type); + public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, StaticCacheInterface $static_cache = NULL) { + // @todo Remove this line in Drupal 9.0.x and make $static_cache required. + $static_cache = isset($static_cache) ? $static_cache : \Drupal::service('entity.static_cache'); + parent::__construct($entity_type, $static_cache); $this->configFactory = $config_factory; $this->uuidService = $uuid_service; @@ -121,7 +126,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('entity.static_cache') ); } @@ -324,43 +330,10 @@ public function hasData() { } /** - * 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 = []; - // Load any available entities from the internal cache. - if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) { - $config_overrides_key = $this->overrideFree ? '' : 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 = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys()); - foreach ($entities as $id => $entity) { - $this->entities[$id][$config_overrides_key] = $entity; - } - } + * {@inheritdoc} + */ + protected function getCacheId($id) { + return parent::getCacheId($id) . ':' . ($this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys())); } /** diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 2b06b92..781a9ae 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\Cache\StaticCache\StaticCacheInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -43,9 +44,13 @@ * The entity manager. * @param \Drupal\Core\Cache\CacheBackendInterface $cache * The cache backend to be used. + * @param \Drupal\Core\Cache\StaticCache\StaticCacheInterface $static_cache + * The static cache backend. */ - public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, CacheBackendInterface $cache) { - parent::__construct($entity_type); + public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, StaticCacheInterface $static_cache = NULL) { + // @todo Remove this line in Drupal 9.0.x and make $static_cache required. + $static_cache = isset($static_cache) ? $static_cache : \Drupal::service('entity.static_cache'); + parent::__construct($entity_type, $static_cache); $this->bundleKey = $this->entityType->getKey('bundle'); $this->entityManager = $entity_manager; $this->cacheBackend = $cache; @@ -58,7 +63,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI return new static( $entity_type, $container->get('entity.manager'), - $container->get('cache.entity') + $container->get('cache.entity'), + $container->get('entity.static_cache') ); } @@ -727,17 +733,17 @@ public function loadUnchanged($id) { */ public function resetCache(array $ids = NULL) { if ($ids) { - $cids = []; - foreach ($ids as $id) { - unset($this->entities[$id]); - $cids[] = $this->buildCacheId($id); - } + parent::resetCache($ids); if ($this->entityType->isPersistentlyCacheable()) { + $cids = []; + foreach ($ids as $id) { + $cids[] = $this->buildCacheId($id); + } $this->cacheBackend->deleteMultiple($cids); } } else { - $this->entities = []; + parent::resetCache(); if ($this->entityType->isPersistentlyCacheable()) { Cache::invalidateTags([$this->entityTypeId . '_values']); } diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php index e5bf880..879aabf 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Entity; use Drupal\Core\Entity\Query\QueryInterface; +use Drupal\Core\Cache\StaticCache\StaticCacheInterface; /** * A base entity storage class. @@ -73,18 +74,37 @@ protected $entityClass; /** + * The static cache. + * + * @var \Drupal\Core\Cache\StaticCache\StaticCacheInterface + */ + protected $staticCache; + + /** + * The static cache cache tag. + * + * @var string + */ + protected $cacheTag; + + /** * Constructs an EntityStorageBase instance. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type * The entity type definition. + * @param \Drupal\Core\Cache\StaticCache\StaticCacheInterface + * The static cache. */ - public function __construct(EntityTypeInterface $entity_type) { + public function __construct(EntityTypeInterface $entity_type, StaticCacheInterface $static_cache = NULL) { $this->entityTypeId = $entity_type->id(); $this->entityType = $entity_type; $this->idKey = $this->entityType->getKey('id'); $this->uuidKey = $this->entityType->getKey('uuid'); $this->langcodeKey = $this->entityType->getKey('langcode'); $this->entityClass = $this->entityType->getClass(); + // @todo Remove this line in Drupal 9.0.x and make $static_cache required. + $this->staticCache = isset($static_cache) ? $static_cache : \Drupal::service('entity.static_cache'); + $this->cacheTag = 'entity_static_cache:' . $this->entityTypeId; } /** @@ -102,6 +122,13 @@ public function getEntityType() { } /** + * Build a cache ID for an entity. + */ + protected function getCacheId($id) { + return 'entity_storage_cache:' . $this->entityTypeId . ':' . $id; + } + + /** * {@inheritdoc} */ public function loadUnchanged($id) { @@ -115,11 +142,12 @@ public function loadUnchanged($id) { public function resetCache(array $ids = NULL) { if ($this->entityType->isStaticallyCacheable() && isset($ids)) { foreach ($ids as $id) { - unset($this->entities[$id]); + $this->staticCache->delete($this->getCacheId($id)); } } else { - $this->entities = []; + // Call the backend method directly. + $this->staticCache->invalidateTags([$this->cacheTag]); } } @@ -135,8 +163,12 @@ public function resetCache(array $ids = NULL) { protected function getFromStaticCache(array $ids) { $entities = []; // Load any available entities from the internal cache. - if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) { - $entities += array_intersect_key($this->entities, array_flip($ids)); + if ($this->entityType->isStaticallyCacheable()) { + foreach ($ids as $id) { + if ($cached = $this->staticCache->get($this->getCacheId($id))) { + $entities[$id] = $cached->data; + } + } } return $entities; } @@ -149,7 +181,9 @@ protected function getFromStaticCache(array $ids) { */ protected function setStaticCache(array $entities) { if ($this->entityType->isStaticallyCacheable()) { - $this->entities += $entities; + foreach ($entities as $id => $entity) { + $this->staticCache->set($this->getCacheId($entity->id()), $entity, StaticCacheInterface::CACHE_PERMANENT, [$this->cacheTag]); + } } } diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php index cd2f26e..7c8d301 100644 --- a/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Entity\KeyValueStore; use Drupal\Component\Uuid\UuidInterface; +use Drupal\Core\Cache\StaticCache\StaticCacheInterface; use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Entity\EntityInterface; @@ -60,9 +61,11 @@ class KeyValueEntityStorage extends EntityStorageBase { * The UUID service. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. + * @param \Drupal\Core\Cache\StaticCache\StaticCacheInterface $static_cache + * The static cache. */ - public function __construct(EntityTypeInterface $entity_type, KeyValueStoreInterface $key_value_store, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) { - parent::__construct($entity_type); + public function __construct(EntityTypeInterface $entity_type, KeyValueStoreInterface $key_value_store, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, StaticCacheInterface $static_cache = NULL) { + parent::__construct($entity_type, $static_cache); $this->keyValueStore = $key_value_store; $this->uuidService = $uuid_service; $this->languageManager = $language_manager; @@ -79,7 +82,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $entity_type, $container->get('keyvalue')->get('entity_storage__' . $entity_type->id()), $container->get('uuid'), - $container->get('language_manager') + $container->get('language_manager'), + $container->get('entity.static_cache') ); } diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index 275e539..acfda78 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -132,7 +132,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('database'), $container->get('entity.manager'), $container->get('cache.entity'), - $container->get('language_manager') + $container->get('language_manager'), + $container->get('entity.static_cache') ); } @@ -160,9 +161,13 @@ public function getFieldStorageDefinitions() { * The cache backend to be used. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. + * @param \Drupal\Core\Cache\CacheBackendInterface $static_cache + * The static cache backend to be used. */ - public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) { - parent::__construct($entity_type, $entity_manager, $cache); + public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, CacheBackendInterface $static_cache = NULL) { + // @todo Remove this line in Drupal 9.0.x and make $static_cache required. + $static_cache = isset($static_cache) ? $static_cache : \Drupal::service('entity.static_cache'); + parent::__construct($entity_type, $entity_manager, $cache, $static_cache); $this->database = $database; $this->languageManager = $language_manager; $this->initTableLayout(); diff --git a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceIntegrationTest.php b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceIntegrationTest.php index ea65c4b..89aea33 100644 --- a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceIntegrationTest.php +++ b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceIntegrationTest.php @@ -83,9 +83,7 @@ public function testSupportedEntityTypesAndWidgets() { // Try to post the form again with no modification and check if the field // values remain the same. - /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ - $storage = $this->container->get('entity_type.manager')->getStorage($this->entityType); - $entity = current($storage->loadByProperties(['name' => $entity_name])); + $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name])); $this->drupalGet($this->entityType . '/manage/' . $entity->id() . '/edit'); $this->assertFieldByName($this->fieldName . '[0][target_id]', $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')'); $this->assertFieldByName($this->fieldName . '[1][target_id]', $referenced_entities[1]->label() . ' (' . $referenced_entities[1]->id() . ')'); @@ -111,7 +109,7 @@ public function testSupportedEntityTypesAndWidgets() { // Try to post the form again with no modification and check if the field // values remain the same. - $entity = current($storage->loadByProperties(['name' => $entity_name])); + $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name])); $this->drupalGet($this->entityType . '/manage/' . $entity->id() . '/edit'); $this->assertFieldByName($this->fieldName . '[target_id]', $target_id . ' (' . $referenced_entities[1]->id() . ')'); @@ -122,7 +120,7 @@ public function testSupportedEntityTypesAndWidgets() { // Since we don't know the form structure for these widgets, just test // that editing and saving an already created entity works. $exclude = ['entity_reference_autocomplete', 'entity_reference_autocomplete_tags']; - $entity = current($storage->loadByProperties(['name' => $entity_name])); + $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name])); $supported_widgets = \Drupal::service('plugin.manager.field.widget')->getOptions('entity_reference'); $supported_widget_types = array_diff(array_keys($supported_widgets), $exclude); @@ -176,8 +174,7 @@ public function testSupportedEntityTypesAndWidgets() { * An array of referenced entities. */ protected function assertFieldValues($entity_name, $referenced_entities) { - $entity = current($this->container->get('entity_type.manager')->getStorage( - $this->entityType)->loadByProperties(['name' => $entity_name])); + $entity = current(\Drupal::entityTypeManager()->getStorage($this->entityType)->loadByProperties(['name' => $entity_name])); $this->assertTrue($entity, format_string('%entity_type: Entity found in the database.', ['%entity_type' => $this->entityType])); diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index 3d86ecd..989f6d5 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -5,6 +5,7 @@ use Drupal\Component\Uuid\UuidInterface; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; +use Drupal\Core\Cache\StaticCache\StaticCache; use Drupal\Core\Config\Config; use Drupal\Core\Config\ConfigDuplicateUUIDException; use Drupal\Core\Config\ConfigFactoryInterface; @@ -133,7 +134,7 @@ protected function setUp() { $entity_query_factory = $this->prophesize(QueryFactoryInterface::class); $entity_query_factory->get($entity_type, 'AND')->willReturn($this->entityQuery->reveal()); - $this->entityStorage = new ConfigEntityStorage($entity_type, $this->configFactory->reveal(), $this->uuidService->reveal(), $this->languageManager->reveal()); + $this->entityStorage = new ConfigEntityStorage($entity_type, $this->configFactory->reveal(), $this->uuidService->reveal(), $this->languageManager->reveal(), new StaticCache()); $this->entityStorage->setModuleHandler($this->moduleHandler->reveal()); $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class); diff --git a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php index 4a2dac5..7756232 100644 --- a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\Core\Entity\KeyValueStore; +use Drupal\Core\Cache\StaticCache\StaticCache; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityFieldManagerInterface; @@ -143,7 +144,7 @@ protected function setUpKeyValueEntityStorage($uuid_key = 'uuid') { ->method('getCurrentLanguage') ->will($this->returnValue($language)); - $this->entityStorage = new KeyValueEntityStorage($this->entityType, $this->keyValueStore, $this->uuidService, $this->languageManager); + $this->entityStorage = new KeyValueEntityStorage($this->entityType, $this->keyValueStore, $this->uuidService, $this->languageManager, new StaticCache()); $this->entityStorage->setModuleHandler($this->moduleHandler); $container = new ContainerBuilder(); diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php index bf3fd46..c1421df 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php @@ -8,6 +8,7 @@ namespace Drupal\Tests\Core\Entity\Sql; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Cache\StaticCache\StaticCache; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManager; @@ -369,7 +370,7 @@ public function testOnEntityTypeCreate() { ->will($this->returnValue($schema_handler)); $storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage') - ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager]) + ->setConstructorArgs(array($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new StaticCache())) ->setMethods(['getStorageSchema']) ->getMock(); @@ -1114,7 +1115,7 @@ protected function setUpEntityStorage() { ->method('getBaseFieldDefinitions') ->will($this->returnValue($this->fieldDefinitions)); - $this->entityStorage = new SqlContentEntityStorage($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager); + $this->entityStorage = new SqlContentEntityStorage($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new StaticCache()); } /** @@ -1189,7 +1190,7 @@ public function testLoadMultipleNoPersistentCache() { ->method('set'); $entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage') - ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager]) + ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new StaticCache()]) ->setMethods(['getFromStorage', 'invokeStorageLoadHook']) ->getMock(); $entity_storage->method('invokeStorageLoadHook') @@ -1241,7 +1242,7 @@ public function testLoadMultiplePersistentCacheMiss() { ->with($key, $entity, CacheBackendInterface::CACHE_PERMANENT, [$this->entityTypeId . '_values', 'entity_field_info']); $entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage') - ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager]) + ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new StaticCache()]) ->setMethods(['getFromStorage', 'invokeStorageLoadHook']) ->getMock(); $entity_storage->method('invokeStorageLoadHook') @@ -1296,7 +1297,7 @@ public function testHasData() { ->method('getBaseFieldDefinitions') ->will($this->returnValue($this->fieldDefinitions)); - $this->entityStorage = new SqlContentEntityStorage($this->entityType, $database, $this->entityManager, $this->cache, $this->languageManager); + $this->entityStorage = new SqlContentEntityStorage($this->entityType, $database, $this->entityManager, $this->cache, $this->languageManager, new StaticCache()); $result = $this->entityStorage->hasData(); diff --git a/core/tests/Drupal/Tests/Core/Session/UserSessionTest.php b/core/tests/Drupal/Tests/Core/Session/UserSessionTest.php index 85fe1bb..a719e55 100644 --- a/core/tests/Drupal/Tests/Core/Session/UserSessionTest.php +++ b/core/tests/Drupal/Tests/Core/Session/UserSessionTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\Core\Session; +use Drupal\Core\Cache\StaticCache\StaticCache; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Session\UserSession; use Drupal\Tests\UnitTestCase; @@ -94,6 +95,7 @@ protected function setUp() { ])); $role_storage = $this->getMockBuilder('Drupal\user\RoleStorage') + ->setConstructorArgs(['role', new StaticCache()]) ->disableOriginalConstructor() ->setMethods(['loadMultiple']) ->getMock();