Commit 20564b99 authored by catch's avatar catch
Browse files

Issue #2282119 by amateescu, jibran, Jo Fitzgerald, yched, plach, timmillwood,...

Issue #2282119 by amateescu, jibran, Jo Fitzgerald, yched, plach, timmillwood, fago, Sam152, larowlan: Make the Entity Field API handle field purging
parent dbc01628
......@@ -510,12 +510,6 @@ services:
- { name: module_install.uninstall_validator }
arguments: ['@entity.manager', '@string_translation']
lazy: true
field_uninstall_validator:
class: Drupal\Core\Field\FieldModuleUninstallValidator
tags:
- { name: module_install.uninstall_validator }
arguments: ['@entity.manager', '@string_translation']
lazy: true
required_module_uninstall_validator:
class: Drupal\Core\Extension\RequiredModuleUninstallValidator
tags:
......@@ -576,9 +570,12 @@ services:
entity.last_installed_schema.repository:
class: Drupal\Core\Entity\EntityLastInstalledSchemaRepository
arguments: ['@keyvalue']
entity_field.deleted_fields_repository:
class: Drupal\Core\Field\DeletedFieldsRepository
arguments: ['@state']
field_storage_definition.listener:
class: Drupal\Core\Field\FieldStorageDefinitionListener
arguments: ['@entity_type.manager', '@event_dispatcher', '@entity.last_installed_schema.repository', '@entity_field.manager']
arguments: ['@entity_type.manager', '@event_dispatcher', '@entity.last_installed_schema.repository', '@entity_field.manager', '@entity_field.deleted_fields_repository']
field_definition.listener:
class: Drupal\Core\Field\FieldDefinitionListener
arguments: ['@entity_type.manager', '@entity_field.manager', '@keyvalue', '@cache.discovery']
......
......@@ -2,9 +2,7 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldDefinitionListenerInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
/**
......@@ -18,27 +16,4 @@
*/
interface DynamicallyFieldableEntityStorageInterface extends FieldableEntityStorageInterface, FieldStorageDefinitionListenerInterface, FieldDefinitionListenerInterface {
/**
* 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);
/**
* Performs final cleanup after all data of a field has been purged.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field being purged.
*/
public function finalizePurge(FieldStorageDefinitionInterface $storage_definition);
}
......@@ -311,7 +311,8 @@ protected function getChangeList() {
}
// @todo Support deleting entity definitions when we support base field
// purging. See https://www.drupal.org/node/2282119.
// purging.
// @see https://www.drupal.org/node/2907779
$this->entityManager->useCaches(TRUE);
......
......@@ -2,6 +2,9 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* A storage that supports entity types with field definitions.
*/
......@@ -24,4 +27,27 @@ interface FieldableEntityStorageInterface extends EntityStorageInterface {
*/
public function countFieldData($storage_definition, $as_bool = FALSE);
/**
* Purges a batch of field data.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The deleted field whose data is being purged.
* @param int $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);
/**
* Performs final cleanup after all data of a field has been purged.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field storage being purged.
*/
public function finalizePurge(FieldStorageDefinitionInterface $storage_definition);
}
......@@ -272,7 +272,7 @@ public function setExtraColumns($table_name, array $column_names) {
* TRUE if the field can be stored in a dedicated table, FALSE otherwise.
*/
public function allowsSharedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
return !$storage_definition->hasCustomStorage() && $storage_definition->isBaseField() && !$storage_definition->isMultiple();
return !$storage_definition->hasCustomStorage() && $storage_definition->isBaseField() && !$storage_definition->isMultiple() && !$storage_definition->isDeleted();
}
/**
......@@ -331,8 +331,8 @@ public function getReservedColumns() {
public function getDedicatedDataTableName(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 unique storage
// {field_deleted_data_UNIQUE_STORAGE_ID}. To make sure we don't end up
// with 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);
......@@ -357,10 +357,10 @@ public function getDedicatedDataTableName(FieldStorageDefinitionInterface $stora
public function getDedicatedRevisionTableName(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 unique storage
// identifier and return the first 10 characters so we end up with a short
// unique ID.
// {field_deleted_revision_UNIQUE_STORAGE_ID}. To make sure we don't end
// up with 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 {
......@@ -389,7 +389,7 @@ protected function generateFieldTableName(FieldStorageDefinitionInterface $stora
// prefixes.
if (strlen($table_name) > 48) {
// Use a shorter separator, a truncated entity_type, and a hash of the
// field UUID.
// field storage unique identifier.
$separator = $revision ? '_r__' : '__';
// Truncate to the same length for the current and revision tables.
$entity_type = substr($storage_definition->getTargetEntityTypeId(), 0, 34);
......
......@@ -19,7 +19,6 @@
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -1468,9 +1467,7 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
$this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id())
);
// @todo Remove the FieldStorageConfigInterface check when non-configurable
// fields support purging: https://www.drupal.org/node/2282119.
if ($storage_definition instanceof FieldStorageConfigInterface && $table_mapping->requiresDedicatedTableStorage($storage_definition)) {
if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
// Mark all data associated with the field for deletion.
$table = $table_mapping->getDedicatedDataTableName($storage_definition);
$revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
......@@ -1554,9 +1551,8 @@ protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definit
// 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_mapping = $this->getTableMapping();
$table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
$table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $storage_definition->isDeleted());
// Get the entities which we want to purge first.
$entity_query = $this->database->select($table_name, 't', ['fetch' => \PDO::FETCH_ASSOC]);
......@@ -1614,7 +1610,7 @@ protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definit
*/
protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefinitionInterface $field_definition) {
$storage_definition = $field_definition->getFieldStorageDefinition();
$is_deleted = $this->storageDefinitionIsDeleted($storage_definition);
$is_deleted = $storage_definition->isDeleted();
$table_mapping = $this->getTableMapping();
$table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
$revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $is_deleted);
......@@ -1651,7 +1647,7 @@ public function countFieldData($storage_definition, $as_bool = FALSE) {
$table_mapping = $this->getTableMapping($storage_definitions);
if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
$is_deleted = $this->storageDefinitionIsDeleted($storage_definition);
$is_deleted = $storage_definition->isDeleted();
if ($this->entityType->isRevisionable()) {
$table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $is_deleted);
}
......@@ -1724,15 +1720,14 @@ public function countFieldData($storage_definition, $as_bool = FALSE) {
*
* @return bool
* Whether the field has been already deleted.
*
* @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use
* \Drupal\Core\Field\FieldStorageDefinitionInterface::isDeleted() instead.
*
* @see https://www.drupal.org/node/2907785
*/
protected function storageDefinitionIsDeleted(FieldStorageDefinitionInterface $storage_definition) {
// Configurable fields are marked for deletion.
if ($storage_definition instanceof FieldStorageConfigInterface) {
return $storage_definition->isDeleted();
}
// For non configurable fields check whether they are still in the last
// installed schema repository.
return !array_key_exists($storage_definition->getName(), $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityTypeId));
return $storage_definition->isDeleted();
}
}
......@@ -3,7 +3,7 @@
namespace Drupal\Core\Entity\Sql;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\DatabaseException;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
......@@ -15,7 +15,7 @@
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\Core\Language\LanguageInterface;
/**
* Defines a schema handler that supports revisionable, translatable entities.
......@@ -86,6 +86,13 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
*/
protected $installedStorageSchema;
/**
* The deleted fields repository.
*
* @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface
*/
protected $deletedFieldsRepository;
/**
* Constructs a SqlContentEntityStorageSchema.
*
......@@ -122,6 +129,23 @@ protected function installedStorageSchema() {
return $this->installedStorageSchema;
}
/**
* Gets the deleted fields repository.
*
* @return \Drupal\Core\Field\DeletedFieldsRepositoryInterface
* The deleted fields repository.
*
* @todo Inject this dependency in the constructor once this class can be
* instantiated as a regular entity handler:
* https://www.drupal.org/node/2332857.
*/
protected function deletedFieldsRepository() {
if (!isset($this->deletedFieldsRepository)) {
$this->deletedFieldsRepository = \Drupal::service('entity_field.deleted_fields_repository');
}
return $this->deletedFieldsRepository;
}
/**
* {@inheritdoc}
*/
......@@ -410,25 +434,17 @@ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $
public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
// Store original definitions so that switching between shared and dedicated
// field table layout works.
$this->originalDefinitions = $this->fieldStorageDefinitions;
$this->originalDefinitions[$original->getName()] = $original;
$this->performFieldSchemaOperation('update', $storage_definition, $original);
$this->originalDefinitions = NULL;
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
// Only configurable fields currently support purging, so prevent deletion
// of ones we can't purge if they have existing data.
// @todo Add purging to all fields: https://www.drupal.org/node/2282119.
try {
if (!($storage_definition instanceof FieldStorageConfigInterface) && $this->storage->countFieldData($storage_definition, TRUE)) {
throw new FieldStorageDefinitionUpdateForbiddenException('Unable to delete a field (' . $storage_definition->getName() . ' in ' . $storage_definition->getTargetEntityTypeId() . ' entity) with data that cannot be purged.');
}
$has_data = $this->storage->countFieldData($storage_definition, TRUE);
}
catch (DatabaseException $e) {
catch (DatabaseExceptionWrapper $e) {
// This may happen when changing field storage schema, since we are not
// able to use a table mapping matching the passed storage definition.
// @todo Revisit this once we are able to instantiate the table mapping
......@@ -436,10 +452,22 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
return;
}
// If the field storage does not have any data, we can safely delete its
// schema.
if (!$has_data) {
$this->performFieldSchemaOperation('delete', $storage_definition);
return;
}
// There's nothing else we can do if the field storage has a custom storage.
if ($storage_definition->hasCustomStorage()) {
return;
}
// Retrieve a table mapping which contains the deleted field still.
$table_mapping = $this->storage->getTableMapping(
$this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id())
);
$storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id());
$table_mapping = $this->storage->getTableMapping($storage_definitions);
if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
// Move the table to a unique name while the table contents are being
// deleted.
......@@ -452,11 +480,72 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
$this->database->schema()->renameTable($revision_table, $revision_new_table);
}
}
else {
// Move the field data from the shared table to a dedicated one in order
// to allow it to be purged like any other field.
$shared_table_field_columns = $table_mapping->getColumnNames($storage_definition->getName());
// @todo Remove when finalizePurge() is invoked from the outside for all
// fields: https://www.drupal.org/node/2282119.
if (!($storage_definition instanceof FieldStorageConfigInterface)) {
$this->performFieldSchemaOperation('delete', $storage_definition);
// Refresh the table mapping to use the deleted storage definition.
$deleted_storage_definition = $this->deletedFieldsRepository()->getFieldStorageDefinitions()[$storage_definition->getUniqueStorageIdentifier()];
$original_storage_definitions = [$storage_definition->getName() => $deleted_storage_definition] + $storage_definitions;
$table_mapping = $this->storage->getTableMapping($original_storage_definitions);
$dedicated_table_field_schema = $this->getDedicatedTableSchema($deleted_storage_definition);
$dedicated_table_field_columns = $table_mapping->getColumnNames($deleted_storage_definition->getName());
$dedicated_table_name = $table_mapping->getDedicatedDataTableName($deleted_storage_definition, TRUE);
$dedicated_table_name_mapping[$table_mapping->getDedicatedDataTableName($deleted_storage_definition)] = $dedicated_table_name;
if ($this->entityType->isRevisionable()) {
$dedicated_revision_table_name = $table_mapping->getDedicatedRevisionTableName($deleted_storage_definition, TRUE);
$dedicated_table_name_mapping[$table_mapping->getDedicatedRevisionTableName($deleted_storage_definition)] = $dedicated_revision_table_name;
}
// Create the dedicated field tables using "deleted" table names.
foreach ($dedicated_table_field_schema as $name => $table) {
if (!$this->database->schema()->tableExists($dedicated_table_name_mapping[$name])) {
$this->database->schema()->createTable($dedicated_table_name_mapping[$name], $table);
}
else {
throw new EntityStorageException('The field ' . $storage_definition->getName() . ' has already been deleted and it is in the process of being purged.');
}
}
if ($this->database->supportsTransactionalDDL()) {
// If the database supports transactional DDL, we can go ahead and rely
// on it. If not, we will have to rollback manually if something fails.
$transaction = $this->database->startTransaction();
}
try {
// Copy the data from the base table.
$is_translatable = $this->entityType->isTranslatable() && $storage_definition->isTranslatable();
$base_table = $is_translatable ? $this->storage->getDataTable() : $this->storage->getBaseTable();
$this->database->insert($dedicated_table_name)
->from($this->getSelectQueryForFieldStorageDeletion($base_table, $shared_table_field_columns, $dedicated_table_field_columns))
->execute();
// Copy the data from the revision table.
if (isset($dedicated_revision_table_name)) {
$revision_table = $is_translatable ? $this->storage->getRevisionDataTable() : $this->storage->getRevisionTable();
$this->database->insert($dedicated_revision_table_name)
->from($this->getSelectQueryForFieldStorageDeletion($revision_table, $shared_table_field_columns, $dedicated_table_field_columns, $base_table))
->execute();
}
}
catch (\Exception $e) {
if (isset($transaction)) {
$transaction->rollBack();
}
else {
// Delete the dedicated tables.
foreach ($dedicated_table_field_schema as $name => $table) {
$this->database->schema()->dropTable($dedicated_table_name_mapping[$name]);
}
}
throw $e;
}
// Delete the field from the shared tables.
$this->deleteSharedTableSchema($storage_definition);
}
}
......@@ -467,6 +556,77 @@ public function finalizePurge(FieldStorageDefinitionInterface $storage_definitio
$this->performFieldSchemaOperation('delete', $storage_definition);
}
/**
* Returns a SELECT query suitable for inserting data into a dedicated table.
*
* @param string $table_name
* The entity table name to select from.
* @param array $shared_table_field_columns
* An array of field column names for a shared table schema.
* @param array $dedicated_table_field_columns
* An array of field column names for a dedicated table schema.
* @param string $base_table
* (optional) The name of the base entity table. Defaults to NULL.
*
* @return \Drupal\Core\Database\Query\SelectInterface
* A database select query.
*/
protected function getSelectQueryForFieldStorageDeletion($table_name, array $shared_table_field_columns, array $dedicated_table_field_columns, $base_table = NULL) {
// Create a SELECT query that generates a result suitable for writing into
// a dedicated field table.
$select = $this->database->select($table_name, 'entity_table');
// Add the bundle column.
if ($bundle = $this->entityType->getKey('bundle')) {
if ($base_table) {
$select->join($base_table, 'base_table', "entity_table.{$this->entityType->getKey('id')} = %alias.{$this->entityType->getKey('id')}");
$select->addField('base_table', $bundle, 'bundle');
}
else {
$select->addField('entity_table', $bundle, 'bundle');
}
}
else {
$select->addExpression(':bundle', 'bundle', [':bundle' => $this->entityType->id()]);
}
// Add the deleted column.
$select->addExpression(':deleted', 'deleted', [':deleted' => 1]);
// Add the entity_id column.
$select->addField('entity_table', $this->entityType->getKey('id'), 'entity_id');
// Add the revision_id column.
if ($this->entityType->isRevisionable()) {
$select->addField('entity_table', $this->entityType->getKey('revision'), 'revision_id');
}
else {
$select->addField('entity_table', $this->entityType->getKey('id'), 'revision_id');
}
// Add the langcode column.
if ($langcode = $this->entityType->getKey('langcode')) {
$select->addField('entity_table', $langcode, 'langcode');
}
else {
$select->addExpression(':langcode', 'langcode', [':langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED]);
}
// Add the delta column and set it to 0 because we are only dealing with
// single cardinality fields.
$select->addExpression(':delta', 'delta', [':delta' => 0]);
// Add all the dynamic field columns.
foreach ($shared_table_field_columns as $field_column_name => $schema_column_name) {
$select->addField('entity_table', $schema_column_name, $dedicated_table_field_columns[$field_column_name]);
}
// Lock the table rows.
$select->forUpdate(TRUE);
return $select;
}
/**
* Checks that we are dealing with the correct entity type.
*
......@@ -1231,17 +1391,13 @@ protected function createSharedTableSchema(FieldStorageDefinitionInterface $stor
* The storage definition of the field being deleted.
*/
protected function deleteDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition) {
// When switching from dedicated to shared field table layout we need need
// to delete the field tables with their regular names. When this happens
// original definitions will be defined.
$deleted = !$this->originalDefinitions;
$table_mapping = $this->storage->getTableMapping();
$table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $deleted);
$table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $storage_definition->isDeleted());
if ($this->database->schema()->tableExists($table_name)) {
$this->database->schema()->dropTable($table_name);
}
if ($this->entityType->isRevisionable()) {
$revision_table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $deleted);
$revision_table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $storage_definition->isDeleted());
if ($this->database->schema()->tableExists($revision_table_name)) {
$this->database->schema()->dropTable($revision_table_name);
}
......
......@@ -415,16 +415,11 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
$update_manager->uninstallEntityType($entity_type);
}
elseif ($entity_type->entityClassImplements(FieldableEntityInterface::CLASS)) {
// The module being installed may be adding new fields to existing
// entity types. Field definitions for any entity type defined by
// the module are handled in the if branch.
$entity_type_id = $entity_type->id();
/** @var \Drupal\Core\Entity\FieldableEntityStorageInterface $storage */
$storage = $entity_manager->getStorage($entity_type_id);
foreach ($entity_manager->getFieldStorageDefinitions($entity_type_id) as $storage_definition) {
// @todo We need to trigger field purging here.
// See https://www.drupal.org/node/2282119.
if ($storage_definition->getProvider() == $module && !$storage->countFieldData($storage_definition, TRUE)) {
// The module being uninstalled might have added new fields to
// existing entity types. This will add them to the deleted fields
// repository so their data will be purged on cron.
foreach ($entity_manager->getFieldStorageDefinitions($entity_type->id()) as $storage_definition) {
if ($storage_definition->getProvider() == $module) {
$update_manager->uninstallFieldStorageDefinition($storage_definition);
}
}
......
......@@ -801,6 +801,39 @@ public function getUniqueStorageIdentifier() {
return $this->getTargetEntityTypeId() . '-' . $this->getName();
}
/**
* {@inheritdoc}
*/
public function getUniqueIdentifier() {
// If we have a specified target bundle, we're dealing with a bundle base
// field definition, so we need to include it in the unique identifier.
if ($this->getTargetBundle()) {
return $this->getTargetEntityTypeId() . '-' . $this->getTargetBundle() . '-' . $this->getName();
}
return $this->getUniqueStorageIdentifier();
}
/**
* {@inheritdoc}
*/
public function isDeleted() {
return !empty($this->definition['deleted']);
}
/**
* Sets whether the field storage is deleted.
*
* @param bool $deleted
* Whether the field storage is deleted.
*
* @return $this
*/
public function setDeleted($deleted) {
$this->definition['deleted'] = $deleted;
return $this;
}
/**
* {@inheritdoc}
*/
......
<?php
namespace Drupal\Core\Field;
use Drupal\Core\State\StateInterface;
/**
* Provides a repository for deleted field and field storage objects.
*
* @internal
*/
class DeletedFieldsRepository implements DeletedFieldsRepositoryInterface {
/**