diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index b30d9ec..d84a995 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -117,13 +117,9 @@ public function loadMultiple(array $ids = NULL) { // database when all requested entities are loaded from cache. $passed_ids = !empty($ids) ? array_flip($ids) : FALSE; // Try to load entities from the static cache, if the entity type supports - // static caching. + // static caching. This will remove ID's that were loaded from $ids. if ($this->cache && $ids) { $entities += $this->cacheGet($ids); - // If any entities were loaded, remove them from the ids still to load. - if ($passed_ids) { - $ids = array_keys(array_diff_key($passed_ids, $entities)); - } } // Load any remaining entities from the database. This is the case if $ids diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php index f303f79..ef99943 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php @@ -109,17 +109,24 @@ public function resetCache(array $ids = NULL) { /** * Gets entities from the static cache. * - * @param $ids - * If not empty, return entities that match these IDs. + * @param array &$ids + * If not empty, return entities that match these IDs. ID's that were found + * will be removed from the list. * - * @return + * @return \Drupal\Core\Entity\EntityInterface[] * Array of entities from the entity cache. */ - protected function cacheGet($ids) { + protected function cacheGet(&$ids) { $entities = array(); // Load any available entities from the internal cache. if ($this->cache && !empty($this->entityCache)) { - $entities += array_intersect_key($this->entityCache, array_flip($ids)); + foreach ($ids as $index => $id) { + if (isset($this->entityCache[$id])) { + $entities[$id] = $this->entityCache[$id]; + // Remove the ID from the list + unset($ids[$index]); + } + } } return $entities; } @@ -127,7 +134,7 @@ protected function cacheGet($ids) { /** * Stores entities in the static entity cache. * - * @param $entities + * @param \Drupal\Core\Entity\EntityInterface[] $entities * Entities to store in the cache. */ protected function cacheSet($entities) { diff --git a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php index e921796..ebb794b 100644 --- a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php @@ -7,11 +7,11 @@ namespace Drupal\Core\Entity; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\Query\QueryInterface; +use Drupal\Core\Field\PrepareCacheInterface; use Drupal\Core\Language\Language; -use Drupal\Component\Utility\NestedArray; -use Drupal\Component\Uuid\Uuid; use Drupal\field\FieldInfo; use Drupal\field\FieldUpdateForbiddenException; use Drupal\field\FieldInterface; @@ -83,6 +83,13 @@ class FieldableDatabaseStorageController extends FieldableEntityStorageControlle protected $fieldInfo; /** + * Cache backend. + * + * @var \Drupal\Core\Cache\CacheBackendInterface + */ + protected $cacheBackend; + + /** * The entity bundle key. * * @var string|bool @@ -104,7 +111,8 @@ public static function createInstance(ContainerInterface $container, $entity_typ $entity_type, $entity_info, $container->get('database'), - $container->get('field.info') + $container->get('field.info'), + $container->get('cache.entity') ); } @@ -119,12 +127,15 @@ public static function createInstance(ContainerInterface $container, $entity_typ * The database connection to be used. * @param \Drupal\field\FieldInfo $field_info * The field info service. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. */ - public function __construct($entity_type, array $entity_info, Connection $database, FieldInfo $field_info) { + public function __construct($entity_type, array $entity_info, Connection $database, FieldInfo $field_info, CacheBackendInterface $cache) { parent::__construct($entity_type, $entity_info); $this->database = $database; $this->fieldInfo = $field_info; + $this->cacheBackend = $cache; $this->bundleKey = !empty($this->entityInfo['entity_keys']['bundle']) ? $this->entityInfo['entity_keys']['bundle'] : FALSE; $this->entityClass = $this->entityInfo['class']; @@ -210,14 +221,10 @@ public function loadMultiple(array $ids = NULL) { // and we need to know if it's empty for this reason to avoid querying the // database when all requested entities are loaded from cache. $passed_ids = !empty($ids) ? array_flip($ids) : FALSE; - // Try to load entities from the static cache, if the entity type supports - // static caching. + // Try to load entities from the cache, if the entity type supports + // static caching. This will remove ID's that were loaded from $ids. if ($this->cache && $ids) { $entities += $this->cacheGet($ids); - // If any entities were loaded, remove them from the ids still to load. - if ($passed_ids) { - $ids = array_keys(array_diff_key($passed_ids, $entities)); - } } // Load any remaining entities from the database. This is the case if $ids @@ -556,18 +563,7 @@ protected function attachLoad(&$queried_entities, $load_revision = FALSE) { $this->loadFieldItems($queried_entities, $load_revision ? static::FIELD_LOAD_REVISION : static::FIELD_LOAD_CURRENT); } - // Call hook_entity_load(). - foreach (\Drupal::moduleHandler()->getImplementations('entity_load') as $module) { - $function = $module . '_entity_load'; - $function($queried_entities, $this->entityType); - } - // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are - // always the queried entities, followed by additional arguments set in - // $this->hookLoadArguments. - $args = array_merge(array($queried_entities), $this->hookLoadArguments); - foreach (\Drupal::moduleHandler()->getImplementations($this->entityType . '_load') as $module) { - call_user_func_array($module . '_' . $this->entityType . '_load', $args); - } + $this->invokeLoadHook($queried_entities); } /** @@ -669,12 +665,12 @@ public function save(EntityInterface $entity) { if ($this->revisionDataTable) { $this->savePropertyData($entity, 'revision_data_table'); } - $this->resetCache(array($entity->id())); $entity->setNewRevision(FALSE); $entity->postSave($this, TRUE); $this->invokeFieldMethod('update', $entity); $this->saveFieldItems($entity, TRUE); $this->invokeHook('update', $entity); + $this->resetCache(array($entity->id())); if ($this->dataTable) { $this->invokeTranslationHooks($entity); } @@ -876,9 +872,23 @@ public function getQueryServiceName() { } /** - * {@inheritdoc} + * Loads values of configurable fields for a group of entities. + * + * Loads all fields for each entity object in a group of a single entity type. + * The loaded field values are added directly to the entity objects. + * + * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities + * An array of entities keyed by entity ID. + * @param int $age + * EntityStorageControllerInterface::FIELD_LOAD_CURRENT to load the most + * recent revision for all fields, or + * EntityStorageControllerInterface::FIELD_LOAD_REVISION to load the version + * indicated by each entity. */ - protected function doLoadFieldItems($entities, $age) { + protected function loadFieldItems(array $entities, $age) { + if (empty($entities) || empty($this->entityInfo['fieldable'])) { + return; + } $load_current = $age == static::FIELD_LOAD_CURRENT; // Collect entities ids, bundles and languages. @@ -944,9 +954,14 @@ protected function doLoadFieldItems($entities, $age) { } /** - * {@inheritdoc} + * Saves values of configurable fields for an entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. + * @param bool $update + * TRUE if the entity is being updated, FALSE if it is being inserted. */ - protected function doSaveFieldItems(EntityInterface $entity, $update) { + protected function saveFieldItems(EntityInterface $entity, $update = TRUE) { $vid = $entity->getRevisionId(); $id = $entity->id(); $bundle = $entity->bundle(); @@ -1029,9 +1044,12 @@ protected function doSaveFieldItems(EntityInterface $entity, $update) { } /** - * {@inheritdoc} + * Deletes values of configurable fields for all revisions of an entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. */ - protected function doDeleteFieldItems(EntityInterface $entity) { + protected function deleteFieldItems(EntityInterface $entity) { foreach ($this->fieldInfo->getBundleInstances($entity->entityType(), $entity->bundle()) as $instance) { $field = $instance->getField(); $table_name = static::_fieldTableName($field); @@ -1046,9 +1064,12 @@ protected function doDeleteFieldItems(EntityInterface $entity) { } /** - * {@inheritdoc} + * Deletes values of configurable fields for a single revision of an entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. It must have a revision ID. */ - protected function doDeleteFieldItemsRevision(EntityInterface $entity) { + protected function deleteFieldItemsRevision(EntityInterface $entity) { $vid = $entity->getRevisionId(); if (isset($vid)) { foreach ($this->fieldInfo->getBundleInstances($entity->entityType(), $entity->bundle()) as $instance) { @@ -1524,4 +1545,158 @@ static public function _fieldColumnName(FieldInterface $field, $column) { return in_array($column, Field::getReservedColumns()) ? $column : $field->getFieldName() . '_' . $column; } + /** + * {@inheritdoc} + */ + protected function cacheGet(&$ids) { + // The parent implementation provides the static cache, so first attempt + // to load entities from it. + $entities = parent::cacheGet($ids); + // @todo: Rename field_cache? + if ($this->entityInfo['field_cache'] && $ids) { + // Now attempt to load the remaining ID's (those that were loaded have been + // removed from $ids already) from the persistent cache. + // Build the list of cache entries to retrieve. + $cids = array(); + foreach ($ids as $id) { + $cids[] = $this->buildCacheId($id); + } + if ($cache = $this->cacheBackend->getMultiple($cids)) { + // Create entity objects based on the loaded values from the cache. + // Put them in a temporary variable so that we can call the load hooks + // for those. + // @todo: Avoid this, which requires that all cached data must + // be in defined fields or find a way to extract non-defined values + // too. + $entities_from_cache = array(); + foreach ($ids as $index => $id) { + $cid = $this->buildCacheId($id); + if (isset($cache[$cid])) { + $values = $cache[$cid]->data['values']; + $translations = $cache[$cid]->data['translations']; + $bundle = $this->bundleKey ? $cache[$cid]->data['bundle'] : FALSE; + $entities_from_cache[$id] = new $this->entityClass($values, $this->entityType, $bundle, $translations); + // Already put the loaded entity into the cache. This will prevent + // cacheSet() from unnecessarily writing them back into the cache + // backend. Do not call that method directly to prevent that. + $this->entityCache[$id] = $entities_from_cache[$id]; + unset($ids[$index]); + } + } + + // Call the load hooks on the entities loaded from the persistent cache. + if (!empty($entities_from_cache)) { + $this->invokeLoadHook($entities_from_cache); + foreach ($entities_from_cache as $id => $entity) { + $entities[$id] = $entity; + } + } + } + } + return $entities; + } + + /** + * {@inheritdoc} + */ + protected function cacheSet($entities) { + if ($this->entityInfo['field_cache']) { + foreach ($entities as $id => $entity) { + // Skip entities that are already in the static cache. + if (isset($this->entityCache[$id])) { + continue; + } + + $data = array( + 'id' => $entity->id(), + 'bundle' => $entity->bundle(), + 'translations' => array_keys($entity->getTranslationLanguages()), + 'values' => array(), + ); + $default_langcode = $entity->getUntranslated()->language()->id; + foreach ($entity->getTranslationLanguages() as $langcode => $language) { + $translation = $entity->getTranslation($langcode); + + // Make sure the default language is valid. + if ($default_langcode == $langcode) { + $langcode = Language::LANGCODE_DEFAULT; + } + + foreach ($translation as $field_name => $items) { + if (!$items->isEmpty()) { + foreach ($items as $delta => $item) { + // If the field item needs to be prepare the cache data, call + // the corresponding method, otherwise use the values as cache + // data. + if ($item instanceof PrepareCacheInterface) { + $data['values'][$field_name][$langcode][$delta] = $item->getCacheData(); + } + else { + $data['values'][$field_name][$langcode][$delta] = $item->getValue(); + } + } + } + } + } + $this->cacheBackend->set($this->buildCacheId($id), $data, CacheBackendInterface::CACHE_PERMANENT, array($this->entityType . '_values' => TRUE)); + } + } + parent::cacheSet($entities); + } + + /** + * {@inheritdoc} + */ + public function resetCache(array $ids = NULL) { + parent::resetCache($ids); + if ($this->entityInfo['field_cache']) { + if ($ids) { + $cids = array(); + foreach ($ids as $id) { + $cids[] = $this->buildCacheId($id); + } + $this->cacheBackend->deleteMultiple($cids); + } + else { + $this->cacheBackend->deleteTags(array($this->entityType . '_values' => TRUE)); + } + } + } + + /** + * Invokes the entity load hooks on the given entities. + * + * @param array $entities + * List of entities to invoke the hook for. + */ + protected function invokeLoadHook($entities) { + // Call hook_entity_load(). + foreach (\Drupal::moduleHandler() + ->getImplementations('entity_load') as $module) { + $function = $module . '_entity_load'; + $function($entities, $this->entityType); + } + // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are + // always the queried entities, followed by additional arguments set in + // $this->hookLoadArguments. + $args = array_merge(array($entities), $this->hookLoadArguments); + foreach (\Drupal::moduleHandler() + ->getImplementations($this->entityType . '_load') as $module) { + call_user_func_array($module . '_' . $this->entityType . '_load', $args); + } + } + + /** + * Returns the cache ID for the passed in entity ID. + * + * @param int $id + * Entity ID for which the cache ID should be built. + * + * @return string + * Cache ID that can be passed to the cache backend. + */ + protected function buildCacheId($id) { + return "values:{$this->entityType}:$id"; + } + } diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php index ec909d9..b1bf430 100644 --- a/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php +++ b/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php @@ -7,213 +7,12 @@ namespace Drupal\Core\Entity; -use Drupal\Core\Field\PrepareCacheInterface; use Drupal\field\FieldInterface; use Drupal\field\FieldInstanceInterface; -use Drupal\Core\Field\ConfigFieldItemListInterface; -use Symfony\Component\DependencyInjection\Container; abstract class FieldableEntityStorageControllerBase extends EntityStorageControllerBase implements FieldableEntityStorageControllerInterface { /** - * Loads values of configurable fields for a group of entities. - * - * Loads all fields for each entity object in a group of a single entity type. - * The loaded field values are added directly to the entity objects. - * - * This method is a wrapper that handles the field data cache. Subclasses - * need to implement the doLoadFieldItems() method with the actual storage - * logic. - * - * @param array $entities - * An array of entities keyed by entity ID. - * @param int $age - * EntityStorageControllerInterface::FIELD_LOAD_CURRENT to load the most - * recent revision for all fields, or - * EntityStorageControllerInterface::FIELD_LOAD_REVISION to load the version - * indicated by each entity. - */ - protected function loadFieldItems(array $entities, $age) { - if (empty($entities)) { - return; - } - - // Only the most current revision of non-deleted fields for cacheable entity - // types can be cached. - $load_current = $age == static::FIELD_LOAD_CURRENT; - $info = entity_get_info($this->entityType); - $use_cache = $load_current && $info['field_cache']; - - // Assume all entities will need to be queried. Entities found in the cache - // will be removed from the list. - $queried_entities = $entities; - - // Fetch available entities from cache, if applicable. - if ($use_cache) { - // Build the list of cache entries to retrieve. - $cids = array(); - foreach ($entities as $id => $entity) { - $cids[] = "field:{$this->entityType}:$id"; - } - $cache = cache('field')->getMultiple($cids); - // Put the cached field values back into the entities and remove them from - // the list of entities to query. - foreach ($entities as $id => $entity) { - $cid = "field:{$this->entityType}:$id"; - if (isset($cache[$cid])) { - unset($queried_entities[$id]); - foreach ($cache[$cid]->data as $langcode => $values) { - $translation = $entity->getTranslation($langcode); - // We do not need to worry about field translatability here, the - // translation object will manage that automatically. - foreach ($values as $field_name => $items) { - $translation->$field_name = $items; - } - } - } - } - } - - // Fetch other entities from their storage location. - if ($queried_entities) { - // Let the storage controller actually load the values. - $this->doLoadFieldItems($queried_entities, $age); - - // Build cache data. - // @todo: Improve this logic to avoid instantiating field objects once - // the field logic is improved to not do that anyway. - if ($use_cache) { - foreach ($queried_entities as $id => $entity) { - $data = array(); - foreach ($entity->getTranslationLanguages() as $langcode => $language) { - $translation = $entity->getTranslation($langcode); - foreach ($translation as $field_name => $items) { - if ($items instanceof ConfigFieldItemListInterface && !$items->isEmpty()) { - foreach ($items as $delta => $item) { - // If the field item needs to prepare the cache data, call the - // corresponding method, otherwise use the values as cache - // data. - if ($item instanceof PrepareCacheInterface) { - $data[$langcode][$field_name][$delta] = $item->getCacheData(); - } - else { - $data[$langcode][$field_name][$delta] = $item->getValue(); - } - } - } - } - } - $cid = "field:{$this->entityType}:$id"; - cache('field')->set($cid, $data); - } - } - } - } - - /** - * Saves values of configurable fields for an entity. - * - * This method is a wrapper that handles the field data cache. Subclasses - * need to implement the doSaveFieldItems() method with the actual storage - * logic. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. - * @param bool $update - * TRUE if the entity is being updated, FALSE if it is being inserted. - */ - protected function saveFieldItems(EntityInterface $entity, $update = TRUE) { - $this->doSaveFieldItems($entity, $update); - - if ($update) { - $entity_info = $entity->entityInfo(); - if ($entity_info['field_cache']) { - cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id()); - } - } - } - - /** - * Deletes values of configurable fields for all revisions of an entity. - * - * This method is a wrapper that handles the field data cache. Subclasses - * need to implement the doDeleteFieldItems() method with the actual storage - * logic. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. - */ - protected function deleteFieldItems(EntityInterface $entity) { - $this->doDeleteFieldItems($entity); - - $entity_info = $entity->entityInfo(); - if ($entity_info['field_cache']) { - cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id()); - } - } - - /** - * Deletes values of configurable fields for a single revision of an entity. - * - * This method is a wrapper that handles the field data cache. Subclasses - * need to implement the doDeleteFieldItemsRevision() method with the actual - * storage logic. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. It must have a revision ID attribute. - */ - protected function deleteFieldItemsRevision(EntityInterface $entity) { - $this->doDeleteFieldItemsRevision($entity); - } - - /** - * Loads values of configurable fields for a group of entities. - * - * This is the method that holds the actual storage logic. - * - * @param array $entities - * An array of entities keyed by entity ID. - * @param int $age - * EntityStorageControllerInterface::FIELD_LOAD_CURRENT to load the most - * recent revision for all fields, or - * EntityStorageControllerInterface::FIELD_LOAD_REVISION to load the version - * indicated by each entity. - */ - abstract protected function doLoadFieldItems($entities, $age); - - /** - * Saves values of configurable fields for an entity. - * - * This is the method that holds the actual storage logic. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. - * @param bool $update - * TRUE if the entity is being updated, FALSE if it is being inserted. - */ - abstract protected function doSaveFieldItems(EntityInterface $entity, $update); - - /** - * Deletes values of configurable fields for all revisions of an entity. - * - * This is the method that holds the actual storage logic. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. - */ - abstract protected function doDeleteFieldItems(EntityInterface $entity); - - /** - * Deletes values of configurable fields for a single revision of an entity. - * - * This is the method that holds the actual storage logic. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. - */ - abstract protected function doDeleteFieldItemsRevision(EntityInterface $entity); - - /** * {@inheritdoc} */ public function onFieldCreate(FieldInterface $field) { } diff --git a/core/lib/Drupal/Core/Field/ConfigEntityReferenceItemBase.php b/core/lib/Drupal/Core/Field/ConfigEntityReferenceItemBase.php index b4c6720..2934917 100644 --- a/core/lib/Drupal/Core/Field/ConfigEntityReferenceItemBase.php +++ b/core/lib/Drupal/Core/Field/ConfigEntityReferenceItemBase.php @@ -94,7 +94,7 @@ public static function schema(FieldInterface $field) { public function isEmpty() { // Avoid loading the entity by first checking the 'target_id'. $target_id = $this->target_id; - if (!empty($target_id)) { + if ($target_id !== NULL && $target_id !== '') { return FALSE; } // Allow auto-create entities. diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php index 5ebec96..bf4c27c 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php @@ -19,21 +19,19 @@ class CustomBlockStorageController extends FieldableDatabaseStorageController { /** - * Overrides \Drupal\Core\Entity\DatabaseStorageController::attachLoad(). + * {@ineheritdoc} */ - protected function attachLoad(&$blocks, $load_revision = FALSE) { - // Create an array of block types for passing as a load argument. - // Note that blocks at this point are still \StdClass objects returned from - // the database. - foreach ($blocks as $entity) { - $types[$entity->type] = $entity->type; + protected function invokeLoadHook($entities) { + // Besides the list of custom blocks, pass one additional argument to + // hook_custom_block_load(), containing a list of block types that were loaded. + $typed_nodes = array(); + foreach ($entities as $id => $node) { + $typed_nodes[$node->bundle()][$id] = $entities[$id]; } + $argument = array_keys($typed_nodes); + $this->hookLoadArguments = array($argument); - // Besides the list of blocks, pass one additional argument to - // hook_custom_block_load(), containing a list of block types that were - // loaded. - $this->hookLoadArguments = array($types); - parent::attachLoad($blocks, $load_revision); + parent::invokeLoadHook($entities); } } diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php index 23e7fd1..04d3191 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php @@ -269,15 +269,15 @@ function testFieldAttachCache() { $entity = clone($entity_init); $entity->{$this->field_name_2} = $values; $entity->save(); - $cid = "field:$entity_type:" . $entity->id(); + $cid = "values:$entity_type:" . $entity->id(); $this->assertFalse(cache('field')->get($cid), 'Cached: no cache entry on insert'); // Load, and check that a cache entry is present with the expected values. $controller = $this->container->get('entity.manager')->getStorageController($entity->entityType()); $controller->resetCache(); $controller->load($entity->id()); - $cache = cache('field')->get($cid); - $this->assertEqual($cache->data[$langcode][$this->field_name_2], $values, 'Cached: correct cache entry on load'); + $cache = cache('entity')->get($cid); + $this->assertEqual($cache->data['values'][$this->field_name_2][$langcode], $values, 'Cached: correct cache entry on load'); // Update with different values, and check that the cache entry is wiped. $values = $this->_generateTestFieldValues($this->field_2->getFieldCardinality()); @@ -287,13 +287,13 @@ function testFieldAttachCache() { )); $entity->{$this->field_name_2} = $values; $entity->save(); - $this->assertFalse(cache('field')->get($cid), 'Cached: no cache entry on update'); + $this->assertFalse(cache('entity')->get($cid), 'Cached: no cache entry on update'); // Load, and check that a cache entry is present with the expected values. $controller->resetCache(); $controller->load($entity->id()); - $cache = cache('field')->get($cid); - $this->assertEqual($cache->data[$langcode][$this->field_name_2], $values, 'Cached: correct cache entry on load'); + $cache = cache('entity')->get($cid); + $this->assertEqual($cache->data['values'][$this->field_name_2][$langcode], $values, 'Cached: correct cache entry on load'); // Create a new revision, and check that the cache entry is wiped. $entity = entity_create($entity_type, array( @@ -309,12 +309,12 @@ function testFieldAttachCache() { // Load, and check that a cache entry is present with the expected values. $controller->resetCache(); $controller->load($entity->id()); - $cache = cache('field')->get($cid); - $this->assertEqual($cache->data[$langcode][$this->field_name_2], $values, 'Cached: correct cache entry on load'); + $cache = cache('entity')->get($cid); + $this->assertEqual($cache->data['values'][$this->field_name_2][$langcode], $values, 'Cached: correct cache entry on load'); // Delete, and check that the cache entry is wiped. $entity->delete(); - $this->assertFalse(cache('field')->get($cid), 'Cached: no cache entry after delete'); + $this->assertFalse(cache('entity')->get($cid), 'Cached: no cache entry after delete'); } /** diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterSecurityTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterSecurityTest.php index d24042a..9751712 100644 --- a/core/modules/filter/lib/Drupal/filter/Tests/FilterSecurityTest.php +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterSecurityTest.php @@ -82,6 +82,7 @@ function testDisableFilterModule() { 'filters[filter_test_replace][status]' => 1, ); $this->drupalPostForm('admin/config/content/formats/manage/' . $format_id, $edit, t('Save configuration')); + \Drupal::entityManager()->getStorageController('node')->resetCache(array($node->id())); // Verify that filter_test_replace filter replaced the content. $this->drupalGet('node/' . $node->id()); diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php index 0c3a623..ff2a4d8 100644 --- a/core/modules/node/lib/Drupal/node/NodeStorageController.php +++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php @@ -30,46 +30,22 @@ public function create(array $values) { } /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad(). + * {@ineheritdoc} */ - protected function attachLoad(&$queried_entities, $load_revision = FALSE) { - $queried_entities = $this->mapFromStorageRecords($queried_entities, $load_revision); - - // Create an array of nodes for each content type and pass this to the - // object type specific callback. To preserve backward-compatibility we - // pass on BC decorators to node-specific hooks, while we pass on the - // regular entity objects else. - $typed_nodes = array(); - foreach ($queried_entities as $id => $node) { - $typed_nodes[$node->bundle()][$id] = $queried_entities[$id]; - } - - if ($load_revision) { - $this->loadFieldItems($queried_entities, static::FIELD_LOAD_REVISION); - } - else { - $this->loadFieldItems($queried_entities, static::FIELD_LOAD_CURRENT); - } - + protected function invokeLoadHook($entities) { // Besides the list of nodes, pass one additional argument to // hook_node_load(), containing a list of node types that were loaded. + $typed_nodes = array(); + foreach ($entities as $id => $node) { + $typed_nodes[$node->bundle()][$id] = $entities[$id]; + } $argument = array_keys($typed_nodes); $this->hookLoadArguments = array($argument); - // Call hook_entity_load(). - foreach (\Drupal::moduleHandler()->getImplementations('entity_load') as $module) { - $function = $module . '_entity_load'; - $function($queried_entities, $this->entityType); - } - // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are - // always the queried entities, followed by additional arguments set in - // $this->hookLoadArguments. - $args = array_merge(array($queried_entities), $this->hookLoadArguments); - foreach (\Drupal::moduleHandler()->getImplementations($this->entityType . '_load') as $module) { - call_user_func_array($module . '_' . $this->entityType . '_load', $args); - } + parent::invokeLoadHook($entities); } + /** * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete(). */ diff --git a/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php b/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php index d581147..b9ed58f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php @@ -61,11 +61,7 @@ function testUpdateAccess() { $this->assertResponse(200); // Access the update page as user 1. - $user1 = user_load(1); - $user1->pass_raw = user_password(); - $user1->pass = $this->container->get('password')->hash(trim($user1->pass_raw)); - db_query("UPDATE {users} SET pass = :pass WHERE uid = :uid", array(':pass' => $user1->getPassword(), ':uid' => $user1->id())); - $this->drupalLogin($user1); + $this->drupalLogin($this->root_user); $this->drupalGet($this->update_url, array('external' => TRUE)); $this->assertResponse(200); } diff --git a/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php b/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php index 49d9f78..43e26f8 100644 --- a/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php +++ b/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php @@ -126,38 +126,36 @@ function testProcessedCache() { // Load the entity and check that the field cache contains the expected // data. $entity = entity_load($entity_type, $entity->id()); - $cache = cache('field')->get("field:$entity_type:" . $entity->id()); - $this->assertEqual($cache->data, array( - Language::LANGCODE_NOT_SPECIFIED => array( - 'summary_field' => array( - 0 => array( - 'value' => $value, - 'summary' => $summary, - 'format' => 'plain_text', - 'processed' => $value, - 'summary_processed' => $summary, - ), + $cache = cache('entity')->get("values:$entity_type:" . $entity->id()); + $this->assertEqual($cache->data['values']['summary_field'], array( + Language::LANGCODE_DEFAULT => array( + 0 => array( + 'value' => $value, + 'summary' => $summary, + 'format' => 'plain_text', + 'processed' => $value, + 'summary_processed' => $summary, ), ), )); // Inject fake processed values into the cache to make sure that these are // used as-is and not re-calculated when the entity is loaded. - $data = array( - Language::LANGCODE_NOT_SPECIFIED => array( - 'summary_field' => array( - 0 => array( - 'value' => $value, - 'summary' => $summary, - 'format' => 'plain_text', - 'processed' => 'Cached processed value', - 'summary_processed' => 'Cached summary processed value', - ), + $data = $cache->data; + $data['values']['summary_field'] = array( + Language::LANGCODE_DEFAULT => array( + 0 => array( + 'value' => $value, + 'summary' => $summary, + 'format' => 'plain_text', + 'processed' => 'Cached processed value', + 'summary_processed' => 'Cached summary processed value', ), ), ); - cache('field')->set("field:$entity_type:" . $entity->id(), $data); - $entity = entity_load($entity_type, $entity->id(), TRUE); + \Drupal::entityManager()->getStorageController($entity_type)->resetCache(); + cache('entity')->set("values:$entity_type:" . $entity->id(), $data); + $entity = entity_load($entity_type, $entity->id()); $this->assertEqual($entity->summary_field->processed, 'Cached processed value'); $this->assertEqual($entity->summary_field->summary_processed, 'Cached summary processed value'); diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php index 48e8206..53d8e48 100644 --- a/core/modules/user/lib/Drupal/user/UserStorageController.php +++ b/core/modules/user/lib/Drupal/user/UserStorageController.php @@ -8,6 +8,7 @@ namespace Drupal\user; use Drupal\Component\Uuid\UuidInterface; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Password\PasswordInterface; use Drupal\Core\Database\Connection; @@ -49,15 +50,15 @@ class UserStorageController extends FieldableDatabaseStorageController implement * The database connection to be used. * @param \Drupal\field\FieldInfo $field_info * The field info service. - * @param \Drupal\Component\Uuid\UuidInterface $uuid_service - * The UUID Service. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. * @param \Drupal\Core\Password\PasswordInterface $password * The password hashing service. * @param \Drupal\user\UserDataInterface $user_data * The user data service. */ - public function __construct($entity_type, $entity_info, Connection $database, FieldInfo $field_info, UuidInterface $uuid_service, PasswordInterface $password, UserDataInterface $user_data) { - parent::__construct($entity_type, $entity_info, $database, $field_info, $uuid_service); + public function __construct($entity_type, $entity_info, Connection $database, FieldInfo $field_info, CacheBackendInterface $cache, PasswordInterface $password, UserDataInterface $user_data) { + parent::__construct($entity_type, $entity_info, $database, $field_info, $cache); $this->password = $password; $this->userData = $user_data; @@ -72,7 +73,7 @@ public static function createInstance(ContainerInterface $container, $entity_typ $entity_info, $container->get('database'), $container->get('field.info'), - $container->get('uuid'), + $container->get('cache.entity'), $container->get('password'), $container->get('user.data') ); diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 91e18d5..b2be351 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -1004,6 +1004,7 @@ function user_login_finalize(UserInterface $account) { ->fields(array('login' => $user->getLastLoginTime())) ->condition('uid', $user->id()) ->execute(); + \Drupal::entityManager()->getStorageController('user')->resetCache(array($account->id())); // Regenerate the session ID to prevent against session fixation attacks. // This is called before hook_user in case one of those functions fails