Issue #2478459 by plach, mkalkbrenner, chx, yched, Berdir, dawehner, benjy: FieldItemInterface methods are only invoked for SQL storage and are inconsistent with hooks
parent
760cd403bf
commit
883c209fb6
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Query\QueryException;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
|
||||
/**
|
||||
|
@ -85,25 +84,25 @@ class ContentEntityNullStorage extends ContentEntityStorageBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doLoadFieldItems($entities, $age) {
|
||||
protected function doLoadRevisionFieldItems($revision_id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSaveFieldItems(EntityInterface $entity, $update) {
|
||||
protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDeleteFieldItems(EntityInterface $entity) {
|
||||
protected function doDeleteFieldItems($entities) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDeleteFieldItemsRevision(EntityInterface $entity) {
|
||||
protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
@ -21,16 +23,35 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
*/
|
||||
protected $bundleKey = FALSE;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cacheBackend;
|
||||
|
||||
/**
|
||||
* Constructs a ContentEntityStorageBase object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache backend to be used.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type) {
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, CacheBackendInterface $cache) {
|
||||
parent::__construct($entity_type);
|
||||
|
||||
$this->bundleKey = $this->entityType->getKey('bundle');
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->cacheBackend = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,7 +59,9 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type
|
||||
$entity_type,
|
||||
$container->get('entity.manager'),
|
||||
$container->get('cache.entity')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -157,6 +180,146 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
*/
|
||||
public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) { }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadRevision($revision_id) {
|
||||
$revision = $this->doLoadRevisionFieldItems($revision_id);
|
||||
|
||||
if ($revision) {
|
||||
$entities = [$revision->id() => $revision];
|
||||
$this->invokeStorageLoadHook($entities);
|
||||
$this->postLoad($entities);
|
||||
}
|
||||
|
||||
return $revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually loads revision field item values from the storage.
|
||||
*
|
||||
* @param int|string $revision_id
|
||||
* The revision identifier.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The specified entity revision or NULL if not found.
|
||||
*/
|
||||
abstract protected function doLoadRevisionFieldItems($revision_id);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave($id, EntityInterface $entity) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
|
||||
if ($entity->isNew()) {
|
||||
// Ensure the entity is still seen as new after assigning it an id, while
|
||||
// storing its data.
|
||||
$entity->enforceIsNew();
|
||||
if ($this->entityType->isRevisionable()) {
|
||||
$entity->setNewRevision();
|
||||
}
|
||||
$return = SAVED_NEW;
|
||||
}
|
||||
else {
|
||||
// @todo Consider returning a different value when saving a non-default
|
||||
// entity revision. See https://www.drupal.org/node/2509360.
|
||||
$return = $entity->isDefaultRevision() ? SAVED_UPDATED : FALSE;
|
||||
}
|
||||
|
||||
$this->populateAffectedRevisionTranslations($entity);
|
||||
$this->doSaveFieldItems($entity);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity field values to the storage.
|
||||
*
|
||||
* This method is responsible for allocating entity and revision identifiers
|
||||
* and updating the entity object with their values.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity object.
|
||||
* @param string[] $names
|
||||
* (optional) The name of the fields to be written to the storage. If an
|
||||
* empty value is passed all field values are saved.
|
||||
*/
|
||||
abstract protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doPreSave(EntityInterface $entity) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityBase $entity */
|
||||
|
||||
// Sync the changes made in the fields array to the internal values array.
|
||||
$entity->updateOriginalValues();
|
||||
|
||||
return parent::doPreSave($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doPostSave(EntityInterface $entity, $update) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
|
||||
if ($update && $this->entityType->isTranslatable()) {
|
||||
$this->invokeTranslationHooks($entity);
|
||||
}
|
||||
|
||||
parent::doPostSave($entity, $update);
|
||||
|
||||
// The revision is stored, it should no longer be marked as new now.
|
||||
if ($this->entityType->isRevisionable()) {
|
||||
$entity->setNewRevision(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete($entities) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
|
||||
foreach ($entities as $entity) {
|
||||
$this->invokeFieldMethod('delete', $entity);
|
||||
}
|
||||
$this->doDeleteFieldItems($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes entity field values from the storage.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
|
||||
* An array of entity objects to be deleted.
|
||||
*/
|
||||
abstract protected function doDeleteFieldItems($entities);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteRevision($revision_id) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
|
||||
if ($revision = $this->loadRevision($revision_id)) {
|
||||
// Prevent deletion if this is the default revision.
|
||||
if ($revision->isDefaultRevision()) {
|
||||
throw new EntityStorageException('Default revision can not be deleted');
|
||||
}
|
||||
$this->invokeFieldMethod('deleteRevision', $revision);
|
||||
$this->doDeleteRevisionFieldItems($revision);
|
||||
$this->invokeHook('revision_delete', $revision);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes field values of an entity revision from the storage.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $revision
|
||||
* An entity revision object to be deleted.
|
||||
*/
|
||||
abstract protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision);
|
||||
|
||||
/**
|
||||
* Checks translation statuses and invoke the related hooks if needed.
|
||||
*
|
||||
|
@ -179,31 +342,96 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes hook_entity_storage_load().
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
|
||||
* List of entities, keyed on the entity ID.
|
||||
*/
|
||||
protected function invokeStorageLoadHook(array &$entities) {
|
||||
if (!empty($entities)) {
|
||||
// Call hook_entity_storage_load().
|
||||
foreach ($this->moduleHandler()->getImplementations('entity_storage_load') as $module) {
|
||||
$function = $module . '_entity_storage_load';
|
||||
$function($entities, $this->entityTypeId);
|
||||
}
|
||||
// Call hook_TYPE_storage_load().
|
||||
foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_storage_load') as $module) {
|
||||
$function = $module . '_' . $this->entityTypeId . '_storage_load';
|
||||
$function($entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function invokeHook($hook, EntityInterface $entity) {
|
||||
if ($hook == 'presave') {
|
||||
$this->invokeFieldMethod('preSave', $entity);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
|
||||
switch ($hook) {
|
||||
case 'presave':
|
||||
$this->invokeFieldMethod('preSave', $entity);
|
||||
break;
|
||||
|
||||
case 'insert':
|
||||
$this->invokeFieldPostSave($entity, FALSE);
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
$this->invokeFieldPostSave($entity, TRUE);
|
||||
break;
|
||||
}
|
||||
|
||||
parent::invokeHook($hook, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a method on the Field objects within an entity.
|
||||
*
|
||||
* Any argument passed will be forwarded to the invoked method.
|
||||
*
|
||||
* @param string $method
|
||||
* The method name.
|
||||
* The name of the method to be invoked.
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity object.
|
||||
*
|
||||
* @return array
|
||||
* A multidimensional associative array of results, keyed by entity
|
||||
* translation language code and field name.
|
||||
*/
|
||||
protected function invokeFieldMethod($method, ContentEntityInterface $entity) {
|
||||
$result = [];
|
||||
$args = array_slice(func_get_args(), 2);
|
||||
foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
|
||||
$translation = $entity->getTranslation($langcode);
|
||||
foreach ($translation->getFields() as $field) {
|
||||
$field->$method();
|
||||
foreach ($translation->getFields() as $name => $items) {
|
||||
// call_user_func_array() is way slower than a direct call so we avoid
|
||||
// using it if have no parameters.
|
||||
$result[$langcode][$name] = $args ? call_user_func_array([$items, $method], $args) : $items->{$method}();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the post save method on the Field objects within an entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity object.
|
||||
* @param bool $update
|
||||
* Specifies whether the entity is being updated or created.
|
||||
*/
|
||||
protected function invokeFieldPostSave(ContentEntityInterface $entity, $update) {
|
||||
// For each entity translation this returns an array of resave flags keyed
|
||||
// by field name, thus we merge them to obtain a list of fields to resave.
|
||||
$resave = [];
|
||||
foreach ($this->invokeFieldMethod('postSave', $entity, $update) as $translation_results) {
|
||||
$resave += array_filter($translation_results);
|
||||
}
|
||||
if ($resave) {
|
||||
$this->doSaveFieldItems($entity, array_keys($resave));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -258,4 +486,118 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures integer entity IDs are valid.
|
||||
*
|
||||
* The identifier sanitization provided by this method has been introduced
|
||||
* as Drupal used to rely on the database to facilitate this, which worked
|
||||
* correctly with MySQL but led to errors with other DBMS such as PostgreSQL.
|
||||
*
|
||||
* @param array $ids
|
||||
* The entity IDs to verify.
|
||||
*
|
||||
* @return array
|
||||
* The sanitized list of entity IDs.
|
||||
*/
|
||||
protected function cleanIds(array $ids) {
|
||||
$definitions = $this->entityManager->getBaseFieldDefinitions($this->entityTypeId);
|
||||
$id_definition = $definitions[$this->entityType->getKey('id')];
|
||||
if ($id_definition->getType() == 'integer') {
|
||||
$ids = array_filter($ids, function ($id) {
|
||||
return is_numeric($id) && $id == (int) $id;
|
||||
});
|
||||
$ids = array_map('intval', $ids);
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets entities from the persistent cache backend.
|
||||
*
|
||||
* @param array|null &$ids
|
||||
* If not empty, return entities that match these IDs. IDs that were found
|
||||
* will be removed from the list.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface[]
|
||||
* Array of entities from the persistent cache.
|
||||
*/
|
||||
protected function getFromPersistentCache(array &$ids = NULL) {
|
||||
if (!$this->entityType->isPersistentlyCacheable() || empty($ids)) {
|
||||
return array();
|
||||
}
|
||||
$entities = array();
|
||||
// Build the list of cache entries to retrieve.
|
||||
$cid_map = array();
|
||||
foreach ($ids as $id) {
|
||||
$cid_map[$id] = $this->buildCacheId($id);
|
||||
}
|
||||
$cids = array_values($cid_map);
|
||||
if ($cache = $this->cacheBackend->getMultiple($cids)) {
|
||||
// Get the entities that were found in the cache.
|
||||
foreach ($ids as $index => $id) {
|
||||
$cid = $cid_map[$id];
|
||||
if (isset($cache[$cid])) {
|
||||
$entities[$id] = $cache[$cid]->data;
|
||||
unset($ids[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores entities in the persistent cache backend.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
|
||||
* Entities to store in the cache.
|
||||
*/
|
||||
protected function setPersistentCache($entities) {
|
||||
if (!$this->entityType->isPersistentlyCacheable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cache_tags = array(
|
||||
$this->entityTypeId . '_values',
|
||||
'entity_field_info',
|
||||
);
|
||||
foreach ($entities as $id => $entity) {
|
||||
$this->cacheBackend->set($this->buildCacheId($id), $entity, CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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->entityType->isPersistentlyCacheable()) {
|
||||
$this->cacheBackend->deleteMultiple($cids);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->entities = array();
|
||||
if ($this->entityType->isPersistentlyCacheable()) {
|
||||
Cache::invalidateTags(array($this->entityTypeId . '_values'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds 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->entityTypeId}:$id";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -382,6 +382,34 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(EntityInterface $entity) {
|
||||
// Track if this entity is new.
|
||||
$is_new = $entity->isNew();
|
||||
|
||||
// Execute presave logic and invoke the related hooks.
|
||||
$id = $this->doPreSave($entity);
|
||||
|
||||
// Perform the save and reset the static cache for the changed entity.
|
||||
$return = $this->doSave($id, $entity);
|
||||
|
||||
// Execute post save logic and invoke the related hooks.
|
||||
$this->doPostSave($entity, !$is_new);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs presave entity processing.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The saved entity.
|
||||
*
|
||||
* @return int|string
|
||||
* The processed entity identifier.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
* If the entity identifier is invalid.
|
||||
*/
|
||||
protected function doPreSave(EntityInterface $entity) {
|
||||
$id = $entity->id();
|
||||
|
||||
// Track the original ID.
|
||||
|
@ -389,13 +417,11 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
|
|||
$id = $entity->getOriginalId();
|
||||
}
|
||||
|
||||
// Track if this entity is new.
|
||||
$is_new = $entity->isNew();
|
||||
// Track if this entity exists already.
|
||||
$id_exists = $this->has($id, $entity);
|
||||
|
||||
// A new entity should not already exist.
|
||||
if ($id_exists && $is_new) {
|
||||
if ($id_exists && $entity->isNew()) {
|
||||
throw new EntityStorageException(SafeMarkup::format('@type entity with ID @id already exists.', array('@type' => $this->entityTypeId, '@id' => $id)));
|
||||
}
|
||||
|
||||
|
@ -408,25 +434,7 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
|
|||
$entity->preSave($this);
|
||||
$this->invokeHook('presave', $entity);
|
||||
|
||||
// Perform the save and reset the static cache for the changed entity.
|
||||
$return = $this->doSave($id, $entity);
|
||||
$this->resetCache(array($id));
|
||||
|
||||
// The entity is no longer new.
|
||||
$entity->enforceIsNew(FALSE);
|
||||
|
||||
// Allow code to run after saving.
|
||||
$entity->postSave($this, !$is_new);
|
||||
$this->invokeHook($is_new ? 'insert' : 'update', $entity);
|
||||
|
||||
// After saving, this is now the "original entity", and subsequent saves
|
||||
// will be updates instead of inserts, and updates must always be able to
|
||||
// correctly identify the original entity.
|
||||
$entity->setOriginalId($entity->id());
|
||||
|
||||
unset($entity->original);
|
||||
|
||||
return $return;
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -443,6 +451,32 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
|
|||
*/
|
||||
abstract protected function doSave($id, EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Performs post save entity processing.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The saved entity.
|
||||
* @param bool $update
|
||||
* Specifies whether the entity is being updated or created.
|
||||
*/
|
||||
protected function doPostSave(EntityInterface $entity, $update) {
|
||||
$this->resetCache(array($entity->id()));
|
||||
|
||||
// The entity is no longer new.
|
||||
$entity->enforceIsNew(FALSE);
|
||||
|
||||
// Allow code to run after saving.
|
||||
$entity->postSave($this, $update);
|
||||
$this->invokeHook($update ? 'update' : 'insert', $entity);
|
||||
|
||||
// After saving, this is now the "original entity", and subsequent saves
|
||||
// will be updates instead of inserts, and updates must always be able to
|
||||
// correctly identify the original entity.
|
||||
$entity->setOriginalId($entity->id());
|
||||
|
||||
unset($entity->original);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an entity query.
|
||||
*
|
||||
|
|
|
@ -79,7 +79,7 @@ interface EntityStorageInterface {
|
|||
/**
|
||||
* Load a specific entity revision.
|
||||
*
|
||||
* @param int $revision_id
|
||||
* @param int|string $revision_id
|
||||
* The revision id.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
namespace Drupal\Core\Entity\Sql;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Database;
|
||||
|
@ -22,7 +21,6 @@ use Drupal\Core\Entity\EntityTypeInterface;
|
|||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
|
@ -109,13 +107,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The entity type's storage schema object.
|
||||
*
|
||||
|
@ -123,13 +114,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
*/
|
||||
protected $storageSchema;
|
||||
|
||||
/**
|
||||
* Cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cacheBackend;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
|
@ -176,10 +160,8 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
* The language manager.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) {
|
||||
parent::__construct($entity_type);
|
||||
parent::__construct($entity_type, $entity_manager, $cache);
|
||||
$this->database = $database;
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->cacheBackend = $cache;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->initTableLayout();
|
||||
}
|
||||
|
@ -414,8 +396,10 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
$entities_from_cache = $this->getFromPersistentCache($ids);
|
||||
|
||||
// Load any remaining entities from the database.
|
||||
$entities_from_storage = $this->getFromStorage($ids);
|
||||
$this->setPersistentCache($entities_from_storage);
|
||||
if ($entities_from_storage = $this->getFromStorage($ids)) {
|
||||
$this->invokeStorageLoadHook($entities_from_storage);
|
||||
$this->setPersistentCache($entities_from_storage);
|
||||
}
|
||||
|
||||
return $entities_from_cache + $entities_from_storage;
|
||||
}
|
||||
|
@ -447,157 +431,12 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
// Map the loaded records into entity objects and according fields.
|
||||
if ($records) {
|
||||
$entities = $this->mapFromStorageRecords($records);
|
||||
|
||||
// Call hook_entity_storage_load().
|
||||
foreach ($this->moduleHandler()->getImplementations('entity_storage_load') as $module) {
|
||||
$function = $module . '_entity_storage_load';
|
||||
$function($entities, $this->entityTypeId);
|
||||
}
|
||||
// Call hook_TYPE_storage_load().
|
||||
foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_storage_load') as $module) {
|
||||
$function = $module . '_' . $this->entityTypeId . '_storage_load';
|
||||
$function($entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures integer entity IDs are valid.
|
||||
*
|
||||
* The identifier sanitization provided by this method has been introduced
|
||||
* as Drupal used to rely on the database to facilitate this, which worked
|
||||
* correctly with MySQL but led to errors with other DBMS such as PostgreSQL.
|
||||
*
|
||||
* @param array $ids
|
||||
* The entity IDs to verify.
|
||||
* @return array
|
||||
* The sanitized list of entity IDs.
|
||||
*/
|
||||
protected function cleanIds(array $ids) {
|
||||
$definitions = $this->entityManager->getBaseFieldDefinitions($this->entityTypeId);
|
||||
$id_definition = $definitions[$this->entityType->getKey('id')];
|
||||
if ($id_definition->getType() == 'integer') {
|
||||
$ids = array_filter($ids, function ($id) {
|
||||
return is_numeric($id) && $id == (int) $id;
|
||||
});
|
||||
$ids = array_map('intval', $ids);
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets entities from the persistent cache backend.
|
||||
*
|
||||
* @param array|null &$ids
|
||||
* If not empty, return entities that match these IDs. IDs that were found
|
||||
* will be removed from the list.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface[]
|
||||
* Array of entities from the persistent cache.
|
||||
*/
|
||||
protected function getFromPersistentCache(array &$ids = NULL) {
|
||||
if (!$this->entityType->isPersistentlyCacheable() || empty($ids)) {
|
||||
return array();
|
||||
}
|
||||
$entities = array();
|
||||
// Build the list of cache entries to retrieve.
|
||||
$cid_map = array();
|
||||
foreach ($ids as $id) {
|
||||
$cid_map[$id] = $this->buildCacheId($id);
|
||||
}
|
||||
$cids = array_values($cid_map);
|
||||
if ($cache = $this->cacheBackend->getMultiple($cids)) {
|
||||
// Get the entities that were found in the cache.
|
||||
foreach ($ids as $index => $id) {
|
||||
$cid = $cid_map[$id];
|
||||
if (isset($cache[$cid])) {
|
||||
$entities[$id] = $cache[$cid]->data;
|
||||
unset($ids[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores entities in the persistent cache backend.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
|
||||
* Entities to store in the cache.
|
||||
*/
|
||||
protected function setPersistentCache($entities) {
|
||||
if (!$this->entityType->isPersistentlyCacheable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cache_tags = array(
|
||||
$this->entityTypeId . '_values',
|
||||
'entity_field_info',
|
||||
);
|
||||
foreach ($entities as $id => $entity) {
|
||||
$this->cacheBackend->set($this->buildCacheId($id), $entity, CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes hook_entity_load_uncached().
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
|
||||
* List of entities, keyed on the entity ID.
|
||||
*/
|
||||
protected function invokeLoadUncachedHook(array &$entities) {
|
||||
if (!empty($entities)) {
|
||||
// Call hook_entity_load_uncached().
|
||||
foreach ($this->moduleHandler()->getImplementations('entity_load_uncached') as $module) {
|
||||
$function = $module . '_entity_load_uncached';
|
||||
$function($entities, $this->entityTypeId);
|
||||
}
|
||||
// Call hook_TYPE_load_uncached().
|
||||
foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_load_uncached') as $module) {
|
||||
$function = $module . '_' . $this->entityTypeId . '_load_uncached';
|
||||
$function($entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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->entityType->isPersistentlyCacheable()) {
|
||||
$this->cacheBackend->deleteMultiple($cids);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->entities = array();
|
||||
if ($this->entityType->isPersistentlyCacheable()) {
|
||||
Cache::invalidateTags(array($this->entityTypeId . '_values'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds 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->entityTypeId}:$id";
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps from storage records to entity objects, and attaches fields.
|
||||
*
|
||||
|
@ -727,7 +566,9 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadRevision($revision_id) {
|
||||
protected function doLoadRevisionFieldItems($revision_id) {
|
||||
$revision = NULL;
|
||||
|
||||
// Build and execute the query.
|
||||
$query_result = $this->buildQuery(array(), $revision_id)->execute();
|
||||
$records = $query_result->fetchAllAssoc($this->idKey);
|
||||
|
@ -735,31 +576,20 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
if (!empty($records)) {
|
||||
// Convert the raw records to entity objects.
|
||||
$entities = $this->mapFromStorageRecords($records, TRUE);
|
||||
$this->postLoad($entities);
|
||||
$entity = reset($entities);
|
||||
if ($entity) {
|
||||
return $entity;
|
||||
}
|
||||
$revision = reset($entities) ?: NULL;
|
||||
}
|
||||
|
||||
return $revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Entity\EntityStorageInterface::deleteRevision().
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteRevision($revision_id) {
|
||||
if ($revision = $this->loadRevision($revision_id)) {
|
||||
// Prevent deletion if this is the default revision.
|
||||
if ($revision->isDefaultRevision()) {
|
||||
throw new EntityStorageException('Default revision can not be deleted');
|
||||
}
|
||||
|
||||
$this->database->delete($this->revisionTable)
|
||||
->condition($this->revisionKey, $revision->getRevisionId())
|
||||
->execute();
|
||||
$this->invokeFieldMethod('deleteRevision', $revision);
|
||||
$this->deleteRevisionFromDedicatedTables($revision);
|
||||
$this->invokeHook('revision_delete', $revision);
|
||||
}
|
||||
protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision) {
|
||||
$this->database->delete($this->revisionTable)
|
||||
->condition($this->revisionKey, $revision->getRevisionId())
|
||||
->execute();
|
||||
$this->deleteRevisionFromDedicatedTables($revision);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -878,7 +708,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete($entities) {
|
||||
protected function doDeleteFieldItems($entities) {
|
||||
$ids = array_keys($entities);
|
||||
|
||||
$this->database->delete($this->entityType->getBaseTable())
|
||||
|
@ -904,7 +734,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
}
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$this->invokeFieldMethod('delete', $entity);
|
||||
$this->deleteFromDedicatedTables($entity);
|
||||
}
|
||||
}
|
||||
|
@ -915,9 +744,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
public function save(EntityInterface $entity) {
|
||||
$transaction = $this->database->startTransaction();
|
||||
try {
|
||||
// Sync the changes made in the fields array to the internal values array.
|
||||
$entity->updateOriginalValues();
|
||||
|
||||
$return = parent::save($entity);
|
||||
|
||||
// Ignore replica server temporarily.
|
||||
|
@ -934,75 +760,97 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave($id, EntityInterface $entity) {
|
||||
// Create the storage record to be saved.
|
||||
$record = $this->mapToStorageRecord($entity);
|
||||
protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
|
||||
$full_save = empty($names);
|
||||
$update = !$full_save || !$entity->isNew();
|
||||
|
||||
$is_new = $entity->isNew();
|
||||
if (!$is_new) {
|
||||
if ($entity->isDefaultRevision()) {
|
||||
$this->database
|
||||
->update($this->baseTable)
|
||||
->fields((array) $record)
|
||||
->condition($this->idKey, $record->{$this->idKey})
|
||||
->execute();
|
||||
$return = SAVED_UPDATED;
|
||||
}
|
||||
else {
|
||||
// @todo, should a different value be returned when saving an entity
|
||||
// with $isDefaultRevision = FALSE?
|
||||
$return = FALSE;
|
||||
}
|
||||
if ($this->revisionTable) {
|
||||
$entity->{$this->revisionKey}->value = $this->saveRevision($entity);
|
||||
}
|
||||
if ($this->dataTable) {
|
||||
$this->populateAffectedRevisionTranslations($entity);
|
||||
$this->saveToSharedTables($entity);
|
||||
}
|
||||
if ($this->revisionDataTable) {
|
||||
$this->saveToSharedTables($entity, $this->revisionDataTable);
|
||||
}
|
||||
if ($full_save) {
|
||||
$shared_table_fields = TRUE;
|
||||
$dedicated_table_fields = TRUE;
|
||||
}
|
||||
else {
|
||||
// Ensure the entity is still seen as new after assigning it an id,
|
||||
// while storing its data.
|
||||
$entity->enforceIsNew();
|
||||
$insert_id = $this->database
|
||||
->insert($this->baseTable, array('return' => Database::RETURN_INSERT_ID))
|
||||
->fields((array) $record)
|
||||
->execute();
|
||||
// Even if this is a new entity the ID key might have been set, in which
|
||||
// case we should not override the provided ID. An ID key that is not set
|
||||
// to any value is interpreted as NULL (or DEFAULT) and thus overridden.
|
||||
if (!isset($record->{$this->idKey})) {
|
||||
$record->{$this->idKey} = $insert_id;
|
||||
}
|
||||
$return = SAVED_NEW;
|
||||
$entity->{$this->idKey}->value = (string) $record->{$this->idKey};
|
||||
if ($this->revisionTable) {
|
||||
$entity->setNewRevision();
|
||||
$record->{$this->revisionKey} = $this->saveRevision($entity);
|
||||
}
|
||||
if ($this->dataTable) {
|
||||
$this->populateAffectedRevisionTranslations($entity);
|
||||
$this->saveToSharedTables($entity);
|
||||
}
|
||||
if ($this->revisionDataTable) {
|
||||
$this->saveToSharedTables($entity, $this->revisionDataTable);
|
||||
}
|
||||
}
|
||||
$this->invokeFieldMethod($is_new ? 'insert' : 'update', $entity);
|
||||
$this->saveToDedicatedTables($entity, !$is_new);
|
||||
$table_mapping = $this->getTableMapping();
|
||||
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
|
||||
$shared_table_fields = FALSE;
|
||||
$dedicated_table_fields = [];
|
||||
|
||||
if (!$is_new && $this->dataTable) {
|
||||
$this->invokeTranslationHooks($entity);
|
||||
// Collect the name of fields to be written in dedicated tables and check
|
||||
// whether shared table records need to be updated.
|
||||
foreach ($names as $name) {
|
||||
$storage_definition = $storage_definitions[$name];
|
||||
if ($table_mapping->allowsSharedTableStorage($storage_definition)) {
|
||||
$shared_table_fields = TRUE;
|
||||
}
|
||||
elseif ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
|
||||
$dedicated_table_fields[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
$entity->enforceIsNew(FALSE);
|
||||
if ($this->revisionTable) {
|
||||
$entity->setNewRevision(FALSE);
|
||||
|
||||
// Update shared table records if necessary.
|
||||
if ($shared_table_fields) {
|
||||
$record = $this->mapToStorageRecord($entity->getUntranslated(), $this->baseTable);
|
||||
// Create the storage record to be saved.
|
||||
if ($update) {
|
||||
$default_revision = $entity->isDefaultRevision();
|
||||
if ($default_revision) {
|
||||
$this->database
|
||||
->update($this->baseTable)
|
||||
->fields((array) $record)
|
||||
->condition($this->idKey, $record->{$this->idKey})
|
||||
->execute();
|
||||
}
|
||||
if ($this->revisionTable) {
|
||||
if ($full_save) {
|
||||
$entity->{$this->revisionKey} = $this->saveRevision($entity);
|
||||
}
|
||||
else {
|
||||
$record = $this->mapToStorageRecord($entity->getUntranslated(), $this->revisionTable);
|
||||
$entity->preSaveRevision($this, $record);
|
||||
$this->database
|
||||
->update($this->revisionTable)
|
||||
->fields((array) $record)
|
||||
->condition($this->revisionKey, $record->{$this->revisionKey})
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
if ($default_revision && $this->dataTable) {
|
||||
$this->saveToSharedTables($entity);
|
||||
}
|
||||
if ($this->revisionDataTable) {
|
||||
$new_revision = $full_save && $entity->isNewRevision();
|
||||
$this->saveToSharedTables($entity, $this->revisionDataTable, $new_revision);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$insert_id = $this->database
|
||||
->insert($this->baseTable, array('return' => Database::RETURN_INSERT_ID))
|
||||
->fields((array) $record)
|
||||
->execute();
|
||||
// Even if this is a new entity the ID key might have been set, in which
|
||||
// case we should not override the provided ID. An ID key that is not set
|
||||
// to any value is interpreted as NULL (or DEFAULT) and thus overridden.
|
||||
if (!isset($record->{$this->idKey})) {
|
||||
$record->{$this->idKey} = $insert_id;
|
||||
}
|
||||
$entity->{$this->idKey} = (string) $record->{$this->idKey};
|
||||
if ($this->revisionTable) {
|
||||
$record->{$this->revisionKey} = $this->saveRevision($entity);
|
||||
}
|
||||
if ($this->dataTable) {
|
||||
$this->saveToSharedTables($entity);
|
||||
}
|
||||
if ($this->revisionDataTable) {
|
||||
$this->saveToSharedTables($entity, $this->revisionDataTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update dedicated table records if necessary.
|
||||
if ($dedicated_table_fields) {
|
||||
$names = is_array($dedicated_table_fields) ? $dedicated_table_fields : [];
|
||||
$this->saveToDedicatedTables($entity, $update, $names);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1019,14 +867,20 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
* The entity object.
|
||||
* @param string $table_name
|
||||
* (optional) The table name to save to. Defaults to the data table.
|
||||
* @param bool $new_revision
|
||||
* (optional) Whether we are dealing with a new revision. By default fetches
|
||||
* the information from the entity object.
|
||||
*/
|
||||
protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL) {
|
||||
protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL, $new_revision = NULL) {
|
||||
if (!isset($table_name)) {
|
||||
$table_name = $this->dataTable;
|
||||
}
|
||||
if (!isset($new_revision)) {
|
||||
$new_revision = $entity->isNewRevision();
|
||||
}
|
||||
$revision = $table_name != $this->dataTable;
|
||||
|
||||
if (!$revision || !$entity->isNewRevision()) {
|
||||
if (!$revision || !$new_revision) {
|
||||
$key = $revision ? $this->revisionKey : $this->idKey;
|
||||
$value = $revision ? $entity->getRevisionId() : $entity->id();
|
||||
// Delete and insert to handle removed values.
|
||||
|
@ -1303,8 +1157,11 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
* The entity.
|
||||
* @param bool $update
|
||||
* TRUE if the entity is being updated, FALSE if it is being inserted.
|
||||
* @param string[] $names
|
||||
* (optional) The names of the fields to be stored. Defaults to all the
|
||||
* available fields.
|
||||
*/
|
||||
protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE) {
|
||||
protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE, $names = array()) {
|
||||
$vid = $entity->getRevisionId();
|
||||
$id = $entity->id();
|
||||
$bundle = $entity->bundle();
|
||||
|
@ -1319,7 +1176,13 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
|
||||
$original = !empty($entity->original) ? $entity->original: NULL;
|
||||
|
||||
foreach ($this->entityManager->getFieldDefinitions($entity_type, $bundle) as $field_name => $field_definition) {
|
||||
// Determine which fields should be actually stored.
|
||||
$definitions = $this->entityManager->getFieldDefinitions($entity_type, $bundle);
|
||||
if ($names) {
|
||||
$definitions = array_intersect_key($definitions, array_flip($names));
|
||||
}
|
||||
|
||||
foreach ($definitions as $field_name => $field_definition) {
|
||||
$storage_definition = $field_definition->getFieldStorageDefinition();
|
||||
if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
|
||||
continue;
|
||||
|
|
|
@ -201,12 +201,7 @@ abstract class FieldItemBase extends Map implements FieldItemInterface {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function insert() { }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update() { }
|
||||
public function postSave($update) { }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
@ -183,26 +183,39 @@ interface FieldItemInterface extends ComplexDataInterface {
|
|||
/**
|
||||
* Defines custom presave behavior for field values.
|
||||
*
|
||||
* This method is called before insert() and update() methods, and before
|
||||
* values are written into storage.
|
||||
* This method is called during the process of saving an entity, just before
|
||||
* values are written into storage. When storing a new entity, its identifier
|
||||
* will not be available yet. This should be used to massage item property
|
||||
* values or perform any other operation that needs to happen before values
|
||||
* are stored. For instance this is the proper phase to auto-create a new
|
||||
* entity for an entity reference field item, because this way it will be
|
||||
* possible to store the referenced entity identifier.
|
||||
*/
|
||||
public function preSave();
|
||||
|
||||
/**
|
||||
* Defines custom insert behavior for field values.
|
||||
* Defines custom post-save behavior for field values.
|
||||
*
|
||||
* This method is called during the process of inserting an entity, just
|
||||
* before values are written into storage.
|
||||
*/
|
||||
public function insert();
|
||||
|
||||
/**
|
||||
* Defines custom update behavior for field values.
|
||||
* This method is called during the process of saving an entity, just after
|
||||
* values are written into storage. This is useful mostly when the business
|
||||
* logic to be implemented always requires the entity identifier, even when
|
||||
* storing a new entity. For instance, when implementing circular entity
|
||||
* references, the referenced entity will be created on pre-save with a dummy
|
||||
* value for the referring entity identifier, which will be updated with the
|
||||
* actual one on post-save.
|
||||
*
|
||||
* This method is called during the process of updating an entity, just before
|
||||
* values are written into storage.
|
||||
* In the rare cases where item properties depend on the entity identifier,
|
||||
* massaging logic will have to be implemented on post-save and returning TRUE
|
||||
* will allow them to be rewritten to the storage with the updated values.
|
||||
*
|
||||
* @param bool $update
|
||||
* Specifies whether the entity is being updated or created.
|
||||
*
|
||||
* @return bool
|
||||
* Whether field items should be rewritten to the storage as a consequence
|
||||
* of the logic implemented by the custom behavior.
|
||||
*/
|
||||
public function update();
|
||||
public function postSave($update);
|
||||
|
||||
/**
|
||||
* Defines custom delete behavior for field values.
|
||||
|
|
|
@ -7,14 +7,12 @@
|
|||
|
||||
namespace Drupal\Core\Field;
|
||||
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TypedData\DataDefinitionInterface;
|
||||
use Drupal\Core\TypedData\Plugin\DataType\ItemList;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
/**
|
||||
* Represents an entity field; that is, a list of field item objects.
|
||||
|
@ -212,15 +210,9 @@ class FieldItemList extends ItemList implements FieldItemListInterface {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function insert() {
|
||||
$this->delegateMethod('insert');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update() {
|
||||
$this->delegateMethod('update');
|
||||
public function postSave($update) {
|
||||
$result = $this->delegateMethod('postSave', $update);
|
||||
return (bool) array_filter($result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -240,13 +232,23 @@ class FieldItemList extends ItemList implements FieldItemListInterface {
|
|||
/**
|
||||
* Calls a method on each FieldItem.
|
||||
*
|
||||
* Any argument passed will be forwarded to the invoked method.
|
||||
*
|
||||
* @param string $method
|
||||
* The name of the method.
|
||||
* The name of the method to be invoked.
|
||||
*
|
||||
* @return array
|
||||
* An array of results keyed by delta.
|
||||
*/
|
||||
protected function delegateMethod($method) {
|
||||
foreach ($this->list as $item) {
|
||||
$item->{$method}();
|
||||
$result = [];
|
||||
$args = array_slice(func_get_args(), 1);
|
||||
foreach ($this->list as $delta => $item) {
|
||||
// call_user_func_array() is way slower than a direct call so we avoid
|
||||
// using it if have no parameters.
|
||||
$result[$delta] = $args ? call_user_func_array([$item, $method], $args) : $item->{$method}();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -130,26 +130,29 @@ interface FieldItemListInterface extends ListInterface, AccessibleInterface {
|
|||
/**
|
||||
* Defines custom presave behavior for field values.
|
||||
*
|
||||
* This method is called before either insert() or update() methods, and
|
||||
* before values are written into storage.
|
||||
* This method is called during the process of saving an entity, just before
|
||||
* item values are written into storage.
|
||||
*
|
||||
* @see \Drupal\Core\Field\FieldItemInterface::preSave()
|
||||
*/
|
||||
public function preSave();
|
||||
|
||||
/**
|
||||
* Defines custom insert behavior for field values.
|
||||
* Defines custom post-save behavior for field values.
|
||||
*
|
||||
* This method is called after the save() method, and before values are
|
||||
* written into storage.
|
||||
*/
|
||||
public function insert();
|
||||
|
||||
/**
|
||||
* Defines custom update behavior for field values.
|
||||
* This method is called during the process of saving an entity, just after
|
||||
* item values are written into storage.
|
||||
*
|
||||
* This method is called after the save() method, and before values are
|
||||
* written into storage.
|
||||
* @param bool $update
|
||||
* Specifies whether the entity is being updated or created.
|
||||
*
|
||||
* @return bool
|
||||
* Whether field items should be rewritten to the storage as a consequence
|
||||
* of the logic implemented by the custom behavior.
|
||||
*
|
||||
* @see \Drupal\Core\Field\FieldItemInterface::postSave()
|
||||
*/
|
||||
public function update();
|
||||
public function postSave($update);
|
||||
|
||||
/**
|
||||
* Defines custom delete behavior for field values.
|
||||
|
|
|
@ -60,6 +60,7 @@ function block_content_test_block_content_insert(BlockContent $block_content) {
|
|||
// Set the block_content title to the block_content ID and save.
|
||||
if ($block_content->label() == 'new') {
|
||||
$block_content->setInfo('BlockContent ' . $block_content->id());
|
||||
$block_content->setNewRevision(FALSE);
|
||||
$block_content->save();
|
||||
}
|
||||
if ($block_content->label() == 'fail_creation') {
|
||||
|
|
|
@ -23,59 +23,54 @@ class FileFieldItemList extends EntityReferenceFieldItemList {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function insert() {
|
||||
parent::insert();
|
||||
public function postSave($update) {
|
||||
$entity = $this->getEntity();
|
||||
|
||||
// Add a new usage for newly uploaded files.
|
||||
foreach ($this->referencedEntities() as $file) {
|
||||
\Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update() {
|
||||
parent::update();
|
||||
$entity = $this->getEntity();
|
||||
|
||||
// Get current target file entities and file IDs.
|
||||
$files = $this->referencedEntities();
|
||||
$fids = array();
|
||||
|
||||
foreach ($files as $file) {
|
||||
$fids[] = $file->id();
|
||||
}
|
||||
|
||||
// On new revisions, all files are considered to be a new usage and no
|
||||
// deletion of previous file usages are necessary.
|
||||
if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) {
|
||||
foreach ($files as $file) {
|
||||
if (!$update) {
|
||||
// Add a new usage for newly uploaded files.
|
||||
foreach ($this->referencedEntities() as $file) {
|
||||
\Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// Get current target file entities and file IDs.
|
||||
$files = $this->referencedEntities();
|
||||
$ids = array();
|
||||
|
||||
// Get the file IDs attached to the field before this update.
|
||||
$field_name = $this->getFieldDefinition()->getName();
|
||||
$original_fids = array();
|
||||
$original_items = $entity->original->getTranslation($this->getLangcode())->$field_name;
|
||||
foreach ($original_items as $item) {
|
||||
$original_fids[] = $item->target_id;
|
||||
}
|
||||
/** @var \Drupal\file\FileInterface $file */
|
||||
foreach ($files as $file) {
|
||||
$ids[] = $file->id();
|
||||
}
|
||||
|
||||
// Decrement file usage by 1 for files that were removed from the field.
|
||||
$removed_fids = array_filter(array_diff($original_fids, $fids));
|
||||
$removed_files = \Drupal::entityManager()->getStorage('file')->loadMultiple($removed_fids);
|
||||
foreach ($removed_files as $file) {
|
||||
\Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id());
|
||||
}
|
||||
// On new revisions, all files are considered to be a new usage and no
|
||||
// deletion of previous file usages are necessary.
|
||||
if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) {
|
||||
foreach ($files as $file) {
|
||||
\Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Add new usage entries for newly added files.
|
||||
foreach ($files as $file) {
|
||||
if (!in_array($file->id(), $original_fids)) {
|
||||
\Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
|
||||
// Get the file IDs attached to the field before this update.
|
||||
$field_name = $this->getFieldDefinition()->getName();
|
||||
$original_ids = array();
|
||||
$original_items = $entity->original->getTranslation($this->getLangcode())->$field_name;
|
||||
foreach ($original_items as $item) {
|
||||
$original_ids[] = $item->target_id;
|
||||
}
|
||||
|
||||
// Decrement file usage by 1 for files that were removed from the field.
|
||||
$removed_ids = array_filter(array_diff($original_ids, $ids));
|
||||
$removed_files = \Drupal::entityManager()->getStorage('file')->loadMultiple($removed_ids);
|
||||
foreach ($removed_files as $file) {
|
||||
\Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id());
|
||||
}
|
||||
|
||||
// Add new usage entries for newly added files.
|
||||
foreach ($files as $file) {
|
||||
if (!in_array($file->id(), $original_ids)) {
|
||||
\Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,6 +170,7 @@ function node_test_node_insert(NodeInterface $node) {
|
|||
// Set the node title to the node ID and save.
|
||||
if ($node->getTitle() == 'new') {
|
||||
$node->setTitle('Node '. $node->id());
|
||||
$node->setNewRevision(FALSE);
|
||||
$node->save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,28 +55,25 @@ class PathItem extends FieldItemBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function insert() {
|
||||
if ($this->alias) {
|
||||
$entity = $this->getEntity();
|
||||
|
||||
if ($path = \Drupal::service('path.alias_storage')->save('/' . $entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode())) {
|
||||
$this->pid = $path['pid'];
|
||||
public function postSave($update) {
|
||||
if (!$update) {
|
||||
if ($this->alias) {
|
||||
$entity = $this->getEntity();
|
||||
if ($path = \Drupal::service('path.alias_storage')->save('/' . $entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode())) {
|
||||
$this->pid = $path['pid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update() {
|
||||
// Delete old alias if user erased it.
|
||||
if ($this->pid && !$this->alias) {
|
||||
\Drupal::service('path.alias_storage')->delete(array('pid' => $this->pid));
|
||||
}
|
||||
// Only save a non-empty alias.
|
||||
elseif ($this->alias) {
|
||||
$entity = $this->getEntity();
|
||||
\Drupal::service('path.alias_storage')->save('/' . $entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode(), $this->pid);
|
||||
else {
|
||||
// Delete old alias if user erased it.
|
||||
if ($this->pid && !$this->alias) {
|
||||
\Drupal::service('path.alias_storage')->delete(array('pid' => $this->pid));
|
||||
}
|
||||
// Only save a non-empty alias.
|
||||
elseif ($this->alias) {
|
||||
$entity = $this->getEntity();
|
||||
\Drupal::service('path.alias_storage')->save('/' . $entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode(), $this->pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains Drupal\system\Tests\Field\FieldItemTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Field;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\Entity\EntityTestMulRev;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\system\Tests\Entity\EntityUnitTestBase;
|
||||
|
||||
/**
|
||||
* Test field item methods.
|
||||
*
|
||||
* @group Field
|
||||
*/
|
||||
class FieldItemTest extends EntityUnitTestBase {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->container->get('state')->set('entity_test.field_test_item', TRUE);
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
|
||||
$entity_type_id = 'entity_test_mulrev';
|
||||
$this->installEntitySchema($entity_type_id);
|
||||
|
||||
$this->fieldName = Unicode::strtolower($this->randomMachineName());
|
||||
|
||||
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'type' => 'field_test',
|
||||
'entity_type' => $entity_type_id,
|
||||
'cardinality' => 1,
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'entity_type' => $entity_type_id,
|
||||
'field_name' => $this->fieldName,
|
||||
'bundle' => $entity_type_id,
|
||||
'label' => 'Test field',
|
||||
])->save();
|
||||
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
$definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
|
||||
$this->assertTrue(!empty($definitions[$this->fieldName]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the field item save workflow.
|
||||
*/
|
||||
public function testSaveWorkflow() {
|
||||
$entity = EntityTestMulRev::create([
|
||||
'name' => $this->randomString(),
|
||||
'field_test_item' => $this->randomString(),
|
||||
$this->fieldName => $this->randomString(),
|
||||
]);
|
||||
|
||||
// Save a new entity and verify that the initial field value is overwritten
|
||||
// with a value containing the entity id, which implies a resave. Check that
|
||||
// the entity data structure and the stored values match.
|
||||
$this->assertSavedFieldItemValue($entity, "field_test:{$this->fieldName}:1:1");
|
||||
|
||||
// Update the entity and verify that the field value is overwritten on
|
||||
// presave if it is not resaved.
|
||||
$this->assertSavedFieldItemValue($entity, 'overwritten');
|
||||
|
||||
// Flag the field value as needing to be resaved and verify it actually is.
|
||||
$entity->field_test_item->value = $entity->{$this->fieldName}->value = 'resave';
|
||||
$this->assertSavedFieldItemValue($entity, "field_test:{$this->fieldName}:1:3");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the saved field item value matches the expected one.
|
||||
*
|
||||
* @param \Drupal\entity_test\Entity\EntityTest $entity
|
||||
* The test entity.
|
||||
* @param $expected_value
|
||||
* The expected field item value.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the item value matches expectations, FALSE otherwise.
|
||||
*/
|
||||
protected function assertSavedFieldItemValue(EntityTest $entity, $expected_value) {
|
||||
$entity->setNewRevision(TRUE);
|
||||
$entity->save();
|
||||
$base_field_expected_value = str_replace($this->fieldName, 'field_test_item', $expected_value);
|
||||
$result = $this->assertEqual($entity->field_test_item->value, $base_field_expected_value);
|
||||
$result = $result && $this->assertEqual($entity->{$this->fieldName}->value, $expected_value);
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$result = $result && $this->assertEqual($entity->field_test_item->value, $base_field_expected_value);
|
||||
$result = $result && $this->assertEqual($entity->{$this->fieldName}->value, $expected_value);
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@ use Drupal\Core\Access\AccessResult;
|
|||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
@ -97,6 +98,23 @@ function entity_test_entity_type_alter(array &$entity_types) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_base_field_info().
|
||||
*/
|
||||
function entity_test_entity_base_field_info(EntityTypeInterface $entity_type) {
|
||||
$fields = [];
|
||||
|
||||
if ($entity_type->id() == 'entity_test_mulrev' && \Drupal::state()->get('entity_test.field_test_item')) {
|
||||
$fields['field_test_item'] = BaseFieldDefinition::create('field_test')
|
||||
->setLabel(t('Field test'))
|
||||
->setDescription(t('A field test.'))
|
||||
->setRevisionable(TRUE)
|
||||
->setTranslatable(TRUE);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_base_field_info_alter().
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\entity_test\Plugin\Field\FieldType\FieldTestItem.
|
||||
*/
|
||||
|
||||
namespace Drupal\entity_test\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\FieldItemBase;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\StringTranslation\TranslationWrapper;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
use Drupal\Core\TypedData\DataDefinitionInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Defines the 'field_test' entity field type.
|
||||
*
|
||||
* @FieldType(
|
||||
* id = "field_test",
|
||||
* label = @Translation("Test field item"),
|
||||
* description = @Translation("A field containing a plain string value."),
|
||||
* category = @Translation("Field"),
|
||||
* )
|
||||
*/
|
||||
class FieldTestItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* Counts how many times all items of this type are saved.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $counter = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
// This is called very early by the user entity roles field. Prevent
|
||||
// early t() calls by using the TranslationWrapper.
|
||||
$properties['value'] = DataDefinition::create('string')
|
||||
->setLabel(new TranslationWrapper('Test value'))
|
||||
->setRequired(TRUE);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function schema(FieldStorageDefinitionInterface $field_definition) {
|
||||
return array(
|
||||
'columns' => array(
|
||||
'value' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(DataDefinitionInterface $definition, $name = NULL, TypedDataInterface $parent = NULL) {
|
||||
parent::__construct($definition, $name, $parent);
|
||||
|
||||
$name = $this->getFieldDefinition()->getName();
|
||||
static::$counter[$name] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave() {
|
||||
$name = $this->getFieldDefinition()->getName();
|
||||
static::$counter[$name]++;
|
||||
|
||||
// Overwrite the field value unless it is going to be overridden, in which
|
||||
// case its final value will already be different from the current one.
|
||||
if (!$this->getEntity()->isNew() && !$this->mustResave()) {
|
||||
$this->setValue('overwritten');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave($update) {
|
||||
// Determine whether the field value should be rewritten to the storage. We
|
||||
// always rewrite on create as we need to store a value including the entity
|
||||
// id.
|
||||
$resave = !$update || $this->mustResave();
|
||||
|
||||
if ($resave) {
|
||||
$entity = $this->getEntity();
|
||||
$definition = $this->getFieldDefinition();
|
||||
$name = $definition->getName();
|
||||
$value = 'field_test:' . $name . ':' . $entity->id() . ':' . static::$counter[$name];
|
||||
$this->setValue($value);
|
||||
}
|
||||
|
||||
return $resave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the field item value should be resaved.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the item should be resaved, FALSE otherwise.
|
||||
*/
|
||||
protected function mustResave() {
|
||||
return $this->getValue()['value'] == 'resave';
|
||||
}
|
||||
|
||||
}
|
|
@ -9,7 +9,7 @@ namespace Drupal\user;
|
|||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
|
@ -72,14 +72,14 @@ class UserStorage extends SqlContentEntityStorage implements UserStorageInterfac
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(EntityInterface $entity) {
|
||||
protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
|
||||
// The anonymous user account is saved with the fixed user ID of 0.
|
||||
// Therefore we need to check for NULL explicitly.
|
||||
if ($entity->id() === NULL) {
|
||||
$entity->uid->value = $this->database->nextId($this->database->query('SELECT MAX(uid) FROM {users}')->fetchField());
|
||||
$entity->enforceIsNew();
|
||||
}
|
||||
return parent::save($entity);
|
||||
return parent::doSaveFieldItems($entity, $names);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1130,8 +1130,10 @@ class SqlContentEntityStorageTest extends UnitTestCase {
|
|||
|
||||
$entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
|
||||
->setConstructorArgs(array($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager))
|
||||
->setMethods(array('getFromStorage'))
|
||||
->setMethods(array('getFromStorage', 'invokeStorageLoadHook'))
|
||||
->getMock();
|
||||
$entity_storage->method('invokeStorageLoadHook')
|
||||
->willReturn(NULL);
|
||||
$entity_storage->expects($this->once())
|
||||
->method('getFromStorage')
|
||||
->with(array($id))
|
||||
|
@ -1180,8 +1182,10 @@ class SqlContentEntityStorageTest extends UnitTestCase {
|
|||
|
||||
$entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
|
||||
->setConstructorArgs(array($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager))
|
||||
->setMethods(array('getFromStorage'))
|
||||
->setMethods(array('getFromStorage', 'invokeStorageLoadHook'))
|
||||
->getMock();
|
||||
$entity_storage->method('invokeStorageLoadHook')
|
||||
->willReturn(NULL);
|
||||
$entity_storage->expects($this->once())
|
||||
->method('getFromStorage')
|
||||
->with(array($id))
|
||||
|
|
Loading…
Reference in New Issue