Commit 32d5530e authored by alexpott's avatar alexpott

Issue #2333113 by effulgentsia, plach: Add an EntityDefinitionUpdateManager so...

Issue #2333113 by effulgentsia, plach: Add an EntityDefinitionUpdateManager so that entity handlers can respond (e.g., by updating db schema) to code updates in a controlled way (e.g., from update.php).
parent 22c1362c
......@@ -286,10 +286,18 @@ services:
arguments: ['@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@router.builder']
entity.manager:
class: Drupal\Core\Entity\EntityManager
arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager']
arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@entity.definitions.installed']
parent: container.trait
tags:
- { name: plugin_manager_cache_clear }
entity.definitions.installed:
class: Drupal\Core\KeyValueStore\KeyValueStoreInterface
factory_method: get
factory_service: keyvalue
arguments: ['entity.definitions.installed']
entity.definition_update_manager:
class: Drupal\Core\Entity\EntityDefinitionUpdateManager
arguments: ['@entity.manager']
entity.form_builder:
class: Drupal\Core\Entity\EntityFormBuilder
arguments: ['@entity.manager', '@form_builder']
......
......@@ -10,14 +10,8 @@
use Drupal\Component\Graph\Graph;
use Drupal\Component\Utility\String;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\ConfigException;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Page\DefaultHtmlPageRenderer;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Utility\Error;
use Drupal\Component\Uuid\Uuid;
use Drupal\Component\Utility\NestedArray;
use Symfony\Component\HttpFoundation\Request;
/**
* Disables any extensions that are incompatible with the current core version.
......@@ -257,6 +251,33 @@ function update_do_one($module, $number, $dependency_map, &$context) {
$context['message'] = 'Updating ' . String::checkPlain($module) . ' module';
}
/**
* Performs entity definition updates, which can trigger schema updates.
*
* @param $module
* The module whose update will be run.
* @param $number
* The update number to run.
* @param $context
* The batch context array.
*/
function update_entity_definitions($module, $number, &$context) {
try {
\Drupal::service('entity.definition_update_manager')->applyUpdates();
}
catch (EntityStorageException $e) {
watchdog_exception('update', $e);
$variables = Error::decodeException($e);
unset($variables['backtrace']);
// The exception message is run through
// \Drupal\Component\Utility\String::checkPlain() by
// \Drupal\Core\Utility\Error::decodeException().
$ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables));
$context['results'][$module][$number] = $ret;
$context['results']['#abort'][] = 'update_entity_definitions';
}
}
/**
* Returns a list of all the pending database updates.
*
......
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityDefinitionUpdateManager.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
use Drupal\Core\Entity\Schema\FieldableEntityStorageSchemaInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Manages entity definition updates.
*/
class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInterface {
use StringTranslationTrait;
/**
* Indicates that a definition has just been created.
*
* @var int
*/
const DEFINITION_CREATED = 1;
/**
* Indicates that a definition has changes.
*
* @var int
*/
const DEFINITION_UPDATED = 2;
/**
* Indicates that a definition has just been deleted.
*
* @var int
*/
const DEFINITION_DELETED = 3;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new EntityDefinitionUpdateManager.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public function needsUpdates() {
return (bool) $this->getChangeList();
}
/**
* {@inheritdoc}
*/
public function getChangeSummary() {
$summary = array();
foreach ($this->getChangeList() as $entity_type_id => $change_list) {
// Process entity type definition changes.
if (!empty($change_list['entity_type']) && $change_list['entity_type'] == static::DEFINITION_UPDATED) {
$entity_type = $this->entityManager->getDefinition($entity_type_id);
$summary[$entity_type_id][] = $this->t('Update the %entity_type entity type.', array('%entity_type' => $entity_type->getLabel()));
}
// Process field storage definition changes.
if (!empty($change_list['field_storage_definitions'])) {
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
$original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
switch ($change) {
case static::DEFINITION_CREATED:
$summary[$entity_type_id][] = $this->t('Create the %field_name field.', array('%field_name' => $storage_definitions[$field_name]->getLabel()));
break;
case static::DEFINITION_UPDATED:
$summary[$entity_type_id][] = $this->t('Update the %field_name field.', array('%field_name' => $storage_definitions[$field_name]->getLabel()));
break;
case static::DEFINITION_DELETED:
$summary[$entity_type_id][] = $this->t('Delete the %field_name field.', array('%field_name' => $original_storage_definitions[$field_name]->getLabel()));
break;
}
}
}
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function applyUpdates() {
foreach ($this->getChangeList() as $entity_type_id => $change_list) {
// Process entity type definition changes.
if (!empty($change_list['entity_type']) && $change_list['entity_type'] == static::DEFINITION_UPDATED) {
$entity_type = $this->entityManager->getDefinition($entity_type_id);
$original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
$this->entityManager->onEntityTypeUpdate($entity_type, $original);
}
// Process field storage definition changes.
if (!empty($change_list['field_storage_definitions'])) {
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
$original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
switch ($change) {
case static::DEFINITION_CREATED:
$this->entityManager->onFieldStorageDefinitionCreate($storage_definitions[$field_name]);
break;
case static::DEFINITION_UPDATED:
$this->entityManager->onFieldStorageDefinitionUpdate($storage_definitions[$field_name], $original_storage_definitions[$field_name]);
break;
case static::DEFINITION_DELETED:
$this->entityManager->onFieldStorageDefinitionDelete($original_storage_definitions[$field_name]);
break;
}
}
}
}
}
/**
* Returns a list of changes to entity type and field storage definitions.
*
* @return array
* An associative array keyed by entity type id of change descriptors. Every
* entry is an associative array with the following optional keys:
* - entity_type: a scalar having only the DEFINITION_UPDATED value.
* - field_storage_definitions: an associative array keyed by field name of
* scalars having one value among:
* - DEFINITION_CREATED
* - DEFINITION_UPDATED
* - DEFINITION_DELETED
*/
protected function getChangeList() {
$this->entityManager->clearCachedDefinitions();
$change_list = array();
foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
$original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
// Only manage changes to already installed entity types. Entity type
// installation is handled elsewhere (e.g.,
// \Drupal\Core\Extension\ModuleHandler::install()).
if (!$original) {
continue;
}
// @todo Support non-storage-schema-changing definition updates too:
// https://www.drupal.org/node/2336895.
if ($this->requiresEntityStorageSchemaChanges($entity_type, $original)) {
$change_list[$entity_type_id]['entity_type'] = static::DEFINITION_UPDATED;
}
if ($entity_type->isFieldable()) {
$field_changes = array();
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
$original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
// Detect created field storage definitions.
foreach (array_diff_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
$field_changes[$field_name] = static::DEFINITION_CREATED;
}
// Detect deleted field storage definitions.
foreach (array_diff_key($original_storage_definitions, $storage_definitions) as $field_name => $original_storage_definition) {
$field_changes[$field_name] = static::DEFINITION_DELETED;
}
// Detect updated field storage definitions.
foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
// @todo Support non-storage-schema-changing definition updates too:
// https://www.drupal.org/node/2336895.
if ($this->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) {
$field_changes[$field_name] = static::DEFINITION_UPDATED;
}
}
if ($field_changes) {
$change_list[$entity_type_id]['field_storage_definitions'] = $field_changes;
}
}
}
return array_filter($change_list);
}
/**
* Checks if the changes to the entity type requires storage schema changes.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The updated entity type definition.
* @param \Drupal\Core\Entity\EntityTypeInterface $original
* The original entity type definition.
*
* @return bool
* TRUE if storage schema changes are required, FALSE otherwise.
*/
protected function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
$storage = $this->entityManager->getStorage($entity_type->id());
return ($storage instanceof EntityStorageSchemaInterface) && $storage->requiresEntityStorageSchemaChanges($entity_type, $original);
}
/**
* Checks if the changes to the storage definition requires schema changes.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The updated field storage definition.
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
* The original field storage definition.
*
* @return bool
* TRUE if storage schema changes are required, FALSE otherwise.
*/
protected function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
$storage = $this->entityManager->getStorage($storage_definition->getTargetEntityTypeId());
return ($storage instanceof FieldableEntityStorageSchemaInterface) && $storage->requiresFieldStorageSchemaChanges($storage_definition, $original);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface.
*/
namespace Drupal\Core\Entity;
/**
* Defines an interface for managing entity definition updates.
*
* During the application lifetime, the definitions of various entity types and
* their data components (e.g., fields for fieldable entity types) can change.
* For example, updated code can be deployed. Some entity handlers may need to
* perform complex or long-running logic in response to the change. For
* example, a SQL-based storage handler may need to update the database schema.
*
* To support this, \Drupal\Core\Entity\EntityManagerInterface has methods to
* retrieve the last installed definitions as well as the definitions specified
* by the current codebase. It also has create/update/delete methods to bring
* the former up to date with the latter.
*
* However, it is not the responsibility of the entity manager to decide how to
* report the differences or when to apply each update. This interface is for
* managing that.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getDefinition()
* @see \Drupal\Core\Entity\EntityManagerInterface::getLastInstalledDefinition()
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldStorageDefinitions()
* @see \Drupal\Core\Entity\EntityManagerInterface::getLastInstalledFieldStorageDefinitions()
* @see \Drupal\Core\Entity\EntityTypeListenerInterface
* @see \Drupal\Core\Field\FieldStorageDefinitionListenerInterface
*/
interface EntityDefinitionUpdateManagerInterface {
/**
* Checks if there are any definition updates that need to be applied.
*
* @return bool
* TRUE if updates are needed.
*/
public function needsUpdates();
/**
* Returns a human readable summary of the detected changes.
*
* @return array
* An associative array keyed by entity type id. Each entry is an array of
* human-readable strings, each describing a change.
*/
public function getChangeSummary();
/**
* Applies all the detected valid changes.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* This exception is thrown if a change cannot be applied without
* unacceptable data loss. In such a case, the site administrator needs to
* apply some other process, such as a custom update function or a
* migration via the Migrate module.
*/
public function applyUpdates();
}
......@@ -8,11 +8,12 @@
namespace Drupal\Core\Entity;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
/**
* Provides an interface for entity type managers.
*/
interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface {
interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface, FieldStorageDefinitionListenerInterface {
/**
* Builds a list of entity type labels suitable for a Form API options list.
......@@ -80,6 +81,41 @@ public function getFieldDefinitions($entity_type_id, $bundle);
*/
public function getFieldStorageDefinitions($entity_type_id);
/**
* Gets the entity type's most recently installed field storage definitions.
*
* During the application lifetime, field storage definitions can change. For
* example, updated code can be deployed. The getFieldStorageDefinitions()
* method will always return the definitions as determined by the current
* codebase. This method, however, returns what the definitions were when the
* last time that one of the
* \Drupal\Core\Field\FieldStorageDefinitionListenerInterface events was last
* fired and completed successfully. In other words, the definitions that
* the entity type's handlers have incorporated into the application state.
* For example, if the entity type's storage handler is SQL-based, the
* definitions for which database tables were created.
*
* Application management code can check if getFieldStorageDefinitions()
* differs from getLastInstalledFieldStorageDefinitions() and decide whether
* to:
* - Invoke the appropriate
* \Drupal\Core\Field\FieldStorageDefinitionListenerInterface
* events so that handlers react to the new definitions.
* - Raise a warning that the application state is incompatible with the
* codebase.
* - Perform some other action.
*
* @param string $entity_type_id
* The entity type ID.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface[]
* The array of installed field storage definitions for the entity type,
* keyed by field name.
*
* @see \Drupal\Core\Entity\EntityTypeListenerInterface
*/
public function getLastInstalledFieldStorageDefinitions($entity_type_id);
/**
* Returns a lightweight map of fields across bundles.
*
......@@ -285,6 +321,38 @@ public function getTranslationFromContext(EntityInterface $entity, $langcode = N
*/
public function getDefinition($entity_type_id, $exception_on_invalid = TRUE);
/**
* Returns the entity type definition in its most recently installed state.
*
* During the application lifetime, entity type definitions can change. For
* example, updated code can be deployed. The getDefinition() method will
* always return the definition as determined by the current codebase. This
* method, however, returns what the definition was when the last time that
* one of the \Drupal\Core\Entity\EntityTypeListenerInterface events was last
* fired and completed successfully. In other words, the definition that
* the entity type's handlers have incorporated into the application state.
* For example, if the entity type's storage handler is SQL-based, the
* definition for which database tables were created.
*
* Application management code can check if getDefinition() differs from
* getLastInstalledDefinition() and decide whether to:
* - Invoke the appropriate \Drupal\Core\Entity\EntityTypeListenerInterface
* event so that handlers react to the new definition.
* - Raise a warning that the application state is incompatible with the
* codebase.
* - Perform some other action.
*
* @param string $entity_type_id
* The entity type ID.
*
* @return \Drupal\Core\Entity\EntityTypeInterface|null
* The installed entity type definition, or NULL if the entity type has
* not yet been installed via onEntityTypeCreate().
*
* @see \Drupal\Core\Entity\EntityTypeListenerInterface
*/
public function getLastInstalledDefinition($entity_type_id);
/**
* {@inheritdoc}
*
......
......@@ -7,7 +7,10 @@
namespace Drupal\Core\Entity\Exception;
use Drupal\Core\Entity\EntityStorageException;
/**
* Exception thrown when a storage definition update is forbidden.
*/
class FieldStorageDefinitionUpdateForbiddenException extends \Exception { }
class FieldStorageDefinitionUpdateForbiddenException extends EntityStorageException {
}
......@@ -9,42 +9,9 @@
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
interface FieldableEntityStorageInterface extends EntityStorageInterface {
/**
* Reacts to the creation of a field storage definition.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The definition being created.
*/
public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition);
/**
* Reacts to the update of a field storage definition.
*
* @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 onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original);
/**
* 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\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field being deleted.
*
* @see purgeFieldData()
*/
public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition);
interface FieldableEntityStorageInterface extends EntityStorageInterface, FieldStorageDefinitionListenerInterface {
/**
* Reacts to the creation of a field.
......@@ -59,7 +26,7 @@ public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definiti
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition being updated.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* @param \Drupal\Core\Field\FieldDefinitionInterface $original
* The original field definition; i.e., the definition before the update.
*/
public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original);
......
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Schema\EntitySchemaHandlerInterface.
*/
namespace Drupal\Core\Entity\Schema;
use Drupal\Core\Entity\EntityTypeListenerInterface;
/**
* Defines an interface for handling the storage schema of entities.
*/
interface EntitySchemaHandlerInterface extends EntityTypeListenerInterface {
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Schema\EntityStorageSchemaInterface.
*/
namespace Drupal\Core\Entity\Schema;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeListenerInterface;
/**
* Defines the interface for entity storage schema handler classes.
*
* An entity type's storage schema handler is responsible for creating the
* storage backend's schema that the entity type's storage handler needs for
* storing its entities. For example, if the storage handler is for a SQL
* backend, then the storage schema handler is responsible for creating the
* needed tables. During the application lifetime, an entity type's definition
* can change in a way that requires changes to the storage schema, so this
* interface defines methods for that as well.
*
* @see \Drupal\Core\Entity\EntityStorageInterface
*/
interface EntityStorageSchemaInterface extends EntityTypeListenerInterface {
/**
* Checks if the changes to the entity type requires storage schema changes.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The updated entity type definition.
* @param \Drupal\Core\Entity\EntityTypeInterface $original
* The original entity type definition.
*
* @return bool
* TRUE if storage schema changes are required, FALSE otherwise.
*/
public function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original);
/**
* Checks if existing data would be lost if the schema changes were applied.
*
* If there are no schema changes needed, then no data needs to be migrated,
* but it is not the responsibility of this function to recheck what
* requiresEntityStorageSchemaChanges() checks. Rather, the meaning of what
* this function returns when requiresEntityStorageSchemaChanges() returns
* FALSE is undefined. Callers are expected to only call this function when
* requiresEntityStorageSchemaChanges() is TRUE.
*
* This function can return FALSE if any of these conditions apply:
* - There are no existing entities for the entity type.
* - There are existing entities, but the schema changes can be applied
* without losing their data (e.g., if the schema changes can be performed
* by altering tables rather than dropping and recreating them).
* - The only entity data that would be lost are ones that are not valid for
* the new definition (e.g., if changing an entity type from revisionable
* to non-revisionable, then it's okay to drop data for the non-default
* revision).
*
* When this function returns FALSE, site administrators will be unable to
* perform an automated update, and will instead need to perform a site
* migration or invoke some custom update process.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The updated entity type definition.
* @param \Drupal\Core\Entity\EntityTypeInterface $original
* The original entity type definition.
*
* @return bool
* TRUE if data migration is required, FALSE otherwise.
*
* @see self::requiresEntityStorageSchemaChanges()
*/
public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original);
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Schema\FieldableEntityStorageSchemaInterface.
*/
namespace Drupal\Core\Entity\Schema;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
/**
* Defines the interface for storage schema classes for fieldable entity types.
*/
interface FieldableEntityStorageSchemaInterface extends EntityStorageSchemaInterface, FieldStorageDefinitionListenerInterface {
/**
* Checks if the changes to the storage definition requires schema changes.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The updated field storage definition.
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
* The original field storage definition.
*
* @return bool
* TRUE if storage schema changes are required, FALSE otherwise.
*/
public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original);
/**
* Checks if existing data would be lost if the schema changes were applied.
*
* If there are no schema changes needed, then no data needs to be migrated,
* but it is not the responsibility of this function to recheck what
* requiresFieldStorageSchemaChanges() checks. Rather, the meaning of what
* this function returns when requiresFieldStorageSchemaChanges() returns
* FALSE is undefined. Callers are expected to only call this function when
* requiresFieldStorageSchemaChanges() is TRUE.
*
* This function can return FALSE if any of these conditions apply:
* - There are no existing entities for the entity type to which this field
* is attached.
* - There are existing entities, but none with existing values for this
* field.
* - There are existing field values, but the schema changes can be applied
* without losing them (e.g., if the schema changes can be performed by
* altering tables rather than dropping and recreating them).
* - The only field values that would be lost are ones that are not valid for
* the new definition (e.g., if changing a field from revisionable to
* non-revisionable, then it's okay to drop data for the non-default
* revision).
*
* When this function returns FALSE, site administrators will be unable to
* perform an automated update, and will instead need to perform a site
* migration or invoke some custom update process.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The updated field storage definition.
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
* The original field storage definition.
*
* @return bool
* TRUE if data migration is required, FALSE otherwise.
*
* @see self::requiresFieldStorageSchemaChanges()
*/
public function requiresFieldDataMigration(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original);
}
......@@ -17,9 +17,9 @@
use Drupal\Core\Entity\EntityManagerInterface;