diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index 809f186..a16b1d2 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -143,18 +143,7 @@ public function loadMultiple(array $ids = NULL) { $entities += $queried_entities; } - // Ensure that the returned array is ordered the same as the original - // $ids array if this was passed in and remove any invalid ids. - if ($passed_ids) { - // Remove any invalid ids from the array. - $passed_ids = array_intersect_key($passed_ids, $entities); - foreach ($entities as $entity) { - $passed_ids[$entity->{$this->idKey}] = $entity; - } - $entities = $passed_ids; - } - - return $entities; + return $this->ensureOrder($passed_ids, $entities); } /** diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index bfbdfb4..b918425 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -145,18 +145,7 @@ public function loadMultiple(array $ids = NULL) { } } - // Ensure that the returned array is ordered the same as the original - // $ids array if this was passed in and remove any invalid ids. - if ($passed_ids) { - // Remove any invalid ids from the array. - $passed_ids = array_intersect_key($passed_ids, $entities); - foreach ($entities as $entity) { - $passed_ids[$entity->id()] = $entity; - } - $entities = $passed_ids; - } - - return $entities; + return $this->ensureOrder($passed_ids, $entities); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php index 7db28fc..04a4fec 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php @@ -162,17 +162,19 @@ protected function invokeHook($hook, EntityInterface $entity) { * Associative array of query results, keyed on the entity ID. */ protected function postLoad(array &$queried_entities) { - $entity_class = $this->entityInfo->getClass(); - $entity_class::postLoad($this, $queried_entities); - // Call hook_entity_load(). - foreach ($this->moduleHandler()->getImplementations('entity_load') as $module) { - $function = $module . '_entity_load'; - $function($queried_entities, $this->entityType); - } - // Call hook_TYPE_load(). - foreach ($this->moduleHandler()->getImplementations($this->entityType . '_load') as $module) { - $function = $module . '_' . $this->entityType . '_load'; - $function($queried_entities); + if (!empty($queried_entities)) { + $entity_class = $this->entityInfo->getClass(); + $entity_class::postLoad($this, $queried_entities); + // Call hook_entity_load(). + foreach ($this->moduleHandler()->getImplementations('entity_load') as $module) { + $function = $module . '_entity_load'; + $function($queried_entities, $this->entityType); + } + // Call hook_TYPE_load(). + foreach ($this->moduleHandler()->getImplementations($this->entityType . '_load') as $module) { + $function = $module . '_' . $this->entityType . '_load'; + $function($queried_entities); + } } } @@ -202,4 +204,29 @@ public function loadByProperties(array $values = array()) { return $result ? $this->loadMultiple($result) : array(); } + /** + * Ensures that entities are returned in the requested order. + * + * @param array|false $ordered_ids + * Array with the originally requested IDs or FALSE. + * @param \Drupal\Core\Entity\EntityInterface $unordered_entities + * List of entities for the requested IDs in unknown order. + * + * @return \Drupal\Core\Entity\EntityInterface + * Array with the correctly ordered entities. + */ + protected function ensureOrder($ordered_ids, $unordered_entities) { + // Ensure that the returned array is ordered the same as the original + // $ids array if this was passed in and remove any invalid ids. + if ($ordered_ids) { + // Remove any invalid ids from the array. + $ordered_entities = array_intersect_key($ordered_ids, $unordered_entities); + foreach ($unordered_entities as $entity) { + $ordered_entities[$entity->id()] = $entity; + } + return $ordered_entities; + } + return $unordered_entities; + } + } diff --git a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php index 282987d..828f58d 100644 --- a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php @@ -7,12 +7,12 @@ namespace Drupal\Core\Entity; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityTypeInterface; 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; @@ -67,7 +67,23 @@ class FieldableDatabaseStorageController extends FieldableEntityStorageControlle * * @var boolean */ - protected $cache; + protected $staticCache; + + /** + * Whether this entity type should use the persistent cache. + * + * Set by entity info. + * + * @var boolean + */ + protected $persistentCache; + + /** + * Holds statically cached entities. + * + * @var array + */ + protected $entities = array(); /** * Active database connection. @@ -84,13 +100,35 @@ class FieldableDatabaseStorageController extends FieldableEntityStorageControlle protected $fieldInfo; /** + * Cache backend. + * + * @var \Drupal\Core\Cache\CacheBackendInterface + */ + protected $cacheBackend; + + /** + * The entity bundle key. + * + * @var string|bool + */ + protected $bundleKey = FALSE; + + /** + * Name of the entity class. + * + * @var string + */ + protected $entityClass; + + /** * {@inheritdoc} */ public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) { return new static( $entity_info, $container->get('database'), - $container->get('field.info') + $container->get('field.info'), + $container->get('cache.entity') ); } @@ -103,12 +141,19 @@ public static function createInstance(ContainerInterface $container, EntityTypeI * 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(EntityTypeInterface $entity_info, Connection $database, FieldInfo $field_info) { + public function __construct(EntityTypeInterface $entity_info, Connection $database, FieldInfo $field_info, CacheBackendInterface $cache) { parent::__construct($entity_info); $this->database = $database; $this->fieldInfo = $field_info; + $this->cacheBackend = $cache; + + // Check if the entity type supports caching of loaded entities. + $this->staticCache = $this->entityInfo->isStaticallyCacheable(); + $this->persistentCache = $this->entityInfo->isFieldDataCacheable(); // Check if the entity type supports IDs. if ($this->entityInfo->hasKey('id')) { @@ -139,60 +184,244 @@ public function __construct(EntityTypeInterface $entity_info, Connection $databa * {@inheritdoc} */ public function loadMultiple(array $ids = NULL) { - $entities = array(); - // Create a new variable which is either a prepared version of the $ids // array for later comparison with the entity cache, or FALSE if no $ids // were passed. The $ids array is reduced as items are loaded from cache, // 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. - 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)); - } + // static caching. This will remove ID's that were loaded from $ids. + $entities_from_static_cache = $this->getFromStaticCache($ids); + + // Load remaining entities either from the persistent cache or storage. + $entities = $this->doLoadMultiple($ids); + + // Pass all entities loaded from the database through $this->postLoad(), + // to invoke the load hooks and postLoad() method on the entity class. + $this->postLoad($entities); + + $this->setStaticCache($entities); + + $entities += $entities_from_static_cache; + return $this->ensureOrder($passed_ids, $entities); + } + + /** + * Loads entities from persistent cache or storage. + * + * @param array $ids + * List of entity IDs to load or NULL to load all. + * + * @return \Drupal\Core\Entity\ContentEntityInterface[] + * Array of loaded entities. + */ + protected function doLoadMultiple(array $ids = NULL) { + // There is nothing to do if $ids is an empty array. + if ($ids === array()) { + return array(); } - // Load any remaining entities from the database. This is the case if $ids - // is set to NULL (so we load all entities) or if there are any ids left to - // load. + // Attempt to load entities from the persistent cache. This will remove ID's + // that were loaded from $ids. + $entities_from_cache = $this->getFromPersistentCache($ids); + + // Load any remaining entities from the database. + $entities_from_storage = $this->getFromStorage($ids); + + $this->setPersistentCache($entities_from_storage); + + return $entities_from_cache + $entities_from_storage; + } + + /** + * Gets entities from the storage. + * + * @param array|null $ids + * If not empty, return entities that match these IDs. Return all entities + * when NULL. + * + * @return \Drupal\Core\Entity\ContentEntityInterface[] + * Array of entities from the entity cache. + */ + protected function getFromStorage(array $ids = NULL) { + $entities = array(); + if ($ids === NULL || $ids) { // Build and execute the query. $query_result = $this->buildQuery($ids)->execute(); - $queried_entities = $query_result->fetchAllAssoc($this->idKey); + $records = $query_result->fetchAllAssoc($this->idKey); + + // Map the loaded records into entity objects and according fields. + if ($records) { + $entities = $this->mapFromStorageRecords($records); + + // Attach field values. + if ($this->entityInfo->isFieldable()) { + $this->loadFieldItems($entities); + } + } + } + + return $entities; + } + + /** + * Gets entities from the static cache. + * + * @param array &$ids + * If not empty, return entities that match these IDs. ID's that were found + * will be removed from the list. + * + * @return \Drupal\Core\Entity\ContentEntityInterface[] + * Array of entities from the entity cache. + */ + protected function getFromStaticCache(&$ids) { + $entities = array(); + + if ($this->staticCache && $ids) { + $entities = array(); + // Load any available entities from the internal cache. + if (!empty($this->entities)) { + foreach ($ids as $index => $id) { + if (isset($this->entities[$id])) { + $entities[$id] = $this->entities[$id]; + // Remove the ID from the list. + unset($ids[$index]); + } + } + } + } + return $entities; + } + + /** + * Stores entities in the static entity cache. + * + * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities + * Entities to store in the cache. + */ + protected function setStaticCache(array $entities) { + if ($this->staticCache) { + $this->entities += $entities; } + } - // Pass all entities loaded from the database through $this->postLoad(), - // which attaches fields (if supported by the entity type) and calls the - // entity type specific load callback, for example hook_node_load(). - if (!empty($queried_entities)) { - $this->postLoad($queried_entities); - $entities += $queried_entities; + /** + * Gets entities from the persistent cache. + * + * @param array &$ids + * If not empty, return entities that match these IDs. ID's that were found + * will be removed from the list. + * + * @return \Drupal\Core\Entity\ContentEntityInterface[] + * Array of entities from the entity cache. + */ + protected function getFromPersistentCache(&$ids) { + if (!$this->persistentCache || empty($ids)) { + return array(); + } + $entities = array(); + // 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. + 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[$id] = new $this->entityClass($values, $this->entityType, $bundle, $translations); + unset($ids[$index]); + } + } } + return $entities; + } - if ($this->cache) { - // Add entities to the cache. - if (!empty($queried_entities)) { - $this->cacheSet($queried_entities); + /** + * Stores entities in the persistent entity cache. + * + * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities + * Entities to store in the cache. + */ + protected function setPersistentCache($entities) { + if (!$this->persistentCache) { + return; + } + + foreach ($entities as $id => $entity) { + $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)); } + } - // Ensure that the returned array is ordered the same as the original - // $ids array if this was passed in and remove any invalid ids. - if ($passed_ids) { - // Remove any invalid ids from the array. - $passed_ids = array_intersect_key($passed_ids, $entities); - foreach ($entities as $entity) { - $passed_ids[$entity->id()] = $entity; + /** + * {@inheritdoc} + */ + public function resetCache(array $ids = NULL) { + if ($ids) { + $cids = array(); + foreach ($ids as $id) { + unset($this->entities[$id]); + $cids[] = $this->buildCacheId($id); + } + if ($this->persistentCache) { + $this->cacheBackend->deleteMultiple($cids); } - $entities = $passed_ids; } + else { + $this->entities = array(); + if ($this->persistentCache) { + $this->cacheBackend->deleteTags(array($this->entityType . '_values' => TRUE)); + } + } + } - return $entities; + /** + * 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"; } /** @@ -323,15 +552,20 @@ protected function attachPropertyData(array &$entities) { public function loadRevision($revision_id) { // Build and execute the query. $query_result = $this->buildQuery(array(), $revision_id)->execute(); - $queried_entities = $query_result->fetchAllAssoc($this->idKey); + if ($records = $query_result->fetchAllAssoc($this->idKey)) { + // Map the record to an entity. + $entities = $this->mapFromStorageRecords($records); - // Pass the loaded entities from the database through $this->postLoad(), - // which attaches fields (if supported by the entity type) and calls the - // entity type specific load callback, for example hook_node_load(). - if (!empty($queried_entities)) { - $this->postLoad($queried_entities); + // Attach field values. + if ($this->entityInfo->isFieldable()) { + $this->loadFieldItems($entities); + } + + // Pass all entities loaded from the database through $this->postLoad(), + // to invoke the load hooks and postLoad() method on the entity class. + $this->postLoad($entities); + return reset($entities); } - return reset($queried_entities); } /** @@ -441,32 +675,6 @@ protected function buildQuery($ids, $revision_id = FALSE) { } /** - * Attaches data to entities upon loading. - * - * This will attach fields, if the entity is fieldable. It calls - * hook_entity_load() for modules which need to add data to all entities. - * It also calls hook_TYPE_load() on the loaded entities. For example - * hook_node_load() or hook_user_load(). If your hook_TYPE_load() - * expects special parameters apart from the queried entities, you can set - * $this->hookLoadArguments prior to calling the method. - * See Drupal\node\NodeStorageController::attachLoad() for an example. - * - * @param $queried_entities - * Associative array of query results, keyed on the entity ID. - */ - protected function postLoad(array &$queried_entities) { - // Map the loaded records into entity objects and according fields. - $queried_entities = $this->mapFromStorageRecords($queried_entities); - - // Attach field values. - if ($this->entityInfo->isFieldable()) { - $this->loadFieldItems($queried_entities); - } - - parent::postLoad($queried_entities); - } - - /** * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::delete(). */ public function delete(array $entities) { @@ -775,9 +983,26 @@ 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. */ - protected function doLoadFieldItems($entities, $age) { + protected function loadFieldItems(array $entities) { + if (empty($entities) || !$this->entityInfo->isFieldable()) { + return; + } + + $age = static::FIELD_LOAD_CURRENT; + foreach ($entities as $entity) { + if (!$entity->isDefaultRevision()) { + $age = static::FIELD_LOAD_REVISION; + break; + } + } $load_current = $age == static::FIELD_LOAD_CURRENT; // Collect entities ids, bundles and languages. @@ -844,9 +1069,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(); @@ -929,9 +1159,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); @@ -946,9 +1179,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) { diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php index 0275597..5f844ad 100644 --- a/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php +++ b/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php @@ -8,10 +8,8 @@ namespace Drupal\Core\Entity; use Drupal\Component\Utility\String; -use Drupal\Core\Field\PrepareCacheInterface; use Drupal\field\FieldInterface; use Drupal\field\FieldInstanceInterface; -use Drupal\Core\Field\ConfigFieldItemListInterface; use Symfony\Component\DependencyInjection\ContainerInterface; abstract class FieldableEntityStorageControllerBase extends EntityStorageControllerBase implements FieldableEntityStorageControllerInterface { @@ -93,206 +91,6 @@ public function create(array $values) { } /** - * 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. - */ - protected function loadFieldItems(array $entities) { - if (empty($entities)) { - return; - } - - $age = static::FIELD_LOAD_CURRENT; - foreach ($entities as $entity) { - if (!$entity->isDefaultRevision()) { - $age = static::FIELD_LOAD_REVISION; - break; - } - } - - // Only the most current revision of non-deleted fields for cacheable entity - // types can be cached. - $load_current = $age == static::FIELD_LOAD_CURRENT; - $use_cache = $load_current && $this->entityInfo->isFieldDataCacheable(); - - // 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->isFieldDataCacheable()) { - 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->isFieldDataCacheable()) { - 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 4d8fbf9..a645e8c 100644 --- a/core/lib/Drupal/Core/Field/ConfigEntityReferenceItemBase.php +++ b/core/lib/Drupal/Core/Field/ConfigEntityReferenceItemBase.php @@ -23,7 +23,7 @@ class ConfigEntityReferenceItemBase extends EntityReferenceItem { 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/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php index 82cc6e4..4a1c4b7 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php @@ -34,12 +34,12 @@ protected function buildQuery($ids, $revision_id = FALSE) { /** * {@inheritdoc} */ - protected function postLoad(array &$queried_entities) { + protected function mapFromStorageRecords(array $records) { // Prepare standard comment fields. - foreach ($queried_entities as &$record) { + foreach ($records as $record) { $record->name = $record->uid ? $record->registered_name : $record->name; } - parent::postLoad($queried_entities); + return parent::mapFromStorageRecords($records); } /** diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php index ccd6cd1..e5e77ef 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php @@ -223,7 +223,7 @@ function testFieldAttachPrepareViewMultiple() { function testFieldAttachCache() { // Initialize random values and a test entity. $entity_init = entity_create('entity_test', array('type' => $this->instance->bundle)); - $langcode = Language::LANGCODE_NOT_SPECIFIED; + $langcode = Language::LANGCODE_DEFAULT; $values = $this->_generateTestFieldValues($this->field->getCardinality()); // Non-cacheable entity type. @@ -257,15 +257,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->getCardinality()); @@ -275,13 +275,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( @@ -297,12 +297,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/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 69cfdba..0d901e8 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\Entity\EntityTypeInterface; use Drupal\Core\Password\PasswordInterface; @@ -48,15 +49,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(EntityTypeInterface $entity_info, Connection $database, FieldInfo $field_info, UuidInterface $uuid_service, PasswordInterface $password, UserDataInterface $user_data) { - parent::__construct($entity_info, $database, $field_info, $uuid_service); + public function __construct(EntityTypeInterface $entity_info, Connection $database, FieldInfo $field_info, CacheBackendInterface $cache, PasswordInterface $password, UserDataInterface $user_data) { + parent::__construct($entity_info, $database, $field_info, $cache); $this->password = $password; $this->userData = $user_data; @@ -70,7 +71,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $entity_info, $container->get('database'), $container->get('field.info'), - $container->get('uuid'), + $container->get('cache.entity'), $container->get('password'), $container->get('user.data') ); @@ -79,23 +80,20 @@ public static function createInstance(ContainerInterface $container, EntityTypeI /** * {@inheritdoc} */ - function postLoad(array &$queried_users) { - foreach ($queried_users as $key => $record) { - $queried_users[$key]->roles = array(); + function mapFromStorageRecords(array $records) { + foreach ($records as $key => $record) { + $records[$key]->roles = array(); if ($record->uid) { - $queried_users[$record->uid]->roles[] = DRUPAL_AUTHENTICATED_RID; + $records[$record->uid]->roles[] = DRUPAL_AUTHENTICATED_RID; } else { - $queried_users[$record->uid]->roles[] = DRUPAL_ANONYMOUS_RID; + $records[$record->uid]->roles[] = DRUPAL_ANONYMOUS_RID; } } // Add any additional roles from the database. - $this->addRoles($queried_users); - - // Call the default postLoad() method. This will add fields and call - // hook_user_load(). - parent::postLoad($queried_users); + $this->addRoles($records); + return parent::mapFromStorageRecords($records); } /** diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 8cb2810..2a586da 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -974,6 +974,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