summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel Catchpole2014-07-01 13:58:10 +0100
committerNathaniel Catchpole2014-07-01 13:59:17 +0100
commitb4f282a55b7da8c883216392cb74bf795cad3ff7 (patch)
treef3415eaba762be6dbc9e257dc654ed81dd76359d
parentda5ea416037360295a46e3770248940c59fbe873 (diff)
Issue #2144263 by fago, yched, alexpott, Berdir, plach, andypost, effulgentsia, michaelfavia: Decouple entity field storage from configurable fields.8.0-alpha13
-rw-r--r--core/includes/entity.inc10
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php408
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php14
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php54
-rw-r--r--core/lib/Drupal/Core/Entity/EntityManager.php8
-rw-r--r--core/lib/Drupal/Core/Entity/Exception/FieldStorageDefinitionUpdateForbiddenException.php13
-rw-r--r--core/lib/Drupal/Core/Entity/FieldableEntityStorageInterface.php111
-rw-r--r--core/lib/Drupal/Core/Entity/Query/Sql/Tables.php10
-rw-r--r--core/lib/Drupal/Core/Field/FieldDefinition.php38
-rw-r--r--core/lib/Drupal/Core/Field/FieldDefinitionInterface.php9
-rw-r--r--core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php7
-rw-r--r--core/modules/comment/src/Tests/CommentFieldsTest.php3
-rw-r--r--core/modules/field/field.api.php5
-rw-r--r--core/modules/field/field.module19
-rw-r--r--core/modules/field/field.purge.inc37
-rw-r--r--core/modules/field/src/ConfigImporterFieldPurger.php7
-rw-r--r--core/modules/field/src/Entity/FieldConfig.php67
-rw-r--r--core/modules/field/src/Entity/FieldInstanceConfig.php13
-rw-r--r--core/modules/field/src/FieldConfigUpdateForbiddenException.php13
-rw-r--r--core/modules/field/src/FieldInstanceConfigStorage.php5
-rw-r--r--core/modules/field/src/Tests/BulkDeleteTest.php17
-rw-r--r--core/modules/field/src/Tests/CrudTest.php5
-rw-r--r--core/modules/field/src/Tests/FieldDataCountTest.php (renamed from core/modules/field/src/Tests/FieldEntityCountTest.php)38
-rw-r--r--core/modules/field/tests/modules/field_test/field_test.field.inc4
-rw-r--r--core/modules/forum/forum.install5
-rw-r--r--core/modules/options/options.module3
-rw-r--r--core/modules/options/src/Tests/OptionsFieldTest.php4
-rw-r--r--core/modules/path/path.module3
-rw-r--r--core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php131
-rw-r--r--core/modules/system/src/Tests/Entity/FieldSqlStorageTest.php9
-rw-r--r--core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.info.yml8
-rw-r--r--core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.install41
-rw-r--r--core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.module69
-rw-r--r--core/profiles/standard/standard.install7
-rw-r--r--core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php10
35 files changed, 816 insertions, 389 deletions
diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index cc539dc..3025433 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -57,16 +57,22 @@ function entity_get_bundles($entity_type = NULL) {
* NULL.
*/
function entity_invoke_bundle_hook($hook, $entity_type, $bundle, $bundle_new = NULL) {
- \Drupal::entityManager()->clearCachedBundles();
+ $entity_manager = \Drupal::entityManager();
+
+ $entity_manager->clearCachedBundles();
// Notify the entity storage.
$method = 'onBundle' . ucfirst($hook);
- $storage = \Drupal::entityManager()->getStorage($entity_type);
+ $storage = $entity_manager->getStorage($entity_type);
if (method_exists($storage, $method)) {
$storage->$method($bundle, $bundle_new);
}
// Invoke hook_entity_bundle_*() hooks.
\Drupal::moduleHandler()->invokeAll('entity_bundle_' . $hook, array($entity_type, $bundle, $bundle_new));
+ // Clear the cached field definitions (not needed in case of 'create').
+ if ($hook == 'rename' || $hook == 'delete') {
+ $entity_manager->clearCachedFieldDefinitions();
+ }
}
/**
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
index b050ba0..2e29857 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
@@ -10,16 +10,15 @@ namespace Drupal\Core\Entity;
use Drupal\Component\Utility\String;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
+use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Entity\Schema\ContentEntitySchemaHandler;
use Drupal\Core\Entity\Sql\DefaultTableMapping;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldConfig;
-use Drupal\field\FieldConfigInterface;
-use Drupal\field\FieldConfigUpdateForbiddenException;
-use Drupal\field\FieldInstanceConfigInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -954,21 +953,22 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
}
// Collect impacted fields.
- $fields = array();
+ $storage_definitions = array();
$definitions = array();
foreach ($bundles as $bundle => $v) {
$definitions[$bundle] = $this->entityManager->getFieldDefinitions($this->entityTypeId, $bundle);
- foreach ($definitions[$bundle] as $field_name => $instance) {
- if ($instance instanceof FieldInstanceConfigInterface) {
- $fields[$field_name] = $instance->getFieldStorageDefinition();
+ foreach ($definitions[$bundle] as $field_name => $field_definition) {
+ $storage_definition = $field_definition->getFieldStorageDefinition();
+ if ($this->usesDedicatedTable($storage_definition)) {
+ $storage_definitions[$field_name] = $storage_definition;
}
}
}
// Load field data.
$langcodes = array_keys(language_list(LanguageInterface::STATE_ALL));
- foreach ($fields as $field_name => $field) {
- $table = $load_current ? static::_fieldTableName($field) : static::_fieldRevisionTableName($field);
+ foreach ($storage_definitions as $field_name => $storage_definition) {
+ $table = $load_current ? static::_fieldTableName($storage_definition) : static::_fieldRevisionTableName($storage_definition);
// Ensure that only values having valid languages are retrieved. Since we
// are loading values for multiple entities, we cannot limit the query to
@@ -992,12 +992,12 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
$delta_count[$row->entity_id][$row->langcode] = 0;
}
- if ($field->getCardinality() == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->langcode] < $field->getCardinality()) {
+ if ($storage_definition->getCardinality() == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->langcode] < $storage_definition->getCardinality()) {
$item = array();
// For each column declared by the field, populate the item from the
// prefixed database column.
- foreach ($field->getColumns() as $column => $attributes) {
- $column_name = static::_fieldColumnName($field, $column);
+ foreach ($storage_definition->getColumns() as $column => $attributes) {
+ $column_name = static::_fieldColumnName($storage_definition, $column);
// Unserialize the value if specified in the column schema.
$item[$column] = (!empty($attributes['serialize'])) ? unserialize($row->$column_name) : $row->$column_name;
}
@@ -1026,13 +1026,13 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
$vid = $id;
}
- foreach ($this->entityManager->getFieldDefinitions($entity_type, $bundle) as $field_name => $instance) {
- if (!($instance instanceof FieldInstanceConfigInterface)) {
+ foreach ($this->entityManager->getFieldDefinitions($entity_type, $bundle) as $field_name => $field_definition) {
+ $storage_definition = $field_definition->getFieldStorageDefinition();
+ if (!$this->usesDedicatedTable($storage_definition)) {
continue;
}
- $field = $instance->getFieldStorageDefinition();
- $table_name = static::_fieldTableName($field);
- $revision_name = static::_fieldRevisionTableName($field);
+ $table_name = static::_fieldTableName($storage_definition);
+ $revision_name = static::_fieldRevisionTableName($storage_definition);
// Delete and insert, rather than update, in case a value was added.
if ($update) {
@@ -1052,13 +1052,13 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
// Prepare the multi-insert query.
$do_insert = FALSE;
$columns = array('entity_id', 'revision_id', 'bundle', 'delta', 'langcode');
- foreach ($field->getColumns() as $column => $attributes) {
- $columns[] = static::_fieldColumnName($field, $column);
+ foreach ($storage_definition->getColumns() as $column => $attributes) {
+ $columns[] = static::_fieldColumnName($storage_definition, $column);
}
$query = $this->database->insert($table_name)->fields($columns);
$revision_query = $this->database->insert($revision_name)->fields($columns);
- $langcodes = $field->isTranslatable() ? $translation_langcodes : array($default_langcode);
+ $langcodes = $field_definition->isTranslatable() ? $translation_langcodes : array($default_langcode);
foreach ($langcodes as $langcode) {
$delta_count = 0;
$items = $entity->getTranslation($langcode)->get($field_name);
@@ -1073,15 +1073,15 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
'delta' => $delta,
'langcode' => $langcode,
);
- foreach ($field->getColumns() as $column => $attributes) {
- $column_name = static::_fieldColumnName($field, $column);
+ foreach ($storage_definition->getColumns() as $column => $attributes) {
+ $column_name = static::_fieldColumnName($storage_definition, $column);
// Serialize the value if specified in the column schema.
$record[$column_name] = !empty($attributes['serialize']) ? serialize($item->$column) : $item->$column;
}
$query->values($record);
$revision_query->values($record);
- if ($field->getCardinality() != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && ++$delta_count == $field->getCardinality()) {
+ if ($storage_definition->getCardinality() != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && ++$delta_count == $storage_definition->getCardinality()) {
break;
}
}
@@ -1103,13 +1103,13 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
* {@inheritdoc}
*/
protected function doDeleteFieldItems(EntityInterface $entity) {
- foreach ($this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $instance) {
- if (!($instance instanceof FieldInstanceConfigInterface)) {
+ foreach ($this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_definition) {
+ $storage_definition = $field_definition->getFieldStorageDefinition();
+ if (!$this->usesDedicatedTable($storage_definition)) {
continue;
}
- $field = $instance->getFieldStorageDefinition();
- $table_name = static::_fieldTableName($field);
- $revision_name = static::_fieldRevisionTableName($field);
+ $table_name = static::_fieldTableName($storage_definition);
+ $revision_name = static::_fieldRevisionTableName($storage_definition);
$this->database->delete($table_name)
->condition('entity_id', $entity->id())
->execute();
@@ -1125,11 +1125,12 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
protected function doDeleteFieldItemsRevision(EntityInterface $entity) {
$vid = $entity->getRevisionId();
if (isset($vid)) {
- foreach ($this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $instance) {
- if (!($instance instanceof FieldInstanceConfigInterface)) {
+ foreach ($this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_definition) {
+ $storage_definition = $field_definition->getFieldStorageDefinition();
+ if (!$this->usesDedicatedTable($storage_definition)) {
continue;
}
- $revision_name = static::_fieldRevisionTableName($instance->getFieldStorageDefinition());
+ $revision_name = static::_fieldRevisionTableName($storage_definition);
$this->database->delete($revision_name)
->condition('entity_id', $entity->id())
->condition('revision_id', $vid)
@@ -1139,10 +1140,25 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
}
/**
+ * Returns whether the field uses a dedicated table for storage.
+ *
+ * @param FieldStorageDefinitionInterface $definition
+ * The field storage definition.
+ *
+ * @return bool
+ * Whether the field uses a dedicated table for storage.
+ */
+ protected function usesDedicatedTable(FieldStorageDefinitionInterface $definition) {
+ // Everything that is not provided by the entity type is stored in a
+ // dedicated table.
+ return $definition->getProvider() != $this->entityType->getProvider() && !$definition->hasCustomStorage();
+ }
+
+ /**
* {@inheritdoc}
*/
- public function onFieldCreate(FieldConfigInterface $field) {
- $schema = $this->_fieldSqlSchema($field);
+ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
+ $schema = $this->_fieldSqlSchema($storage_definition);
foreach ($schema as $name => $table) {
$this->database->schema()->createTable($name, $table);
}
@@ -1151,10 +1167,8 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
/**
* {@inheritdoc}
*/
- public function onFieldUpdate(FieldConfigInterface $field) {
- $original = $field->original;
-
- if (!$field->hasData()) {
+ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
+ if (!$storage_definition->hasData()) {
// There is no data. Re-create the tables completely.
if ($this->database->supportsTransactionalDDL()) {
@@ -1168,7 +1182,7 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
foreach ($original_schema as $name => $table) {
$this->database->schema()->dropTable($name, $table);
}
- $schema = $this->_fieldSqlSchema($field);
+ $schema = $this->_fieldSqlSchema($storage_definition);
foreach ($schema as $name => $table) {
$this->database->schema()->createTable($name, $table);
}
@@ -1190,8 +1204,8 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
}
}
else {
- if ($field->getColumns() != $original->getColumns()) {
- throw new FieldConfigUpdateForbiddenException("The SQL storage cannot change the schema for an existing field with data.");
+ if ($storage_definition->getColumns() != $original->getColumns()) {
+ throw new FieldStorageDefinitionUpdateForbiddenException("The SQL storage cannot change the schema for an existing field with data.");
}
// There is data, so there are no column changes. Drop all the prior
// indexes and create all the new ones, except for all the priors that
@@ -1199,33 +1213,33 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
$table = static::_fieldTableName($original);
$revision_table = static::_fieldRevisionTableName($original);
- $schema = $field->getSchema();
+ $schema = $storage_definition->getSchema();
$original_schema = $original->getSchema();
foreach ($original_schema['indexes'] as $name => $columns) {
if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) {
- $real_name = static::_fieldIndexName($field, $name);
+ $real_name = static::_fieldIndexName($storage_definition, $name);
$this->database->schema()->dropIndex($table, $real_name);
$this->database->schema()->dropIndex($revision_table, $real_name);
}
}
- $table = static::_fieldTableName($field);
- $revision_table = static::_fieldRevisionTableName($field);
+ $table = static::_fieldTableName($storage_definition);
+ $revision_table = static::_fieldRevisionTableName($storage_definition);
foreach ($schema['indexes'] as $name => $columns) {
if (!isset($original_schema['indexes'][$name]) || $columns != $original_schema['indexes'][$name]) {
- $real_name = static::_fieldIndexName($field, $name);
+ $real_name = static::_fieldIndexName($storage_definition, $name);
$real_columns = array();
foreach ($columns as $column_name) {
// Indexes can be specified as either a column name or an array with
// column name and length. Allow for either case.
if (is_array($column_name)) {
$real_columns[] = array(
- static::_fieldColumnName($field, $column_name[0]),
+ static::_fieldColumnName($storage_definition, $column_name[0]),
$column_name[1],
);
}
else {
- $real_columns[] = static::_fieldColumnName($field, $column_name);
+ $real_columns[] = static::_fieldColumnName($storage_definition, $column_name);
}
}
$this->database->schema()->addIndex($table, $real_name, $real_columns);
@@ -1238,20 +1252,18 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
/**
* {@inheritdoc}
*/
- public function onFieldDelete(FieldConfigInterface $field) {
+ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
// Mark all data associated with the field for deletion.
- $table = static::_fieldTableName($field);
- $revision_table = static::_fieldRevisionTableName($field);
+ $table = static::_fieldTableName($storage_definition);
+ $revision_table = static::_fieldRevisionTableName($storage_definition);
$this->database->update($table)
->fields(array('deleted' => 1))
->execute();
// Move the table to a unique name while the table contents are being
// deleted.
- $deleted_field = clone $field;
- $deleted_field->deleted = TRUE;
- $new_table = static::_fieldTableName($deleted_field);
- $revision_new_table = static::_fieldRevisionTableName($deleted_field);
+ $new_table = static::_fieldTableName($storage_definition, TRUE);
+ $revision_new_table = static::_fieldRevisionTableName($storage_definition, TRUE);
$this->database->schema()->renameTable($table, $new_table);
$this->database->schema()->renameTable($revision_table, $revision_new_table);
}
@@ -1259,17 +1271,19 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
/**
* {@inheritdoc}
*/
- public function onInstanceDelete(FieldInstanceConfigInterface $instance) {
- $field = $instance->getFieldStorageDefinition();
- $table_name = static::_fieldTableName($field);
- $revision_name = static::_fieldRevisionTableName($field);
+ public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition) {
+ $storage_definition = $field_definition->getFieldStorageDefinition();
+ $table_name = static::_fieldTableName($storage_definition);
+ $revision_name = static::_fieldRevisionTableName($storage_definition);
+
+ // Mark field data as deleted.
$this->database->update($table_name)
->fields(array('deleted' => 1))
- ->condition('bundle', $instance->bundle)
+ ->condition('bundle', $field_definition->getBundle())
->execute();
$this->database->update($revision_name)
->fields(array('deleted' => 1))
- ->condition('bundle', $instance->bundle)
+ ->condition('bundle', $field_definition->getBundle())
->execute();
}
@@ -1277,76 +1291,171 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
* {@inheritdoc}
*/
public function onBundleRename($bundle, $bundle_new) {
- // We need to account for deleted fields and instances. The method runs
- // before the instance definitions are updated, so we need to fetch them
- // using the old bundle name.
- $instances = entity_load_multiple_by_properties('field_instance_config', array('entity_type' => $this->entityTypeId, 'bundle' => $bundle, 'include_deleted' => TRUE));
- foreach ($instances as $instance) {
- $field = $instance->getFieldStorageDefinition();
- $table_name = static::_fieldTableName($field);
- $revision_name = static::_fieldRevisionTableName($field);
- $this->database->update($table_name)
- ->fields(array('bundle' => $bundle_new))
- ->condition('bundle', $bundle)
- ->execute();
- $this->database->update($revision_name)
- ->fields(array('bundle' => $bundle_new))
- ->condition('bundle', $bundle)
- ->execute();
+ // The method runs before the field definitions are updated, so we use the
+ // old bundle name.
+ $field_definitions = $this->entityManager->getFieldDefinitions($this->entityTypeId, $bundle);
+ // We need to handle deleted fields too. For now, this only makes sense for
+ // configurable fields, so we use the specific API.
+ // @todo Use the unified store of deleted field definitions instead in
+ // https://www.drupal.org/node/2282119
+ $field_definitions += entity_load_multiple_by_properties('field_instance_config', array('entity_type' => $this->entityTypeId, 'bundle' => $bundle, 'deleted' => TRUE, 'include_deleted' => TRUE));
+
+ foreach ($field_definitions as $field_definition) {
+ $storage_definition = $field_definition->getFieldStorageDefinition();
+ if ($this->usesDedicatedTable($storage_definition)) {
+ $is_deleted = $this->storageDefinitionIsDeleted($storage_definition);
+ $table_name = static::_fieldTableName($storage_definition, $is_deleted);
+ $revision_name = static::_fieldRevisionTableName($storage_definition, $is_deleted);
+ $this->database->update($table_name)
+ ->fields(array('bundle' => $bundle_new))
+ ->condition('bundle', $bundle)
+ ->execute();
+ $this->database->update($revision_name)
+ ->fields(array('bundle' => $bundle_new))
+ ->condition('bundle', $bundle)
+ ->execute();
+ }
}
}
/**
* {@inheritdoc}
*/
- protected function readFieldItemsToPurge(EntityInterface $entity, FieldInstanceConfigInterface $instance) {
- $field = $instance->getFieldStorageDefinition();
- $table_name = static::_fieldTableName($field);
- $query = $this->database->select($table_name, 't', array('fetch' => \PDO::FETCH_ASSOC))
- ->condition('entity_id', $entity->id())
- ->orderBy('delta');
- foreach ($field->getColumns() as $column_name => $data) {
- $query->addField('t', static::_fieldColumnName($field, $column_name), $column_name);
- }
- return $query->execute()->fetchAll();
+ protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definition, $batch_size) {
+ // Check whether the whole field storage definition is gone, or just some
+ // bundle fields.
+ $storage_definition = $field_definition->getFieldStorageDefinition();
+ $is_deleted = $this->storageDefinitionIsDeleted($storage_definition);
+ $table_name = static::_fieldTableName($storage_definition, $is_deleted);
+
+ // Get the entities which we want to purge first.
+ $entity_query = $this->database->select($table_name, 't', array('fetch' => \PDO::FETCH_ASSOC));
+ $or = $entity_query->orConditionGroup();
+ foreach ($storage_definition->getColumns() as $column_name => $data) {
+ $or->isNotNull(static::_fieldColumnName($storage_definition, $column_name));
+ }
+ $entity_query
+ ->distinct(TRUE)
+ ->fields('t', array('entity_id'))
+ ->condition('bundle', $field_definition->getBundle())
+ ->range(0, $batch_size);
+
+ // Create a map of field data table column names to field column names.
+ $column_map = array();
+ foreach ($storage_definition->getColumns() as $column_name => $data) {
+ $column_map[static::_fieldColumnName($storage_definition, $column_name)] = $column_name;
+ }
+
+ $entities = array();
+ $items_by_entity = array();
+ foreach ($entity_query->execute() as $row) {
+ $item_query = $this->database->select($table_name, 't', array('fetch' => \PDO::FETCH_ASSOC))
+ ->fields('t')
+ ->condition('entity_id', $row['entity_id'])
+ ->orderBy('delta');
+
+ foreach ($item_query->execute() as $item_row) {
+ if (!isset($entities[$item_row['revision_id']])) {
+ // Create entity with the right revision id and entity id combination.
+ $item_row['entity_type'] = $this->entityTypeId;
+ // @todo: Replace this by an entity object created via an entity
+ // factory, see https://drupal.org/node/1867228.
+ $entities[$item_row['revision_id']] = _field_create_entity_from_ids((object) $item_row);
+ }
+ $item = array();
+ foreach ($column_map as $db_column => $field_column) {
+ $item[$field_column] = $item_row[$db_column];
+ }
+ $items_by_entity[$item_row['revision_id']][] = $item;
+ }
+ }
+
+ // Create field item objects and return.
+ foreach ($items_by_entity as $revision_id => $values) {
+ $items_by_entity[$revision_id] = \Drupal::typedDataManager()->create($field_definition, $values, $field_definition->getName(), $entities[$revision_id]);
+ }
+ return $items_by_entity;
}
/**
* {@inheritdoc}
*/
- public function purgeFieldItems(EntityInterface $entity, FieldInstanceConfigInterface $instance) {
- $field = $instance->getFieldStorageDefinition();
- $table_name = static::_fieldTableName($field);
- $revision_name = static::_fieldRevisionTableName($field);
+ protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefinitionInterface $field_definition) {
+ $storage_definition = $field_definition->getFieldStorageDefinition();
+ $is_deleted = $this->storageDefinitionIsDeleted($storage_definition);
+ $table_name = static::_fieldTableName($storage_definition, $is_deleted);
+ $revision_name = static::_fieldRevisionTableName($storage_definition, $is_deleted);
+ $revision_id = $this->entityType->isRevisionable() ? $entity->getRevisionId() : $entity->id();
$this->database->delete($table_name)
- ->condition('entity_id', $entity->id())
+ ->condition('revision_id', $revision_id)
->execute();
$this->database->delete($revision_name)
- ->condition('entity_id', $entity->id())
+ ->condition('revision_id', $revision_id)
->execute();
}
/**
* {@inheritdoc}
*/
- public function onFieldPurge(FieldConfigInterface $field) {
- $table_name = static::_fieldTableName($field);
- $revision_name = static::_fieldRevisionTableName($field);
+ public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) {
+ $table_name = static::_fieldTableName($storage_definition, TRUE);
+ $revision_name = static::_fieldRevisionTableName($storage_definition, TRUE);
$this->database->schema()->dropTable($table_name);
$this->database->schema()->dropTable($revision_name);
}
/**
+ * {@inheritdoc}
+ */
+ public function countFieldData($storage_definition, $as_bool = FALSE) {
+ $is_deleted = $this->storageDefinitionIsDeleted($storage_definition);
+ $table_name = static::_fieldTableName($storage_definition, $is_deleted);
+
+ $query = $this->database->select($table_name, 't');
+ $or = $query->orConditionGroup();
+ foreach ($storage_definition->getColumns() as $column_name => $data) {
+ $or->isNotNull(static::_fieldColumnName($storage_definition, $column_name));
+ }
+ $query
+ ->condition($or)
+ ->fields('t', array('entity_id'))
+ ->distinct(TRUE);
+ // If we are performing the query just to check if the field has data
+ // limit the number of rows.
+ if ($as_bool) {
+ $query->range(0, 1);
+ }
+ $count = $query->countQuery()->execute()->fetchField();
+ return $as_bool ? (bool) $count : (int) $count;
+ }
+
+ /**
+ * Returns whether the passed field has been already deleted.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ *
+ * @return bool
+ * Whether the field has been already deleted.
+ */
+ protected function storageDefinitionIsDeleted(FieldStorageDefinitionInterface $storage_definition) {
+ return !array_key_exists($storage_definition->getName(), $this->entityManager->getFieldStorageDefinitions($this->entityTypeId));
+ }
+
+ /**
* Gets the SQL table schema.
*
* @private Calling this function circumvents the entity system and is
* strongly discouraged. This function is not considered part of the public
* API and modules relying on it might break even in minor releases.
*
- * @param \Drupal\field\FieldConfigInterface $field
- * The field object
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
* @param array $schema
* The field schema array. Mandatory for upgrades, omit otherwise.
+ * @param bool $deleted
+ * (optional) Whether the schema of the table holding the values of a
+ * deleted field should be returned.
*
* @return array
* The same as a hook_schema() implementation for the data and the
@@ -1354,17 +1463,11 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
*
* @see hook_schema()
*/
- public static function _fieldSqlSchema(FieldConfigInterface $field, array $schema = NULL) {
- if ($field->deleted) {
- $description_current = "Data storage for deleted field {$field->uuid()} ({$field->entity_type}, {$field->getName()}).";
- $description_revision = "Revision archive storage for deleted field {$field->uuid()} ({$field->entity_type}, {$field->getName()}).";
- }
- else {
- $description_current = "Data storage for {$field->entity_type} field {$field->getName()}.";
- $description_revision = "Revision archive storage for {$field->entity_type} field {$field->getName()}.";
- }
+ public static function _fieldSqlSchema(FieldStorageDefinitionInterface $storage_definition, array $schema = NULL, $deleted = FALSE) {
+ $description_current = "Data storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
+ $description_revision = "Revision archive storage for {$storage_definition->getTargetEntityTypeId()} field {$storage_definition->getName()}.";
- $entity_type_id = $field->entity_type;
+ $entity_type_id = $storage_definition->getTargetEntityTypeId();
$entity_manager = \Drupal::entityManager();
$entity_type = $entity_manager->getDefinition($entity_type_id);
$definitions = $entity_manager->getBaseFieldDefinitions($entity_type_id);
@@ -1452,47 +1555,47 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
);
if (!$schema) {
- $schema = $field->getSchema();
+ $schema = $storage_definition->getSchema();
}
// Add field columns.
foreach ($schema['columns'] as $column_name => $attributes) {
- $real_name = static::_fieldColumnName($field, $column_name);
+ $real_name = static::_fieldColumnName($storage_definition, $column_name);
$current['fields'][$real_name] = $attributes;
}
// Add unique keys.
foreach ($schema['unique keys'] as $unique_key_name => $columns) {
- $real_name = static::_fieldIndexName($field, $unique_key_name);
+ $real_name = static::_fieldIndexName($storage_definition, $unique_key_name);
foreach ($columns as $column_name) {
- $current['unique keys'][$real_name][] = static::_fieldColumnName($field, $column_name);
+ $current['unique keys'][$real_name][] = static::_fieldColumnName($storage_definition, $column_name);
}
}
// Add indexes.
foreach ($schema['indexes'] as $index_name => $columns) {
- $real_name = static::_fieldIndexName($field, $index_name);
+ $real_name = static::_fieldIndexName($storage_definition, $index_name);
foreach ($columns as $column_name) {
// Indexes can be specified as either a column name or an array with
// column name and length. Allow for either case.
if (is_array($column_name)) {
$current['indexes'][$real_name][] = array(
- static::_fieldColumnName($field, $column_name[0]),
+ static::_fieldColumnName($storage_definition, $column_name[0]),
$column_name[1],
);
}
else {
- $current['indexes'][$real_name][] = static::_fieldColumnName($field, $column_name);
+ $current['indexes'][$real_name][] = static::_fieldColumnName($storage_definition, $column_name);
}
}
}
// Add foreign keys.
foreach ($schema['foreign keys'] as $specifier => $specification) {
- $real_name = static::_fieldIndexName($field, $specifier);
+ $real_name = static::_fieldIndexName($storage_definition, $specifier);
$current['foreign keys'][$real_name]['table'] = $specification['table'];
foreach ($specification['columns'] as $column_name => $referenced) {
- $sql_storage_column = static::_fieldColumnName($field, $column_name);
+ $sql_storage_column = static::_fieldColumnName($storage_definition, $column_name);
$current['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced;
}
}
@@ -1505,8 +1608,8 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
$revision['fields']['revision_id']['description'] = 'The entity revision id this data is attached to';
return array(
- static::_fieldTableName($field) => $current,
- static::_fieldRevisionTableName($field) => $revision,
+ static::_fieldTableName($storage_definition) => $current,
+ static::_fieldRevisionTableName($storage_definition) => $revision,
);
}
@@ -1520,23 +1623,26 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
* support. Always call entity_load() before using the data found in the
* table.
*
- * @param \Drupal\field\FieldConfigInterface $field
- * The field object.
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ * @param bool $is_deleted
+ * (optional) Whether the table name holding the values of a deleted field
+ * should be returned.
*
* @return string
* A string containing the generated name for the database table.
- *
*/
- static public function _fieldTableName(FieldConfigInterface $field) {
- if ($field->deleted) {
+ public static function _fieldTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
+ if ($is_deleted) {
// When a field is a deleted, the table is renamed to
// {field_deleted_data_FIELD_UUID}. To make sure we don't end up with
- // table names longer than 64 characters, we hash the uuid and return the
- // first 10 characters so we end up with a short unique ID.
- return "field_deleted_data_" . substr(hash('sha256', $field->uuid()), 0, 10);
+ // table names longer than 64 characters, we hash the unique storage
+ // identifier and return the first 10 characters so we end up with a short
+ // unique ID.
+ return "field_deleted_data_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
}
else {
- return static::_generateFieldTableName($field, FALSE);
+ return static::_generateFieldTableName($storage_definition, FALSE);
}
}
@@ -1550,22 +1656,26 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
* support. Always call entity_load() before using the data found in the
* table.
*
- * @param \Drupal\field\FieldConfigInterface $field
- * The field object.
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
+ * @param bool $is_deleted
+ * (optional) Whether the table name holding the values of a deleted field
+ * should be returned.
*
* @return string
* A string containing the generated name for the database table.
*/
- static public function _fieldRevisionTableName(FieldConfigInterface $field) {
- if ($field->deleted) {
+ public static function _fieldRevisionTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
+ if ($is_deleted) {
// When a field is a deleted, the table is renamed to
// {field_deleted_revision_FIELD_UUID}. To make sure we don't end up with
- // table names longer than 64 characters, we hash the uuid and return the
- // first 10 characters so we end up with a short unique ID.
- return "field_deleted_revision_" . substr(hash('sha256', $field->uuid()), 0, 10);
+ // table names longer than 64 characters, we hash the unique storage
+ // identifier and return the first 10 characters so we end up with a short
+ // unique ID.
+ return "field_deleted_revision_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
}
else {
- return static::_generateFieldTableName($field, TRUE);
+ return static::_generateFieldTableName($storage_definition, TRUE);
}
}
@@ -1575,17 +1685,17 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
* The method accounts for a maximum table name length of 64 characters, and
* takes care of disambiguation.
*
- * @param \Drupal\field\FieldConfigInterface $field
- * The field object.
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
* @param bool $revision
* TRUE for revision table, FALSE otherwise.
*
* @return string
* The final table name.
*/
- static protected function _generateFieldTableName(FieldConfigInterface $field, $revision) {
+ protected static function _generateFieldTableName(FieldStorageDefinitionInterface $storage_definition, $revision) {
$separator = $revision ? '_revision__' : '__';
- $table_name = $field->entity_type . $separator . $field->name;
+ $table_name = $storage_definition->getTargetEntityTypeId() . $separator . $storage_definition->getName();
// Limit the string to 48 characters, keeping a 16 characters margin for db
// prefixes.
if (strlen($table_name) > 48) {
@@ -1593,8 +1703,8 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
// field UUID.
$separator = $revision ? '_r__' : '__';
// Truncate to the same length for the current and revision tables.
- $entity_type = substr($field->entity_type, 0, 34);
- $field_hash = substr(hash('sha256', $field->uuid()), 0, 10);
+ $entity_type = substr($storage_definition->getTargetEntityTypeId(), 0, 34);
+ $field_hash = substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
$table_name = $entity_type . $separator . $field_hash;
}
return $table_name;
@@ -1607,8 +1717,8 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
* strongly discouraged. This function is not considered part of the public
* API and modules relying on it might break even in minor releases.
*
- * @param \Drupal\field\FieldConfigInterface $field
- * The field structure
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
* @param string $index
* The name of the index.
*
@@ -1616,8 +1726,8 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
* A string containing a generated index name for a field data table that is
* unique among all other fields.
*/
- static public function _fieldIndexName(FieldConfigInterface $field, $index) {
- return $field->getName() . '_' . $index;
+ public static function _fieldIndexName(FieldStorageDefinitionInterface $storage_definition, $index) {
+ return $storage_definition->getName() . '_' . $index;
}
/**
@@ -1630,8 +1740,8 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
* support. Always call entity_load() before using the data found in the
* table.
*
- * @param \Drupal\field\FieldConfigInterface $field
- * The field object.
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field storage definition.
* @param string $column
* The name of the column.
*
@@ -1639,8 +1749,8 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
* A string containing a generated column name for a field data table that is
* unique among all other fields.
*/
- static public function _fieldColumnName(FieldConfigInterface $field, $column) {
- return in_array($column, FieldConfig::getReservedColumns()) ? $column : $field->getName() . '_' . $column;
+ public static function _fieldColumnName(FieldStorageDefinitionInterface $storage_definition, $column) {
+ return in_array($column, FieldConfig::getReservedColumns()) ? $column : $storage_definition->getName() . '_' . $column;
}
}
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
index 98dbd2f..12aeca3 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
@@ -8,7 +8,7 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Query\QueryException;
-use Drupal\field\FieldInstanceConfigInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Defines a null entity storage.
@@ -109,13 +109,14 @@ class ContentEntityNullStorage extends ContentEntityStorageBase {
/**
* {@inheritdoc}
*/
- protected function readFieldItemsToPurge(EntityInterface $entity, FieldInstanceConfigInterface $instance) {
+ protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definition, $batch_size) {
+ return array();
}
/**
* {@inheritdoc}
*/
- protected function purgeFieldItems(EntityInterface $entity, FieldInstanceConfigInterface $instance) {
+ protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefinitionInterface $field_definition) {
}
/**
@@ -130,4 +131,11 @@ class ContentEntityNullStorage extends ContentEntityStorageBase {
protected function has($id, EntityInterface $entity) {
}
+ /**
+ * {@inheritdoc}
+ */
+ public function countFieldData($storage_definition, $as_bool = FALSE) {
+ return $as_bool ? FALSE : 0;
+ }
+
}
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index 29c1de9..dad9674 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -8,8 +8,9 @@
namespace Drupal\Core\Entity;
use Drupal\Component\Utility\String;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Cache\Cache;
-use Drupal\field\FieldConfigInterface;
use Drupal\field\FieldInstanceConfigInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -267,32 +268,32 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Fie
/**
* {@inheritdoc}
*/
- public function onFieldCreate(FieldConfigInterface $field) { }
+ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) { }
/**
* {@inheritdoc}
*/
- public function onFieldUpdate(FieldConfigInterface $field) { }
+ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { }
/**
* {@inheritdoc}
*/
- public function onFieldDelete(FieldConfigInterface $field) { }
+ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) { }
/**
* {@inheritdoc}
*/
- public function onInstanceCreate(FieldInstanceConfigInterface $instance) { }
+ public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definition) { }
/**
* {@inheritdoc}
*/
- public function onInstanceUpdate(FieldInstanceConfigInterface $instance) { }
+ public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original) { }
/**
* {@inheritdoc}
*/
- public function onInstanceDelete(FieldInstanceConfigInterface $instance) { }
+ public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition) { }
/**
* {@inheritdoc}
@@ -312,45 +313,46 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Fie
/**
* {@inheritdoc}
*/
- public function onFieldItemsPurge(EntityInterface $entity, FieldInstanceConfigInterface $instance) {
- if ($values = $this->readFieldItemsToPurge($entity, $instance)) {
- $items = \Drupal::typedDataManager()->create($instance, $values, $instance->getName(), $entity);
+ public function purgeFieldData(FieldDefinitionInterface $field_definition, $batch_size) {
+ $items_by_entity = $this->readFieldItemsToPurge($field_definition, $batch_size);
+
+ foreach ($items_by_entity as $items) {
$items->delete();
+ $this->purgeFieldItems($items->getEntity(), $field_definition);
}
- $this->purgeFieldItems($entity, $instance);
+ return count($items_by_entity);
}
/**
- * Reads values to be purged for a single field of a single entity.
+ * Reads values to be purged for a single field.
*
* This method is called during field data purge, on fields for which
* onFieldDelete() or onFieldInstanceDelete() has previously run.
*
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * The entity.
- * @param \Drupal\field\FieldInstanceConfigInterface $instance
- * The field instance.
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The field definition.
+ * @param $batch_size
+ * The maximum number of field data records to purge before returning.
*
- * @return array
- * The field values, in their canonical array format (numerically indexed
- * array of items, each item being a property/value array).
+ * @return \Drupal\Core\Field\FieldItemListInterface[]
+ * An array of field item lists, keyed by entity revision id.
*/
- abstract protected function readFieldItemsToPurge(EntityInterface $entity, FieldInstanceConfigInterface $instance);
+ abstract protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definition, $batch_size);
/**
- * Removes field data from storage during purge.
+ * Removes field items from storage per entity during purge.
*
- * @param EntityInterface $entity
- * The entity whose values are being purged.
- * @param FieldInstanceConfigInterface $instance
+ * @param ContentEntityInterface $entity
+ * The entity revision, whose values are being purged.
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field whose values are bing purged.
*/
- abstract protected function purgeFieldItems(EntityInterface $entity, FieldInstanceConfigInterface $instance);
+ abstract protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefinitionInterface $field_definition);
/**
* {@inheritdoc}
*/
- public function onFieldPurge(FieldConfigInterface $field) { }
+ public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) { }
/**
* Checks translation statuses and invoke the related hooks if needed.
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index cf5ae6b..ba644be 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -394,11 +394,13 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
}
}
- // Automatically set the field name for non-configurable fields.
+ // Automatically set the field name, target entity type and bundle
+ // for non-configurable fields.
foreach ($base_field_definitions as $field_name => $base_field_definition) {
if ($base_field_definition instanceof FieldDefinition) {
$base_field_definition->setName($field_name);
$base_field_definition->setTargetEntityTypeId($entity_type_id);
+ $base_field_definition->setBundle(NULL);
}
}
@@ -491,11 +493,13 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
}
}
- // Automatically set the field name for non-configurable fields.
+ // Automatically set the field name, target entity type and bundle
+ // for non-configurable fields.
foreach ($bundle_field_definitions as $field_name => $field_definition) {
if ($field_definition instanceof FieldDefinition) {
$field_definition->setName($field_name);
$field_definition->setTargetEntityTypeId($entity_type_id);
+ $field_definition->setBundle($bundle);
}
}
diff --git a/core/lib/Drupal/Core/Entity/Exception/FieldStorageDefinitionUpdateForbiddenException.php b/core/lib/Drupal/Core/Entity/Exception/FieldStorageDefinitionUpdateForbiddenException.php
new file mode 100644
index 0000000..54e2bff
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Exception/FieldStorageDefinitionUpdateForbiddenException.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Exception\StorageDefinitionUpdateForbiddenException.
+ */
+
+namespace Drupal\Core\Entity\Exception;
+
+/**
+ * Exception thrown when a storage definition update is forbidden.
+ */
+class FieldStorageDefinitionUpdateForbiddenException extends \Exception { }
diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/FieldableEntityStorageInterface.php
index 2998c21..2d695c3 100644
--- a/core/lib/Drupal/Core/Entity/FieldableEntityStorageInterface.php
+++ b/core/lib/Drupal/Core/Entity/FieldableEntityStorageInterface.php
@@ -7,71 +7,78 @@
namespace Drupal\Core\Entity;
-use Drupal\field\FieldConfigInterface;
-use Drupal\field\FieldInstanceConfigInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
interface FieldableEntityStorageInterface extends EntityStorageInterface {
/**
- * Allows reaction to the creation of a configurable field.
+ * Reacts to the creation of a field storage definition.
*
- * @param \Drupal\field\FieldConfigInterface $field
- * The field being created.
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The definition being created.
*/
- public function onFieldCreate(FieldConfigInterface $field);
+ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition);
/**
- * Allows reaction to the update of a configurable field.
+ * Reacts to the update of a field storage definition.
*
- * @param \Drupal\field\FieldConfigInterface $field
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field being updated.
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
+ * The original storage definition; i.e., the definition before the update.
+ *
+ * @throws \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException
+ * Thrown when the update to the field is forbidden.
*/
- public function onFieldUpdate(FieldConfigInterface $field);
+ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original);
/**
- * Allows reaction to the deletion of a configurable field.
+ * Reacts to the deletion of a field storage definition.
*
* Stored values should not be wiped at once, but marked as 'deleted' so that
* they can go through a proper purge process later on.
*
- * @param \Drupal\field\FieldConfigInterface $field
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field being deleted.
*
- * @see fieldPurgeData()
+ * @see purgeFieldData()
*/
- public function onFieldDelete(FieldConfigInterface $field);
+ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition);
/**
- * Allows reaction to the creation of a configurable field instance.
+ * Reacts to the creation of a field.
*
- * @param \Drupal\field\FieldInstanceConfigInterface $instance
- * The instance being created.
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The field definition created.
*/
- public function onInstanceCreate(FieldInstanceConfigInterface $instance);
+ public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definition);
/**
- * Allows reaction to the update of a configurable field instance.
+ * Reacts to the update of a field.
*
- * @param \Drupal\field\FieldInstanceConfigInterface $instance
- * The instance being updated.
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The field definition being updated.
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The original field definition; i.e., the definition before the update.
*/
- public function onInstanceUpdate(FieldInstanceConfigInterface $instance);
+ public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original);
/**
- * Allows reaction to the deletion of a configurable field instance.
+ * Reacts to the deletion of a field.
*
* Stored values should not be wiped at once, but marked as 'deleted' so that
* they can go through a proper purge process later on.
*
- * @param \Drupal\field\FieldInstanceConfigInterface $instance
- * The instance being deleted.
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The field definition being deleted.
*
- * @see fieldPurgeData()
+ * @see purgeFieldData()
*/
- public function onInstanceDelete(FieldInstanceConfigInterface $instance);
+ public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition);
/**
- * Allows reaction to a bundle being created.
+ * Reacts to a bundle being created.
*
* @param string $bundle
* The name of the bundle created.
@@ -79,10 +86,9 @@ interface FieldableEntityStorageInterface extends EntityStorageInterface {
public function onBundleCreate($bundle);
/**
- * Allows reaction to a bundle being renamed.
+ * Reacts to a bundle being renamed.
*
- * This method runs before field instance definitions are updated with the new
- * bundle name.
+ * This method runs before fields are updated with the new bundle name.
*
* @param string $bundle
* The name of the bundle being renamed.
@@ -92,9 +98,9 @@ interface FieldableEntityStorageInterface extends EntityStorageInterface {
public function onBundleRename($bundle, $bundle_new);
/**
- * Allows reaction to a bundle being deleted.
+ * Reacts to a bundle being deleted.
*
- * This method runs before field and instance definitions are deleted.
+ * This method runs before fields are deleted.
*
* @param string $bundle
* The name of the bundle being deleted.
@@ -102,24 +108,43 @@ interface FieldableEntityStorageInterface extends EntityStorageInterface {
public function onBundleDelete($bundle);
/**
- * Purges the field data for a single field on a single entity.
+ * Purges a batch of field data.
+ *
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ * The deleted field whose data is being purged.
+ * @param $batch_size
+ * The maximum number of field data records to purge before returning,
+ * relating to the count of field data records returned by
+ * \Drupal\Core\Entity\FieldableEntityStorageInterface::countFieldData().
+ *
+ * @return int
+ * The number of field data records that have been purged.
+ */
+ public function purgeFieldData(FieldDefinitionInterface $field_definition, $batch_size);
+
+ /**
+ * Determines the number of entities with values for a given field.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+ * The field for which to count data records.
+ * @param bool $as_bool
+ * (Optional) Optimises the query for checking whether there are any records
+ * or not. Defaults to FALSE.
*
- * The entity itself is not being deleted, and it is quite possible that
- * other field data will remain attached to it.
+ * @return bool|int
+ * The number of entities. If $as_bool parameter is TRUE then the
+ * value will either be TRUE or FALSE.
*
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * The entity whose field data is being purged.
- * @param \Drupal\field\FieldInstanceConfigInterface $instance
- * The deleted field instance whose data is being purged.
+ * @see \Drupal\Core\Entity\FieldableEntityStorageInterface::purgeFieldData()
*/
- public function onFieldItemsPurge(EntityInterface $entity, FieldInstanceConfigInterface $instance);
+ public function countFieldData($storage_definition, $as_bool = FALSE);
/**
- * Performs final cleanup after all data on all instances has been purged.
+ * Performs final cleanup after all data of a field has been purged.
*
- * @param \Drupal\field\FieldConfigInterface $instance
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field being purged.
*/
- public function onFieldPurge(FieldConfigInterface $field);
+ public function finalizePurge(FieldStorageDefinitionInterface $storage_definition);
}
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
index 6926603..d5a6334 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
@@ -106,15 +106,7 @@ class Tables implements TablesInterface {
// This can either be the name of an entity base field or a configurable
// field.
$specifier = $specifiers[$key];
- // Normally it is a field name, but field_purge_batch() is passing in
- // id:$field_id so check that first.
- /* @var \Drupal\Core\Field\FieldDefinitionInterface $field */
- if (substr($specifier, 0, 3) == 'id:') {
- if ($fields = entity_load_multiple_by_properties('field_config', array('uuid' => substr($specifier, 3), 'include_deleted' => TRUE))) {
- $field = current($fields);
- }
- }
- elseif (isset($field_storage_definitions[$specifier])) {
+ if (isset($field_storage_definitions[$specifier])) {
$field = $field_storage_definitions[$specifier];
}
else {
diff --git a/core/lib/Drupal/Core/Field/FieldDefinition.php b/core/lib/Drupal/Core/Field/FieldDefinition.php
index a0576f6..828ee70 100644
--- a/core/lib/Drupal/Core/Field/FieldDefinition.php
+++ b/core/lib/Drupal/Core/Field/FieldDefinition.php
@@ -509,8 +509,7 @@ class FieldDefinition extends ListDataDefinition implements FieldDefinitionInter
* @param string $entity_type_id
* The name of the target entity type to set.
*
- * @return static
- * The object itself for chaining.
+ * @return $this
*/
public function setTargetEntityTypeId($entity_type_id) {
$this->definition['entity_type'] = $entity_type_id;
@@ -520,6 +519,26 @@ class FieldDefinition extends ListDataDefinition implements FieldDefinitionInter
/**
* {@inheritdoc}
*/
+ public function getBundle() {
+ return isset($this->definition['bundle']) ? $this->definition['bundle'] : NULL;
+ }
+
+ /**
+ * Sets the bundle this field is defined for.
+ *
+ * @param string|null $bundle
+ * The bundle, or NULL if the field is not bundle-specific.
+ *
+ * @return $this
+ */
+ public function setBundle($bundle) {
+ $this->definition['bundle'] = $bundle;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function getSchema() {
if (!isset($this->schema)) {
// Get the schema from the field item class.
@@ -576,7 +595,7 @@ class FieldDefinition extends ListDataDefinition implements FieldDefinitionInter
* {@inheritdoc}
*/
public function hasCustomStorage() {
- return !empty($this->definition['custom_storage']);
+ return !empty($this->definition['custom_storage']) || $this->isComputed();
}
/**
@@ -587,8 +606,14 @@ class FieldDefinition extends ListDataDefinition implements FieldDefinitionInter
* TRUE otherwise.
*
* @return $this
+ *
+ * @throws \LogicException
+ * Thrown if custom storage is to be set to FALSE for a computed field.
*/
public function setCustomStorage($custom_storage) {
+ if (!$custom_storage && $this->isComputed()) {
+ throw new \LogicException("Entity storage cannot store a computed field.");
+ }
$this->definition['custom_storage'] = $custom_storage;
return $this;
}
@@ -600,4 +625,11 @@ class FieldDefinition extends ListDataDefinition implements FieldDefinitionInter
return $this;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getUniqueStorageIdentifier() {
+ return $this->getTargetEntityTypeId() . '-' . $this->getName();
+ }
+
}
diff --git a/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php b/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php
index 2933656..be7f0fe 100644
--- a/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php
@@ -76,6 +76,15 @@ interface FieldDefinitionInterface extends ListDataDefinitionInterface {
public function getType();
/**
+ * Gets the bundle the field is defined for.
+ *
+ * @return string|null
+ * The bundle the field is defined for, or NULL if it is a base field; i.e.,
+ * it is not bundle-specific.
+ */
+ public function getBundle();
+
+ /**
* Returns whether the display for the field can be configured.
*
* @param string $display_context
diff --git a/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php b/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php
index 161b0b3..10f090e 100644
--- a/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php
@@ -297,4 +297,11 @@ interface FieldStorageDefinitionInterface {
*/
public function hasCustomStorage();
+ /**
+ * Returns a unique identifier for the field.
+ *
+ * @return string
+ */
+ public function getUniqueStorageIdentifier();
+
}
diff --git a/core/modules/comment/src/Tests/CommentFieldsTest.php b/core/modules/comment/src/Tests/CommentFieldsTest.php
index 5dd3bb7..5bb4380 100644
--- a/core/modules/comment/src/Tests/CommentFieldsTest.php
+++ b/core/modules/comment/src/Tests/CommentFieldsTest.php
@@ -85,9 +85,6 @@ class CommentFieldsTest extends CommentTestBase {
// Purge field data now to allow comment module to be uninstalled once the
// field has been deleted.
field_purge_batch(10);
- // Call again as field_purge_batch() won't remove both the instances and
- // field in a single pass.
- field_purge_batch(10);
// Disable the comment module.
$edit = array();
diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index e8fb142..f364c51 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -6,7 +6,6 @@
*/
use Drupal\Component\Utility\NestedArray;
-use Drupal\field\FieldConfigUpdateForbiddenException;
/**
* @defgroup field_types Field Types API
@@ -248,7 +247,7 @@ function hook_field_info_max_weight($entity_type, $bundle, $context, $context_mo
* that cannot be updated.
*
* To forbid the update from occurring, throw a
- * Drupal\field\FieldConfigUpdateForbiddenException.
+ * \Drupal\Core\Entity\Exception\StorageDefinitionUpdateForbiddenException.
*
* @param \Drupal\field\FieldConfigInterface $field
* The field as it will be post-update.
@@ -270,7 +269,7 @@ function hook_field_config_update_forbid(\Drupal\field\FieldConfigInterface $fie
->range(0, 1)
->execute();
if ($found) {
- throw new FieldConfigUpdateForbiddenException("Cannot update a list field not to include keys with existing data");
+ throw new \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException("Cannot update a list field not to include keys with existing data");
}
}
}
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 1cd2972..3874c4e 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -198,20 +198,27 @@ function field_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundl
}
}
-
/**
* Implements hook_entity_bundle_rename().
*/
function field_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) {
- $instances = entity_load_multiple_by_properties('field_instance_config', array('entity_type' => $entity_type, 'bundle' => $bundle_old));
+ $instances = entity_load_multiple_by_properties('field_instance_config', array('entity_type' => $entity_type, 'bundle' => $bundle_old, 'include_deleted' => TRUE));
foreach ($instances as $instance) {
- if ($instance->entity_type == $entity_type && $instance->bundle == $bundle_old) {
- $id_new = $instance->entity_type . '.' . $bundle_new . '.' . $instance->field_name;
- $instance->set('id', $id_new);
- $instance->bundle = $bundle_new;
+ $id_new = $instance->entity_type . '.' . $bundle_new . '.' . $instance->field_name;
+ $instance->set('id', $id_new);
+ $instance->bundle = $bundle_new;
+ // Save non-deleted instances.
+ if (!$instance->isDeleted()) {
$instance->allowBundleRename();
$instance->save();
}
+ // Update deleted instances directly in the state storage.
+ else {
+ $state = \Drupal::state();
+ $deleted_instances = $state->get('field.instance.deleted') ?: array();
+ $deleted_instances[$instance->uuid] = $instance->toArray();
+ $state->set('field.instance.deleted', $deleted_instances);
+ }
}
}
diff --git a/core/modules/field/field.purge.inc b/core/modules/field/field.purge.inc
index 4471970..4a289cf 100644
--- a/core/modules/field/field.purge.inc
+++ b/core/modules/field/field.purge.inc
@@ -80,7 +80,6 @@ function field_purge_batch($batch_size, $field_uuid = NULL) {
else {
$instances = entity_load_multiple_by_properties('field_instance_config', array('deleted' => TRUE, 'include_deleted' => TRUE));
}
- $factory = \Drupal::service('entity.query');
$info = \Drupal::entityManager()->getDefinitions();
foreach ($instances as $instance) {
$entity_type = $instance->entity_type;
@@ -92,36 +91,16 @@ function field_purge_batch($batch_size, $field_uuid = NULL) {
continue;
}
- $ids = (object) array(
- 'entity_type' => $entity_type,
- 'bundle' => $instance->bundle,
- );
- // Retrieve some entities.
- $query = $factory->get($entity_type)
- ->condition('id:' . $instance->getFieldStorageDefinition()->uuid() . '.deleted', 1)
- ->range(0, $batch_size);
- // If there's no bundle key, all results will have the same bundle.
- if ($bundle_key = $info[$entity_type]->getKey('bundle')) {
- $query->condition($bundle_key, $ids->bundle);
- }
- $results = $query->execute();
- if ($results) {
- foreach ($results as $revision_id => $entity_id) {
- $ids->revision_id = $revision_id;
- $ids->entity_id = $entity_id;
- $entity = _field_create_entity_from_ids($ids);
- \Drupal::entityManager()->getStorage($entity_type)->onFieldItemsPurge($entity, $instance);
- $batch_size--;
- }
- // Only delete up to the maximum number of records.
- if ($batch_size == 0) {
- break;
- }
- }
- else {
+ $count_purged = \Drupal::entityManager()->getStorage($entity_type)->purgeFieldData($instance, $batch_size);
+ if ($count_purged < $batch_size || $count_purged == 0) {
// No field data remains for the instance, so we can remove it.
field_purge_instance($instance);
}
+ $batch_size -= $count_purged;
+ // Only delete up to the maximum number of records.
+ if ($batch_size == 0) {
+ break;
+ }
}
// Retrieve all deleted fields. Any that have no instances can be purged.
@@ -187,7 +166,7 @@ function field_purge_field($field) {
$state->set('field.field.deleted', $deleted_fields);
// Notify the storage layer.
- \Drupal::entityManager()->getStorage($field->entity_type)->onFieldPurge($field);
+ \Drupal::entityManager()->getStorage($field->entity_type)->finalizePurge($field);
// Invoke external hooks after the cache is cleared for API consistency.
\Drupal::moduleHandler()->invokeAll('field_purge_field', array($field));
diff --git a/core/modules/field/src/ConfigImporterFieldPurger.php b/core/modules/field/src/ConfigImporterFieldPurger.php
index db3d8b9..a33df39 100644
--- a/core/modules/field/src/ConfigImporterFieldPurger.php
+++ b/core/modules/field/src/ConfigImporterFieldPurger.php
@@ -75,7 +75,8 @@ class ConfigImporterFieldPurger {
$context['sandbox']['field']['steps_to_delete'] = 0;
$fields = static::getFieldsToPurge($context['sandbox']['field']['extensions'], $config_importer->getUnprocessedConfiguration('delete'));
foreach ($fields as $field) {
- $row_count = $field->entityCount();
+ $row_count = \Drupal::entityManager()->getStorage($field->getTargetEntityTypeId())
+ ->countFieldData($field);
if ($row_count > 0) {
// The number of steps to delete each field is determined by the
// purge_batch_size setting. For example if the field has 9 rows and the
@@ -84,8 +85,8 @@ class ConfigImporterFieldPurger {
$context['sandbox']['field']['steps_to_delete'] += $how_many_steps;
}
}
- // Each field needs one last field_purge_batch() call to remove the last
- // instance and the field itself.
+ // Each field possibly needs one last field_purge_batch() call to remove the
+ // last instance and the field itself.
$context['sandbox']['field']['steps_to_delete'] += count($fields);
$context['sandbox']['field']['current_progress'] = 0;
diff --git a/core/modules/field/src/Entity/FieldConfig.php b/core/modules/field/src/Entity/FieldConfig.php
index 8235487..af025e3 100644
--- a/core/modules/field/src/Entity/FieldConfig.php
+++ b/core/modules/field/src/Entity/FieldConfig.php
@@ -293,7 +293,7 @@ class FieldConfig extends ConfigEntityBase implements FieldConfigInterface {
$this->settings += $field_type_manager->getDefaultSettings($this->type);
// Notify the entity storage.
- $entity_manager->getStorage($this->entity_type)->onFieldCreate($this);
+ $entity_manager->getStorage($this->entity_type)->onFieldStorageDefinitionCreate($this);
}
/**
@@ -339,7 +339,7 @@ class FieldConfig extends ConfigEntityBase implements FieldConfigInterface {
// Notify the storage. The controller can reject the definition
// update as invalid by raising an exception, which stops execution before
// the definition is written to config.
- $entity_manager->getStorage($this->entity_type)->onFieldUpdate($this);
+ $entity_manager->getStorage($this->entity_type)->onFieldStorageDefinitionUpdate($this, $this->original);
}
/**
@@ -408,7 +408,7 @@ class FieldConfig extends ConfigEntityBase implements FieldConfigInterface {
// Notify the storage.
foreach ($fields as $field) {
if (!$field->deleted) {
- \Drupal::entityManager()->getStorage($field->entity_type)->onFieldDelete($field);
+ \Drupal::entityManager()->getStorage($field->entity_type)->onFieldStorageDefinitionDelete($field);
$field->deleted = TRUE;
}
}
@@ -631,59 +631,7 @@ class FieldConfig extends ConfigEntityBase implements FieldConfigInterface {
* TRUE if the field has data for any entity; FALSE otherwise.
*/
public function hasData() {
- return $this->entityCount(TRUE);
- }
-
- /**
- * Determines the number of entities that have field data.
- *
- * @param bool $as_bool
- * (Optional) Optimises query for hasData(). Defaults to FALSE.
- *
- * @return bool|int
- * The number of entities that have field data. If $as_bool parameter is
- * TRUE then the value will either be TRUE or FALSE.
- */
- public function entityCount($as_bool = FALSE) {
- $count = 0;
- $factory = \Drupal::service('entity.query');
- $entity_type = \Drupal::entityManager()->getDefinition($this->entity_type);
- // Entity Query throws an exception if there is no base table.
- if ($entity_type->getBaseTable()) {
- if ($this->deleted) {
- $query = $factory->get($this->entity_type)
- ->condition('id:' . $this->uuid() . '.deleted', 1);
- }
- elseif ($this->getBundles()) {
- $storage_details = $this->getSchema();
- $columns = array_keys($storage_details['columns']);
- $query = $factory->get($this->entity_type);
- $group = $query->orConditionGroup();
- foreach ($columns as $column) {
- $group->exists($this->name . '.' . $column);
- }
- $query = $query->condition($group);
- }
-
- if (isset($query)) {
- $query
- ->count()
- ->accessCheck(FALSE);
- // If we are performing the query just to check if the field has data
- // limit the number of rows returned by the subquery.
- if ($as_bool) {
- $query->range(0, 1);
- }
- $count = $query->execute();
- }
- }
-
- if ($as_bool) {
- return (bool) $count;
- }
- else {
- return (int) $count;
- }
+ return \Drupal::entityManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
}
/**
@@ -763,6 +711,13 @@ class FieldConfig extends ConfigEntityBase implements FieldConfigInterface {
}
/**
+ * {@inheritdoc}
+ */
+ public function getUniqueStorageIdentifier() {
+ return $this->uuid();
+ }
+
+ /**
* Helper to retrieve the field item class.
*/
protected function getFieldItemClass() {
diff --git a/core/modules/field/src/Entity/FieldInstanceConfig.php b/core/modules/field/src/Entity/FieldInstanceConfig.php
index f6d62f7..25fd4aa 100644
--- a/core/modules/field/src/Entity/FieldInstanceConfig.php
+++ b/core/modules/field/src/Entity/FieldInstanceConfig.php
@@ -336,7 +336,7 @@ class FieldInstanceConfig extends ConfigEntityBase implements FieldInstanceConfi
// Set the default instance settings.
$this->settings += $field_type_manager->getDefaultInstanceSettings($field->type);
// Notify the entity storage.
- $entity_manager->getStorage($this->entity_type)->onInstanceCreate($this);
+ $entity_manager->getStorage($this->entity_type)->onFieldDefinitionCreate($this);
}
else {
// Some updates are always disallowed.
@@ -352,7 +352,7 @@ class FieldInstanceConfig extends ConfigEntityBase implements FieldInstanceConfi
// Set the default instance settings.
$this->settings += $field_type_manager->getDefaultInstanceSettings($field->type);
// Notify the entity storage.
- $entity_manager->getStorage($this->entity_type)->onInstanceUpdate($this);
+ $entity_manager->getStorage($this->entity_type)->onFieldDefinitionUpdate($this, $this->original);
}
if (!$this->isSyncing()) {
// Ensure the correct dependencies are present.
@@ -423,7 +423,7 @@ class FieldInstanceConfig extends ConfigEntityBase implements FieldInstanceConfi
// Notify the entity storage.
foreach ($instances as $instance) {
if (!$instance->deleted) {
- \Drupal::entityManager()->getStorage($instance->entity_type)->onInstanceDelete($instance);
+ \Drupal::entityManager()->getStorage($instance->entity_type)->onFieldDefinitionDelete($instance);
}
}
@@ -606,6 +606,13 @@ class FieldInstanceConfig extends ConfigEntityBase implements FieldInstanceConfi
/**
* {@inheritdoc}
*/
+ public function getBundle() {
+ return $this->bundle;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function allowBundleRename() {
$this->bundle_rename_allowed = TRUE;
}
diff --git a/core/modules/field/src/FieldConfigUpdateForbiddenException.php b/core/modules/field/src/FieldConfigUpdateForbiddenException.php
deleted file mode 100644
index 412e146..0000000
--- a/core/modules/field/src/FieldConfigUpdateForbiddenException.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\field\FieldConfigUpdateForbiddenException.
- */
-
-namespace Drupal\field;
-
-/**
- * Exception class thrown by hook_field_config_update_forbid().
- */
-class FieldConfigUpdateForbiddenException extends FieldException {}
diff --git a/core/modules/field/src/FieldInstanceConfigStorage.php b/core/modules/field/src/FieldInstanceConfigStorage.php
index 42b258e..742319d 100644
--- a/core/modules/field/src/FieldInstanceConfigStorage.php
+++ b/core/modules/field/src/FieldInstanceConfigStorage.php
@@ -161,7 +161,10 @@ class FieldInstanceConfigStorage extends ConfigEntityStorage {
}
}
- $matching_instances[] = $instance;
+ // When returning deleted instances, key the results by UUID since they
+ // can include several instances with the same ID.
+ $key = $include_deleted ? $instance->uuid() : $instance->id();
+ $matching_instances[$key] = $instance;
}
return $matching_instances;
diff --git a/core/modules/field/src/Tests/BulkDeleteTest.php b/core/modules/field/src/Tests/BulkDeleteTest.php
index cb0e8f9..8e96eab 100644
--- a/core/modules/field/src/Tests/BulkDeleteTest.php
+++ b/core/modules/field/src/Tests/BulkDeleteTest.php
@@ -182,7 +182,7 @@ class BulkDeleteTest extends FieldUnitTestBase {
// The instance still exists, deleted.
$instances = entity_load_multiple_by_properties('field_instance_config', array('field_id' => $field->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE));
$this->assertEqual(count($instances), 1, 'There is one deleted instance');
- $instance = $instances[0];
+ $instance = $instances[$instance->uuid()];
$this->assertEqual($instance->bundle, $bundle, 'The deleted instance is for the correct bundle');
// Check that the actual stored content did not change during delete.
@@ -306,9 +306,16 @@ class BulkDeleteTest extends FieldUnitTestBase {
}
$this->checkHooksInvocations($hooks, $actual_hooks);
+ // The instance still exists, deleted.
+ $instances = entity_load_multiple_by_properties('field_instance_config', array('uuid' => $instance->uuid(), 'include_deleted' => TRUE));
+ $this->assertTrue(isset($instances[$instance->uuid()]) && $instances[$instance->uuid()]->deleted, 'The instance exists and is deleted');
+
// Purge again to purge the instance.
field_purge_batch(0);
+ // The instance is gone.
+ $instances = entity_load_multiple_by_properties('field_instance_config', array('uuid' => $instance->uuid(), 'include_deleted' => TRUE));
+ $this->assertEqual(count($instances), 0, 'The instance is purged.');
// The field still exists, not deleted.
$fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE));
$this->assertTrue(isset($fields[$field->uuid()]) && !$fields[$field->uuid()]->deleted, 'The field exists and is not deleted');
@@ -334,14 +341,18 @@ class BulkDeleteTest extends FieldUnitTestBase {
}
$this->checkHooksInvocations($hooks, $actual_hooks);
- // The field still exists, deleted.
+ // The field and instance still exist, deleted.
+ $instances = entity_load_multiple_by_properties('field_instance_config', array('uuid' => $instance->uuid(), 'include_deleted' => TRUE));
+ $this->assertTrue(isset($instances[$instance->uuid()]) && $instances[$instance->uuid()]->deleted, 'The instance exists and is deleted');
$fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE));
$this->assertTrue(isset($fields[$field->uuid()]) && $fields[$field->uuid()]->deleted, 'The field exists and is deleted');
// Purge again to purge the instance and the field.
field_purge_batch(0);
- // The field is gone.
+ // The field and instance are gone.
+ $instances = entity_load_multiple_by_properties('field_instance_config', array('uuid' => $instance->uuid(), 'include_deleted' => TRUE));
+ $this->assertEqual(count($instances), 0, 'The instance is purged.');
$fields = entity_load_multiple_by_properties('field_config', array('uuid' => $field->uuid(), 'include_deleted' => TRUE));
$this->assertEqual(count($fields), 0, 'The field is purged.');
}
diff --git a/core/modules/field/src/Tests/CrudTest.php b/core/modules/field/src/Tests/CrudTest.php
index 782897c..6a16101 100644
--- a/core/modules/field/src/Tests/CrudTest.php
+++ b/core/modules/field/src/Tests/CrudTest.php
@@ -8,6 +8,7 @@
namespace Drupal\field\Tests;
use Drupal\Core\Entity\EntityStorageException;
+use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\FieldException;
@@ -440,7 +441,7 @@ class CrudTest extends FieldUnitTestBase {
$field->save();
$this->pass(t("A changeable setting can be updated."));
}
- catch (FieldException $e) {
+ catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->fail(t("An unchangeable setting cannot be updated."));
}
$field->settings['unchangeable']++;
@@ -448,7 +449,7 @@ class CrudTest extends FieldUnitTestBase {
$field->save();
$this->fail(t("An unchangeable setting can be updated."));
}
- catch (FieldException $e) {
+ catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass(t("An unchangeable setting cannot be updated."));
}
}
diff --git a/core/modules/field/src/Tests/FieldEntityCountTest.php b/core/modules/field/src/Tests/FieldDataCountTest.php
index b55619f..3275b47 100644
--- a/core/modules/field/src/Tests/FieldEntityCountTest.php
+++ b/core/modules/field/src/Tests/FieldDataCountTest.php
@@ -2,7 +2,7 @@
/**
* @file
- * Contains \Drupal\field\Tests\FieldEntityCountTest.
+ * Contains \Drupal\field\Tests\FieldDataCountTest.
*/
namespace Drupal\field\Tests;
@@ -10,22 +10,38 @@ namespace Drupal\field\Tests;
use Drupal\Core\Entity\ContentEntityDatabaseStorage;
/**
- * Tests entityCount() and hasData() methods on FieldConfig entity.
+ * Tests counting field data records.
*
- * @see \Drupal\field\Entity\FieldConfig::entityCount()
+ * @see \Drupal\Core\Entity\FieldableEntityStorageInterface::countFieldData()
* @see \Drupal\field\Entity\FieldConfig::hasData()
*/
-class FieldEntityCountTest extends FieldUnitTestBase {
+class FieldDataCountTest extends FieldUnitTestBase {
+ /**
+ * @var \Drupal\Core\Entity\FieldableEntityStorageInterface
+ */
+ protected $storage;
+
+ /**
+ * {@inheritdoc}
+ */
public static function getInfo() {
return array(
- 'name' => 'Field config entityCount() and hasData() tests.',
- 'description' => 'Tests entityCount() and hasData() methods on FieldConfig entity.',
+ 'name' => 'Field config hasData() tests.',
+ 'description' => 'Tests counting field data records and the hasData() method on FieldConfig entity.',
'group' => 'Field API',
);
}
/**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ parent::setUp();
+ $this->storage = \Drupal::entityManager()->getStorage('entity_test');
+ }
+
+ /**
* Tests entityCount() and hadData() methods.
*/
public function testEntityCountAndHasData() {
@@ -45,7 +61,7 @@ class FieldEntityCountTest extends FieldUnitTestBase {
))->save();
$this->assertIdentical($field->hasdata(), FALSE, 'There are no entities with field data.');
- $this->assertIdentical($field->entityCount(), 0, 'There are 0 entities with field data.');
+ $this->assertIdentical($this->storage->countFieldData($field), 0, 'There are 0 entities with field data.');
// Create 1 entity without the field.
$entity = entity_create('entity_test');
@@ -53,7 +69,7 @@ class FieldEntityCountTest extends FieldUnitTestBase {
$entity->save();
$this->assertIdentical($field->hasdata(), FALSE, 'There are no entities with field data.');
- $this->assertIdentical($field->entityCount(), 0, 'There are 0 entities with field data.');
+ $this->assertIdentical($this->storage->countFieldData($field), 0, 'There are 0 entities with field data.');
// Create 12 entities to ensure that the purging works as expected.
for ($i=0; $i < 12; $i++) {
@@ -79,16 +95,16 @@ class FieldEntityCountTest extends FieldUnitTestBase {
}
$this->assertIdentical($field->hasdata(), TRUE, 'There are entities with field data.');
- $this->assertEqual($field->entityCount(), 12, 'There are 12 entities with field data.');
+ $this->assertEqual($this->storage->countFieldData($field), 12, 'There are 12 entities with field data.');
// Ensure the methods work on deleted fields.
$field->delete();
$this->assertIdentical($field->hasdata(), TRUE, 'There are entities with deleted field data.');
- $this->assertEqual($field->entityCount(), 12, 'There are 12 entities with deleted field data.');
+ $this->assertEqual($this->storage->countFieldData($field), 12, 'There are 12 entities with deleted field data.');
field_purge_batch(6);
$this->assertIdentical($field->hasdata(), TRUE, 'There are entities with deleted field data.');
- $this->assertEqual($field->entityCount(), 6, 'There are 6 entities with deleted field data.');
+ $this->assertEqual($this->storage->countFieldData($field), 6, 'There are 6 entities with deleted field data.');
}
}
diff --git a/core/modules/field/tests/modules/field_test/field_test.field.inc b/core/modules/field/tests/modules/field_test/field_test.field.inc
index e43a7e7..93e090b 100644
--- a/core/modules/field/tests/modules/field_test/field_test.field.inc
+++ b/core/modules/field/tests/modules/field_test/field_test.field.inc
@@ -10,7 +10,7 @@ use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\FieldConfigInterface;
-use Drupal\field\FieldConfigUpdateForbiddenException;
+use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
/**
* Implements hook_field_widget_info_alter().
@@ -24,7 +24,7 @@ function field_test_field_widget_info_alter(&$info) {
*/
function field_test_field_config_update_forbid(FieldConfigInterface $field, FieldConfigInterface $prior_field) {
if ($field->getType() == 'test_field' && $field->getSetting('unchangeable') != $prior_field->getSetting('unchangeable')) {
- throw new FieldConfigUpdateForbiddenException("field_test 'unchangeable' setting cannot be changed'");
+ throw new FieldStorageDefinitionUpdateForbiddenException("field_test 'unchangeable' setting cannot be changed'");
}
}
diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install
index 1f5ed4f..2b3d14f 100644
--- a/core/modules/forum/forum.install
+++ b/core/modules/forum/forum.install
@@ -108,10 +108,7 @@ function forum_uninstall() {
}
// Purge field data now to allow taxonomy and options module to be uninstalled
- // if this is the only field remaining. We need to run it twice because
- // field_purge_batch() will not remove the instance and the field in the same
- // pass.
- field_purge_batch(10);
+ // if this is the only field remaining.
field_purge_batch(10);
// Allow to delete a forum's node type.
$locked = \Drupal::state()->get('node.type.locked');
diff --git a/core/modules/options/options.module b/core/modules/options/options.module
index e149477..d9a1b24 100644
--- a/core/modules/options/options.module
+++ b/core/modules/options/options.module
@@ -8,6 +8,7 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\field\FieldConfigInterface;
use Drupal\field\FieldConfigUpdateForbiddenException;
@@ -100,7 +101,7 @@ function options_field_config_update_forbid(FieldConfigInterface $field, FieldCo
$prior_allowed_values = $prior_field->getSetting('allowed_values');
$lost_keys = array_diff(array_keys($prior_allowed_values), array_keys($allowed_values));
if (_options_values_in_use($field->entity_type, $field->getName(), $lost_keys)) {
- throw new FieldConfigUpdateForbiddenException(t('A list field (@field_name) with existing data cannot have its keys changed.', array('@field_name' => $field->getName())));
+ throw new FieldStorageDefinitionUpdateForbiddenException(t('A list field (@field_name) with existing data cannot have its keys changed.', array('@field_name' => $field->getName())));
}
}
}
diff --git a/core/modules/options/src/Tests/OptionsFieldTest.php b/core/modules/options/src/Tests/OptionsFieldTest.php
index a1d2c14..ed775b4 100644
--- a/core/modules/options/src/Tests/OptionsFieldTest.php
+++ b/core/modules/options/src/Tests/OptionsFieldTest.php
@@ -7,7 +7,7 @@
namespace Drupal\options\Tests;
-use Drupal\field\FieldException;
+use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
/**
* Tests for the 'Options' field types.
@@ -50,7 +50,7 @@ class OptionsFieldTest extends OptionsFieldUnitTestBase {
$this->field->save();
$this->fail(t('Cannot update a list field to not include keys with existing data.'));
}
- catch (FieldException $e) {
+ catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass(t('Cannot update a list field to not include keys with existing data.'));
}
// Empty the value, so that we can actually remove the option.
diff --git a/core/modules/path/path.module b/core/modules/path/path.module
index d2c9d58..8721d5e 100644
--- a/core/modules/path/path.module
+++ b/core/modules/path/path.module
@@ -86,7 +86,8 @@ function path_entity_base_field_info(EntityTypeInterface $entity_type) {
'type' => 'path',
'weight' => 30,
))
- ->setDisplayConfigurable('form', TRUE);
+ ->setDisplayConfigurable('form', TRUE)
+ ->setCustomStorage(TRUE);
return $fields;
}
diff --git a/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php b/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php
new file mode 100644
index 0000000..0023397
--- /dev/null
+++ b/core/modules/system/src/Tests/Entity/EntityBundleFieldTest.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\EntityBundleFieldTest.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+/**
+ * Tests adding a custom bundle field.
+ */
+class EntityBundleFieldTest extends EntityUnitTestBase {
+
+ /**
+ * The module handler.
+ *
+ * @var \Drupal\Core\Extension\ModuleHandlerInterface
+ */
+ protected $moduleHandler;
+
+ /**
+ * The database connection used.
+ *
+ * @var \Drupal\Core\Database\Connection
+ */
+ protected $database;
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('menu_link');
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => 'Entity bundle fields',
+ 'description' => 'Tests providing a custom bundle field.',
+ 'group' => 'Entity API',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ parent::setUp();
+ $this->installSchema('user', array('users_data'));
+ $this->installSchema('system', array('router'));
+ $this->moduleHandler = $this->container->get('module_handler');
+ $this->database = $this->container->get('database');
+ }
+
+ /**
+ * Tests the custom bundle field creation and deletion.
+ */
+ public function testCustomBundleFieldCreateDelete() {
+ // Install the module which adds the field.
+ $this->moduleHandler->install(array('entity_bundle_field_test'), FALSE);
+ $definition = $this->entityManager->getFieldDefinitions('entity_test', 'custom')['custom_field'];
+ $this->assertNotNull($definition, 'Field definition found.');
+
+ // Make sure the table has been created.
+ $table = $this->entityManager->getStorage('entity_test')->_fieldTableName($definition);
+ $this->assertTrue($this->database->schema()->tableExists($table), 'Table created');
+ $this->moduleHandler->uninstall(array('entity_bundle_field_test'), FALSE);
+ $this->assertFalse($this->database->schema()->tableExists($table), 'Table dropped');
+ }
+
+ /**
+ * Tests making use of a custom bundle field.
+ */
+ public function testCustomBundleFieldUsage() {
+ // Check that an entity with bundle entity_test does not have the custom
+ // field.
+ $this->moduleHandler->install(array('entity_bundle_field_test'), FALSE);
+ $storage = $this->entityManager->getStorage('entity_test');
+ $entity = $storage->create([
+ 'type' => 'entity_test',
+ ]);
+ $this->assertFalse($entity->hasField('custom_field'));
+
+ // Check that the custom bundle has the defined custom field and check
+ // saving and deleting of custom field data.
+ $entity = $storage->create([
+ 'type' => 'custom',
+ ]);
+ $this->assertTrue($entity->hasField('custom_field'));
+ $entity->custom_field->value = 'swanky';
+ $entity->save();
+ $storage->resetCache();
+ $entity = $storage->load($entity->id());
+ $this->assertEqual($entity->custom_field->value, 'swanky', 'Entity was saved correct.y');
+
+ $entity->custom_field->value = 'cozy';
+ $entity->save();
+ $storage->resetCache();
+ $entity = $storage->load($entity->id());
+ $this->assertEqual($entity->custom_field->value, 'cozy', 'Entity was updated correctly.');
+
+ $entity->delete();
+ $table = $storage->_fieldTableName($entity->getFieldDefinition('custom_field'));
+ $result = $this->database->select($table, 'f')
+ ->fields('f')
+ ->condition('f.entity_id', $entity->id())
+ ->execute();
+ $this->assertFalse($result->fetchAssoc(), 'Field data has been deleted');
+
+ // Create another entity to test that values are marked as deleted when a
+ // bundle is deleted.
+ $entity = $storage->create(['type' => 'custom', 'custom_field' => 'new']);
+ $entity->save();
+ entity_test_delete_bundle('custom');
+
+ $table = $storage->_fieldTableName($entity->getFieldDefinition('custom_field'));
+ $result = $this->database->select($table, 'f')
+ ->condition('f.entity_id', $entity->id())
+ ->condition('deleted', 1)
+ ->countQuery()
+ ->execute();
+ $this->assertEqual(1, $result->fetchField(), 'Field data has been deleted');
+
+ // @todo Test field purge and table deletion once supported.
+ // $this->assertFalse($this->database->schema()->tableExists($table), 'Custom field table was deleted');
+ }
+
+}
diff --git a/core/modules/system/src/Tests/Entity/FieldSqlStorageTest.php b/core/modules/system/src/Tests/Entity/FieldSqlStorageTest.php
index 9ac6452..28cfed2 100644
--- a/core/modules/system/src/Tests/Entity/FieldSqlStorageTest.php
+++ b/core/modules/system/src/Tests/Entity/FieldSqlStorageTest.php
@@ -9,9 +9,8 @@ namespace Drupal\system\Tests\Entity;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\ContentEntityDatabaseStorage;
-use Drupal\field\FieldException;
+use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\field\Entity\FieldConfig;
-use Drupal\system\Tests\Entity\EntityUnitTestBase;
/**
* Tests field storage.
@@ -322,7 +321,7 @@ class FieldSqlStorageTest extends EntityUnitTestBase {
$field->save();
$this->fail(t('Cannot update field schema with data.'));
}
- catch (FieldException $e) {
+ catch (FieldStorageDefinitionUpdateForbiddenException $e) {
$this->pass(t('Cannot update field schema with data.'));
}
}
@@ -560,9 +559,9 @@ class FieldSqlStorageTest extends EntityUnitTestBase {
'deleted' => TRUE,
));
$expected = 'field_deleted_data_' . substr(hash('sha256', $field->uuid()), 0, 10);
- $this->assertEqual(ContentEntityDatabaseStorage::_fieldTableName($field), $expected);
+ $this->assertEqual(ContentEntityDatabaseStorage::_fieldTableName($field, TRUE), $expected);
$expected = 'field_deleted_revision_' . substr(hash('sha256', $field->uuid()), 0, 10);
- $this->assertEqual(ContentEntityDatabaseStorage::_fieldRevisionTableName($field), $expected);
+ $this->assertEqual(ContentEntityDatabaseStorage::_fieldRevisionTableName($field, TRUE), $expected);
}
}
diff --git a/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.info.yml b/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.info.yml
new file mode 100644
index 0000000..6732090
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.info.yml
@@ -0,0 +1,8 @@
+name: 'Entity bundle field test module'
+type: module
+description: 'Provides a bundle field to the test entity.'
+package: Testing
+version: VERSION
+core: 8.x
+dependencies:
+ - entity_test
diff --git a/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.install b/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.install
new file mode 100644
index 0000000..6065425
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.install
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function entity_bundle_field_test_install() {
+ $manager = \Drupal::entityManager();
+ // Notify the entity storage of our custom field.
+ $definition = $manager->getFieldStorageDefinitions('entity_test')['custom_field'];
+ $manager->getStorage('entity_test')->onFieldStorageDefinitionCreate($definition);
+
+ // Create the custom bundle and put our bundle field on it.
+ entity_test_create_bundle('custom');
+ $definition = $manager->getFieldDefinitions('entity_test', 'custom')['custom_field'];
+ $manager->getStorage('entity_test')->onFieldDefinitionCreate($definition);
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function entity_bundle_field_test_uninstall() {
+ entity_bundle_field_test_is_uninstalling(TRUE);
+ $manager = \Drupal::entityManager();
+ // Notify the entity storage that our field is gone.
+ $definition = $manager->getFieldDefinitions('entity_test', 'custom')['custom_field'];
+ $manager->getStorage('entity_test')->onFieldDefinitionDelete($definition);
+ $storage_definition = $manager->getFieldStorageDefinitions('entity_test')['custom_field'];
+ $manager->getStorage('entity_test')->onFieldStorageDefinitionDelete($storage_definition);
+ $manager->clearCachedFieldDefinitions();
+
+ do {
+ $count = $manager->getStorage('entity_test')->purgeFieldData($definition, 500);
+ }
+ while ($count != 0);
+ $manager->getStorage('entity_test')->finalizePurge($definition);
+}
diff --git a/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.module b/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.module
new file mode 100644
index 0000000..55f5bff
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_bundle_field_test/entity_bundle_field_test.module
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Test module for the entity API providing a bundle field.
+ */
+
+use Drupal\Core\Field\FieldDefinition;
+
+/**
+ * Tracks whether the module is currently being uninstalled.
+ *
+ * @param bool|null $value
+ * (optional) If set, the value any subsequent calls should return.
+ *
+ * @return bool
+ * Whether the module is currently uninstalling.
+ */
+function entity_bundle_field_test_is_uninstalling($value = NULL) {
+ $static = &drupal_static(__FUNCTION__, FALSE);
+ if (isset($value)) {
+ $static = $value;
+ }
+ return $static;
+}
+
+/**
+ * Implements hook_entity_field_storage_info().
+ */
+function entity_bundle_field_test_entity_field_storage_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) {
+ if ($entity_type->id() == 'entity_test' && !entity_bundle_field_test_is_uninstalling()) {
+ // @todo: Make use of a FieldStorageDefinition class instead of
+ // FieldDefinition as this should not implement FieldDefinitionInterface.
+ // See https://drupal.org/node/2280639.
+ $definitions['custom_field'] = FieldDefinition::create('string')
+ ->setName('custom_field')
+ ->setLabel(t('A custom field'))
+ ->setTargetEntityTypeId($entity_type->id());
+ return $definitions;
+ }
+}
+
+/**
+ * Implements hook_entity_bundle_field_info().
+ */
+function entity_bundle_field_test_entity_bundle_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
+ if ($entity_type->id() == 'entity_test' && $bundle == 'custom' && !entity_bundle_field_test_is_uninstalling()) {
+ $definitions['custom_field'] = FieldDefinition::create('string')
+ ->setName('custom_field')
+ ->setLabel(t('A custom field'));
+ return $definitions;
+ }
+}
+
+/**
+ * Implements hook_entity_bundle_delete().
+ */
+function entity_bundle_field_test_entity_bundle_delete($entity_type_id, $bundle) {
+ if ($entity_type_id == 'entity_test' && $bundle == 'custom') {
+ // Notify the entity storage that our field is gone.
+ $field_definition = FieldDefinition::create('string')
+ ->setTargetEntityTypeId($entity_type_id)
+ ->setBundle($bundle)
+ ->setName('custom_field')
+ ->setLabel(t('A custom field'));
+ \Drupal::entityManager()->getStorage('entity_test')
+ ->onFieldDefinitionDelete($field_definition);
+ }
+}
diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install
index f588cd3..30656be 100644
--- a/core/profiles/standard/standard.install
+++ b/core/profiles/standard/standard.install
@@ -78,4 +78,11 @@ function standard_install() {
// Enable the admin theme.
\Drupal::config('node.settings')->set('use_admin_theme', '1')->save();
+
+ // @todo Remove in https://www.drupal.org/node/2295129.
+ // Resave the plain_text formatter so that default filter plugins and
+ // dependencies are calculated correctly. This resolves an issue caused by the
+ // fact that filter is installed before editor but the standard profile also
+ // enables the file module.
+ entity_load('filter_format', 'plain_text')->save();
}
diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php
index 49efb66..c743200 100644
--- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php
@@ -957,14 +957,16 @@ class ContentEntityDatabaseStorageTest extends UnitTestCase {
->will($this->returnValue($this->fieldDefinitions));
// Define a field definition for a test_field field.
- $field = $this->getMock('\Drupal\field\FieldConfigInterface');
+ $field = $this->getMock('\Drupal\Core\Field\FieldStorageDefinitionInterface');
$field->deleted = FALSE;
- $field->entity_type = 'test_entity';
- $field->name = 'test_field';
$field->expects($this->any())
->method('getName')
- ->will($this->returnValue('test'));
+ ->will($this->returnValue('test_field'));
+
+ $field->expects($this->any())
+ ->method('getTargetEntityTypeId')
+ ->will($this->returnValue('test_entity'));
$field_schema = array(
'columns' => array(