Update to Drupal 8.0.0-beta15. For more information, see: https://www.drupal.org/node/2563023
This commit is contained in:
parent
2720a9ec4b
commit
f3791f1da3
1898 changed files with 54300 additions and 11481 deletions
|
|
@ -801,6 +801,8 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
$translation->translations = &$this->translations;
|
||||
$translation->enforceIsNew = &$this->enforceIsNew;
|
||||
$translation->newRevision = &$this->newRevision;
|
||||
$translation->entityKeys = &$this->entityKeys;
|
||||
$translation->translatableEntityKeys = &$this->translatableEntityKeys;
|
||||
$translation->translationInitialize = FALSE;
|
||||
$translation->typedData = NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayIn
|
|||
* Returns the entity_form_display object used to build an entity form.
|
||||
*
|
||||
* Depending on the configuration of the form mode for the entity bundle, this
|
||||
* can be either the display object associated to the form mode, or the
|
||||
* can be either the display object associated with the form mode, or the
|
||||
* 'default' display.
|
||||
*
|
||||
* This method should only be used internally when rendering an entity form.
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ class EntityViewDisplay extends EntityDisplayBase implements EntityViewDisplayIn
|
|||
* Returns the display objects used to render a set of entities.
|
||||
*
|
||||
* Depending on the configuration of the view mode for each bundle, this can
|
||||
* be either the display object associated to the view mode, or the 'default'
|
||||
* display.
|
||||
* be either the display object associated with the view mode, or the
|
||||
* 'default' display.
|
||||
*
|
||||
* This method should only be used internally when rendering an entity. When
|
||||
* assigning suggested display options for a component in a given view mode,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace Drupal\Core\Entity;
|
|||
|
||||
use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
|
||||
use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
|
|
@ -95,13 +96,13 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyUpdates() {
|
||||
$change_list = $this->getChangeList();
|
||||
if ($change_list) {
|
||||
$complete_change_list = $this->getChangeList();
|
||||
if ($complete_change_list) {
|
||||
// self::getChangeList() only disables the cache and does not invalidate.
|
||||
// In case there are changes, explicitly invalidate caches.
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
}
|
||||
foreach ($change_list as $entity_type_id => $change_list) {
|
||||
foreach ($complete_change_list as $entity_type_id => $change_list) {
|
||||
// Process entity type definition changes before storage definitions ones
|
||||
// this is necessary when you change an entity type from non-revisionable
|
||||
// to revisionable and at the same time add revisionable fields to the
|
||||
|
|
@ -127,42 +128,76 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEntityUpdate($op, $entity_type_id, $reset_cached_definitions = TRUE) {
|
||||
$change_list = $this->getChangeList();
|
||||
if (!isset($change_list[$entity_type_id]) || $change_list[$entity_type_id]['entity_type'] !== $op) {
|
||||
return FALSE;
|
||||
}
|
||||
if ($reset_cached_definitions) {
|
||||
// self::getChangeList() only disables the cache and does not invalidate.
|
||||
// In case there are changes, explicitly invalidate caches.
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
}
|
||||
$this->doEntityUpdate($op, $entity_type_id);
|
||||
return TRUE;
|
||||
public function getEntityType($entity_type_id) {
|
||||
$entity_type = $this->entityManager->getLastInstalledDefinition($entity_type_id);
|
||||
return $entity_type ? clone $entity_type : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyFieldUpdate($op, $entity_type_id, $field_name, $reset_cached_definitions = TRUE) {
|
||||
$change_list = $this->getChangeList();
|
||||
if (!isset($change_list[$entity_type_id]['field_storage_definitions']) || $change_list[$entity_type_id]['field_storage_definitions'][$field_name] !== $op) {
|
||||
return FALSE;
|
||||
public function installEntityType(EntityTypeInterface $entity_type) {
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
$this->entityManager->onEntityTypeCreate($entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateEntityType(EntityTypeInterface $entity_type) {
|
||||
$original = $this->getEntityType($entity_type->id());
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
$this->entityManager->onEntityTypeUpdate($entity_type, $original);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function uninstallEntityType(EntityTypeInterface $entity_type) {
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
$this->entityManager->onEntityTypeDelete($entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installFieldStorageDefinition($name, $entity_type_id, $provider, FieldStorageDefinitionInterface $storage_definition) {
|
||||
// @todo Pass a mutable field definition interface when we have one. See
|
||||
// https://www.drupal.org/node/2346329.
|
||||
if ($storage_definition instanceof BaseFieldDefinition) {
|
||||
$storage_definition
|
||||
->setName($name)
|
||||
->setTargetEntityTypeId($entity_type_id)
|
||||
->setProvider($provider)
|
||||
->setTargetBundle(NULL);
|
||||
}
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
$this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
|
||||
}
|
||||
|
||||
if ($reset_cached_definitions) {
|
||||
// self::getChangeList() only disables the cache and does not invalidate.
|
||||
// In case there are changes, explicitly invalidate caches.
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldStorageDefinition($name, $entity_type_id) {
|
||||
$storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
|
||||
return isset($storage_definitions[$name]) ? clone $storage_definitions[$name] : NULL;
|
||||
}
|
||||
|
||||
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
|
||||
$original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
|
||||
$storage_definition = isset($storage_definitions[$field_name]) ? $storage_definitions[$field_name] : NULL;
|
||||
$original_storage_definition = isset($original_storage_definitions[$field_name]) ? $original_storage_definitions[$field_name] : NULL;
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
|
||||
$original = $this->getFieldStorageDefinition($storage_definition->getName(), $storage_definition->getTargetEntityTypeId());
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
$this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $original);
|
||||
}
|
||||
|
||||
$this->doFieldUpdate($op, $storage_definition, $original_storage_definition);
|
||||
return TRUE;
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function uninstallFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
$this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for managing entity definition updates.
|
||||
*
|
||||
|
|
@ -25,12 +27,22 @@ namespace Drupal\Core\Entity;
|
|||
* report the differences or when to apply each update. This interface is for
|
||||
* managing that.
|
||||
*
|
||||
* This interface also provides methods to retrieve instances of the definitions
|
||||
* to be updated ready to be manipulated. In fact when definitions change in
|
||||
* code the system needs to be notified about that and the definitions stored in
|
||||
* state need to be reconciled with the ones living in code. This typically
|
||||
* happens in Update API functions, which need to take the system from a known
|
||||
* state to another known state. Relying on the definitions living in code might
|
||||
* prevent this, as the system might transition directly to the last available
|
||||
* state, and thus skipping the intermediate steps. Manipulating the definitions
|
||||
* in state allows to avoid this and ensures that the various steps of the
|
||||
* update process are predictable and repeatable.
|
||||
*
|
||||
* @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
|
||||
* @see hook_update_N()
|
||||
*/
|
||||
interface EntityDefinitionUpdateManagerInterface {
|
||||
|
||||
|
|
@ -75,6 +87,9 @@ interface EntityDefinitionUpdateManagerInterface {
|
|||
/**
|
||||
* Applies all the detected valid changes.
|
||||
*
|
||||
* Use this with care, as it will apply updates for any module, which will
|
||||
* lead to unpredictable results.
|
||||
*
|
||||
* @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
|
||||
|
|
@ -84,67 +99,92 @@ interface EntityDefinitionUpdateManagerInterface {
|
|||
public function applyUpdates();
|
||||
|
||||
/**
|
||||
* Performs a single entity definition update.
|
||||
* Returns an entity type definition ready to be manipulated.
|
||||
*
|
||||
* This method should be used from hook_update_N() functions to process
|
||||
* entity definition updates as part of the update function. This is only
|
||||
* necessary if the hook_update_N() implementation relies on the entity
|
||||
* definition update. All remaining entity definition updates will be run
|
||||
* automatically after the hook_update_N() implementations.
|
||||
* When needing to apply updates to existing entity type definitions, this
|
||||
* method should always be used to retrieve a definition ready to be
|
||||
* manipulated.
|
||||
*
|
||||
* @param string $op
|
||||
* The operation to perform, either static::DEFINITION_CREATED or
|
||||
* static::DEFINITION_UPDATED.
|
||||
* @param string $entity_type_id
|
||||
* The entity type to update.
|
||||
* @param bool $reset_cached_definitions
|
||||
* (optional). Determines whether to clear the Entity Manager's cached
|
||||
* definitions before applying the update. Defaults to TRUE. Can be used
|
||||
* to prevent unnecessary cache invalidation when a hook_update_N() makes
|
||||
* multiple calls to this method.
|
||||
* The entity type identifier.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity update is processed, FALSE if not.
|
||||
*
|
||||
* @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.
|
||||
* @return \Drupal\Core\Entity\EntityTypeInterface
|
||||
* The entity type definition.
|
||||
*/
|
||||
public function applyEntityUpdate($op, $entity_type_id, $reset_cached_definitions = TRUE);
|
||||
public function getEntityType($entity_type_id);
|
||||
|
||||
/**
|
||||
* Performs a single field storage definition update.
|
||||
* Installs a new entity type definition.
|
||||
*
|
||||
* This method should be used from hook_update_N() functions to process field
|
||||
* storage definition updates as part of the update function. This is only
|
||||
* necessary if the hook_update_N() implementation relies on the field storage
|
||||
* definition update. All remaining field storage definition updates will be
|
||||
* run automatically after the hook_update_N() implementations.
|
||||
*
|
||||
* @param string $op
|
||||
* The operation to perform, possible values are static::DEFINITION_CREATED,
|
||||
* static::DEFINITION_UPDATED or static::DEFINITION_DELETED.
|
||||
* @param string $entity_type_id
|
||||
* The entity type to update.
|
||||
* @param string $field_name
|
||||
* The field name to update.
|
||||
* @param bool $reset_cached_definitions
|
||||
* (optional). Determines whether to clear the Entity Manager's cached
|
||||
* definitions before applying the update. Defaults to TRUE. Can be used
|
||||
* to prevent unnecessary cache invalidation when a hook_update_N() makes
|
||||
* multiple calls to this method.
|
||||
|
||||
* @return bool
|
||||
* TRUE if the entity update is processed, FALSE if not.
|
||||
*
|
||||
* @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.
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
*/
|
||||
public function applyFieldUpdate($op, $entity_type_id, $field_name, $reset_cached_definitions = TRUE);
|
||||
public function installEntityType(EntityTypeInterface $entity_type);
|
||||
|
||||
/**
|
||||
* Applies any change performed to the passed entity type definition.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
*/
|
||||
public function updateEntityType(EntityTypeInterface $entity_type);
|
||||
|
||||
/**
|
||||
* Uninstalls an entity type definition.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
*/
|
||||
public function uninstallEntityType(EntityTypeInterface $entity_type);
|
||||
|
||||
/**
|
||||
* Returns a field storage definition ready to be manipulated.
|
||||
*
|
||||
* When needing to apply updates to existing field storage definitions, this
|
||||
* method should always be used to retrieve a storage definition ready to be
|
||||
* manipulated.
|
||||
*
|
||||
* @param string $name
|
||||
* The field name.
|
||||
* @param string $entity_type_id
|
||||
* The entity type identifier.
|
||||
*
|
||||
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface
|
||||
* The field storage definition.
|
||||
*
|
||||
* @todo Make this return a mutable storage definition interface when we have
|
||||
* one. See https://www.drupal.org/node/2346329.
|
||||
*/
|
||||
public function getFieldStorageDefinition($name, $entity_type_id);
|
||||
|
||||
/**
|
||||
* Installs a new field storage definition.
|
||||
*
|
||||
* @param string $name
|
||||
* The field storage definition name.
|
||||
* @param string $entity_type_id
|
||||
* The target entity type identifier.
|
||||
* @param string $provider
|
||||
* The name of the definition provider.
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
|
||||
* The field storage definition.
|
||||
*/
|
||||
public function installFieldStorageDefinition($name, $entity_type_id, $provider, FieldStorageDefinitionInterface $storage_definition);
|
||||
|
||||
/**
|
||||
* Applies any change performed to the passed field storage definition.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
|
||||
* The field storage definition.
|
||||
*/
|
||||
public function updateFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition);
|
||||
|
||||
/**
|
||||
* Uninstalls a field storage definition.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
|
||||
* The field storage definition.
|
||||
*/
|
||||
public function uninstallFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,19 +256,9 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
|
|||
parent::calculateDependencies();
|
||||
$target_entity_type = $this->entityManager()->getDefinition($this->targetEntityType);
|
||||
|
||||
$bundle_entity_type_id = $target_entity_type->getBundleEntityType();
|
||||
if ($bundle_entity_type_id != 'bundle') {
|
||||
// If the target entity type uses entities to manage its bundles then
|
||||
// depend on the bundle entity.
|
||||
if (!$bundle_entity = $this->entityManager()->getStorage($bundle_entity_type_id)->load($this->bundle)) {
|
||||
throw new \LogicException("Missing bundle entity, entity type $bundle_entity_type_id, entity id {$this->bundle}.");
|
||||
}
|
||||
$this->addDependency('config', $bundle_entity->getConfigDependencyName());
|
||||
}
|
||||
else {
|
||||
// Depend on the provider of the entity type.
|
||||
$this->addDependency('module', $target_entity_type->getProvider());
|
||||
}
|
||||
// Create dependency on the bundle.
|
||||
$bundle_config_dependency = $target_entity_type->getBundleConfigDependency($this->bundle);
|
||||
$this->addDependency($bundle_config_dependency['type'], $bundle_config_dependency['name']);
|
||||
|
||||
// If field.module is enabled, add dependencies on 'field_config' entities
|
||||
// for both displayed and hidden fields. We intentionally leave out base
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ use Drupal\Core\StringTranslation\StringTranslationTrait;
|
|||
/**
|
||||
* Provides a base class for entity handlers.
|
||||
*
|
||||
* @todo Deprecate this in https://www.drupal.org/node/2471663.
|
||||
* @deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.0.
|
||||
* Implement the container injection pattern of
|
||||
* \Drupal\Core\Entity\EntityHandlerInterface::createInstance() to obtain the
|
||||
* module handler service for your class.
|
||||
*/
|
||||
abstract class EntityHandlerBase {
|
||||
use StringTranslationTrait;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ namespace Drupal\Core\Entity;
|
|||
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Defines a generic implementation to build a listing of entities.
|
||||
|
|
@ -40,9 +39,12 @@ class EntityListBuilder extends EntityHandlerBase implements EntityListBuilderIn
|
|||
protected $entityType;
|
||||
|
||||
/**
|
||||
* The number of entities to list per page.
|
||||
* The number of entities to list per page, or FALSE to list all entities.
|
||||
*
|
||||
* @var int
|
||||
* For example, set this to FALSE if the list uses client-side filters that
|
||||
* require all entities to be listed (like the views overview).
|
||||
*
|
||||
* @var int|false
|
||||
*/
|
||||
protected $limit = 50;
|
||||
|
||||
|
|
@ -92,25 +94,31 @@ class EntityListBuilder extends EntityHandlerBase implements EntityListBuilderIn
|
|||
* An array of entity IDs.
|
||||
*/
|
||||
protected function getEntityIds() {
|
||||
$query = $this->getStorage()->getQuery();
|
||||
$keys = $this->entityType->getKeys();
|
||||
return $query
|
||||
->sort($keys['id'])
|
||||
->pager($this->limit)
|
||||
->execute();
|
||||
$query = $this->getStorage()->getQuery()
|
||||
->sort($this->entityType->getKey('id'));
|
||||
|
||||
// Only add the pager if a limit is specified.
|
||||
if ($this->limit) {
|
||||
$query->pager($this->limit);
|
||||
}
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the escaped label of an entity.
|
||||
* Gets the label of an entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity being listed.
|
||||
*
|
||||
* @return string
|
||||
* The escaped entity label.
|
||||
* The entity label.
|
||||
*
|
||||
* @deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.0
|
||||
* Use $entity->label() instead. This method used to escape the entity
|
||||
* label. The render system's autoescape is now relied upon.
|
||||
*/
|
||||
protected function getLabel(EntityInterface $entity) {
|
||||
return SafeMarkup::checkPlain($entity->label());
|
||||
return $entity->label();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -227,9 +235,13 @@ class EntityListBuilder extends EntityHandlerBase implements EntityListBuilderIn
|
|||
$build['table']['#rows'][$entity->id()] = $row;
|
||||
}
|
||||
}
|
||||
$build['pager'] = array(
|
||||
'#type' => 'pager',
|
||||
);
|
||||
|
||||
// Only add the pager if a limit is specified.
|
||||
if ($this->limit) {
|
||||
$build['pager'] = array(
|
||||
'#type' => 'pager',
|
||||
);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -423,7 +423,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
$keys = array_filter($entity_type->getKeys());
|
||||
|
||||
// Fail with an exception for non-fieldable entity types.
|
||||
if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
|
||||
if (!$entity_type->isSubclassOf(FieldableEntityInterface::class)) {
|
||||
throw new \LogicException("Getting the base fields is not supported for entity type {$entity_type->getLabel()}.");
|
||||
}
|
||||
|
||||
|
|
@ -655,7 +655,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
// bundles, and we do not expect to have so many different entity
|
||||
// types for this to become a bottleneck.
|
||||
foreach ($this->getDefinitions() as $entity_type_id => $entity_type) {
|
||||
if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
|
||||
if ($entity_type->isSubclassOf(FieldableEntityInterface::class)) {
|
||||
$bundles = array_keys($this->getBundleInfo($entity_type_id));
|
||||
foreach ($this->getBaseFieldDefinitions($entity_type_id) as $field_name => $base_field_definition) {
|
||||
$this->fieldMap[$entity_type_id][$field_name] = [
|
||||
|
|
@ -1205,7 +1205,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
$this->eventDispatcher->dispatch(EntityTypeEvents::CREATE, new EntityTypeEvent($entity_type));
|
||||
|
||||
$this->setLastInstalledDefinition($entity_type);
|
||||
if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
|
||||
if ($entity_type->isSubclassOf(FieldableEntityInterface::class)) {
|
||||
$this->setLastInstalledFieldStorageDefinitions($entity_type_id, $this->getFieldStorageDefinitions($entity_type_id));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class EntityType implements EntityTypeInterface {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle_entity_type = 'bundle';
|
||||
protected $bundle_entity_type = NULL;
|
||||
|
||||
/**
|
||||
* The name of the entity type for which bundles are provided.
|
||||
|
|
@ -232,6 +232,13 @@ class EntityType implements EntityTypeInterface {
|
|||
*/
|
||||
protected $constraints = array();
|
||||
|
||||
/**
|
||||
* Any additional properties and values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $additional = [];
|
||||
|
||||
/**
|
||||
* Constructs a new EntityType.
|
||||
*
|
||||
|
|
@ -248,7 +255,7 @@ class EntityType implements EntityTypeInterface {
|
|||
}
|
||||
|
||||
foreach ($definition as $property => $value) {
|
||||
$this->{$property} = $value;
|
||||
$this->set($property, $value);
|
||||
}
|
||||
|
||||
// Ensure defaults.
|
||||
|
|
@ -279,14 +286,25 @@ class EntityType implements EntityTypeInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($property) {
|
||||
return isset($this->{$property}) ? $this->{$property} : NULL;
|
||||
if (property_exists($this, $property)) {
|
||||
$value = isset($this->{$property}) ? $this->{$property} : NULL;
|
||||
}
|
||||
else {
|
||||
$value = isset($this->additional[$property]) ? $this->additional[$property] : NULL;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($property, $value) {
|
||||
$this->{$property} = $value;
|
||||
if (property_exists($this, $property)) {
|
||||
$this->{$property} = $value;
|
||||
}
|
||||
else {
|
||||
$this->additional[$property] = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -764,4 +782,30 @@ class EntityType implements EntityTypeInterface {
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBundleConfigDependency($bundle) {
|
||||
// If this entity type uses entities to manage its bundles then depend on
|
||||
// the bundle entity.
|
||||
if ($bundle_entity_type_id = $this->getBundleEntityType()) {
|
||||
if (!$bundle_entity = \Drupal::entityManager()->getStorage($bundle_entity_type_id)->load($bundle)) {
|
||||
throw new \LogicException(sprintf('Missing bundle entity, entity type %s, entity id %s.', $bundle_entity_type_id, $bundle));
|
||||
}
|
||||
$config_dependency = [
|
||||
'type' => 'config',
|
||||
'name' => $bundle_entity->getConfigDependencyName(),
|
||||
];
|
||||
}
|
||||
else {
|
||||
// Depend on the provider of the entity type.
|
||||
$config_dependency = [
|
||||
'type' => 'module',
|
||||
'name' => $this->getProvider(),
|
||||
];
|
||||
}
|
||||
|
||||
return $config_dependency;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -732,4 +732,17 @@ interface EntityTypeInterface {
|
|||
*/
|
||||
public function addConstraint($constraint_name, $options = NULL);
|
||||
|
||||
/**
|
||||
* Gets the config dependency info for this entity, if any exists.
|
||||
*
|
||||
* @param string $bundle
|
||||
* The bundle name.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing the following keys:
|
||||
* - 'type': The config dependency type (e.g. 'module', 'config').
|
||||
* - 'name': The name of the config dependency.
|
||||
*/
|
||||
public function getBundleConfigDependency($bundle);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -465,7 +465,7 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
|
|||
// series of fields individually for cases such as views tables.
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$bundle = $entity->bundle();
|
||||
$key = $entity_type_id . ':' . $bundle . ':' . $field_name . ':' . crc32(serialize($display_options));
|
||||
$key = $entity_type_id . ':' . $bundle . ':' . $field_name . ':' . hash('crc32b', serialize($display_options));
|
||||
if (!isset($this->singleFieldDisplays[$key])) {
|
||||
$this->singleFieldDisplays[$key] = EntityViewDisplay::create(array(
|
||||
'targetEntityType' => $entity_type_id,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Entity\Plugin\DataType;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
|
||||
|
|
@ -114,7 +113,7 @@ class EntityAdapter extends TypedData implements \IteratorAggregate, ComplexData
|
|||
*/
|
||||
public function getProperties($include_computed = FALSE) {
|
||||
if (!isset($this->entity)) {
|
||||
throw new MissingDataException(SafeMarkup::format('Unable to get properties as no entity has been provided.'));
|
||||
throw new MissingDataException('Unable to get properties as no entity has been provided.');
|
||||
}
|
||||
if (!$this->entity instanceof FieldableEntityInterface) {
|
||||
// @todo: Add support for config entities in
|
||||
|
|
|
|||
|
|
@ -189,9 +189,10 @@ class DefaultTableMapping implements TableMappingInterface {
|
|||
public function getColumnNames($field_name) {
|
||||
if (!isset($this->columnMapping[$field_name])) {
|
||||
$this->columnMapping[$field_name] = array();
|
||||
$storage_definition = $this->fieldStorageDefinitions[$field_name];
|
||||
foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) {
|
||||
$this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($storage_definition, $property_name);
|
||||
if (isset($this->fieldStorageDefinitions[$field_name])) {
|
||||
foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) {
|
||||
$this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($this->fieldStorageDefinitions[$field_name], $property_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->columnMapping[$field_name];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ namespace Drupal\Core\Entity\Sql;
|
|||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\DatabaseExceptionWrapper;
|
||||
use Drupal\Core\Database\SchemaException;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\ContentEntityStorageBase;
|
||||
use Drupal\Core\Entity\EntityBundleListenerInterface;
|
||||
|
|
@ -1351,7 +1353,9 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
|
||||
$this->getStorageSchema()->onEntityTypeCreate($entity_type);
|
||||
$this->wrapSchemaException(function () use ($entity_type) {
|
||||
$this->getStorageSchema()->onEntityTypeCreate($entity_type);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1364,14 +1368,18 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
// definition.
|
||||
$this->initTableLayout();
|
||||
// Let the schema handler adapt to possible table layout changes.
|
||||
$this->getStorageSchema()->onEntityTypeUpdate($entity_type, $original);
|
||||
$this->wrapSchemaException(function () use ($entity_type, $original) {
|
||||
$this->getStorageSchema()->onEntityTypeUpdate($entity_type, $original);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
|
||||
$this->getStorageSchema()->onEntityTypeDelete($entity_type);
|
||||
$this->wrapSchemaException(function () use ($entity_type) {
|
||||
$this->getStorageSchema()->onEntityTypeDelete($entity_type);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1386,14 +1394,18 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
if ($this->getTableMapping()->allowsSharedTableStorage($storage_definition)) {
|
||||
$this->tableMapping = NULL;
|
||||
}
|
||||
$this->getStorageSchema()->onFieldStorageDefinitionCreate($storage_definition);
|
||||
$this->wrapSchemaException(function () use ($storage_definition) {
|
||||
$this->getStorageSchema()->onFieldStorageDefinitionCreate($storage_definition);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
|
||||
$this->getStorageSchema()->onFieldStorageDefinitionUpdate($storage_definition, $original);
|
||||
$this->wrapSchemaException(function () use ($storage_definition, $original) {
|
||||
$this->getStorageSchema()->onFieldStorageDefinitionUpdate($storage_definition, $original);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1421,7 +1433,31 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
}
|
||||
|
||||
// Update the field schema.
|
||||
$this->getStorageSchema()->onFieldStorageDefinitionDelete($storage_definition);
|
||||
$this->wrapSchemaException(function () use ($storage_definition) {
|
||||
$this->getStorageSchema()->onFieldStorageDefinitionDelete($storage_definition);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a database schema exception into an entity storage exception.
|
||||
*
|
||||
* @param callable $callback
|
||||
* The callback to be executed.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
* When a database schema exception is thrown.
|
||||
*/
|
||||
protected function wrapSchemaException(callable $callback) {
|
||||
$message = 'Exception thrown while performing a schema update.';
|
||||
try {
|
||||
$callback();
|
||||
}
|
||||
catch (SchemaException $e) {
|
||||
throw new EntityStorageException($message, 0, $e);
|
||||
}
|
||||
catch (DatabaseExceptionWrapper $e) {
|
||||
throw new EntityStorageException($message, 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1598,10 +1634,12 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
foreach ($storage_definition->getColumns() as $column_name => $data) {
|
||||
$or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name));
|
||||
}
|
||||
$query
|
||||
->condition($or)
|
||||
->fields('t', array('entity_id'))
|
||||
->distinct(TRUE);
|
||||
$query->condition($or);
|
||||
if (!$as_bool) {
|
||||
$query
|
||||
->fields('t', array('entity_id'))
|
||||
->distinct(TRUE);
|
||||
}
|
||||
}
|
||||
elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
|
||||
// Ascertain the table this field is mapped too.
|
||||
|
|
|
|||
|
|
@ -186,27 +186,44 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
|
||||
return $this->getDedicatedTableSchema($storage_definition) != $this->loadFieldSchemaData($original);
|
||||
}
|
||||
elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
|
||||
$field_name = $storage_definition->getName();
|
||||
$schema = array();
|
||||
foreach (array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames()) as $table_name) {
|
||||
if (in_array($field_name, $table_mapping->getFieldNames($table_name))) {
|
||||
$column_names = $table_mapping->getColumnNames($storage_definition->getName());
|
||||
$schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
|
||||
}
|
||||
}
|
||||
return $schema != $this->loadFieldSchemaData($original);
|
||||
}
|
||||
else {
|
||||
if ($storage_definition->hasCustomStorage()) {
|
||||
// The field has custom storage, so we don't know if a schema change is
|
||||
// needed or not, but since per the initial checks earlier in this
|
||||
// function, nothing about the definition changed that we manage, we
|
||||
// return FALSE.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return $this->getSchemaFromStorageDefinition($storage_definition) != $this->loadFieldSchemaData($original);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the schema data for the given field storage definition.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
|
||||
* The field storage definition. The field that must not have custom
|
||||
* storage, i.e. the storage must take care of storing the field.
|
||||
*
|
||||
* @return array
|
||||
* The schema data.
|
||||
*/
|
||||
protected function getSchemaFromStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
|
||||
assert('!$storage_definition->hasCustomStorage();');
|
||||
$table_mapping = $this->storage->getTableMapping();
|
||||
$schema = [];
|
||||
if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
|
||||
$schema = $this->getDedicatedTableSchema($storage_definition);
|
||||
}
|
||||
elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
|
||||
$field_name = $storage_definition->getName();
|
||||
foreach (array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames()) as $table_name) {
|
||||
if (in_array($field_name, $table_mapping->getFieldNames($table_name))) {
|
||||
$column_names = $table_mapping->getColumnNames($storage_definition->getName());
|
||||
$schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -285,7 +302,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
|
||||
// If a migration is required, we can't proceed.
|
||||
if ($this->requiresEntityDataMigration($entity_type, $original)) {
|
||||
throw new EntityStorageException('The SQL storage cannot change the schema for an existing entity type with data.');
|
||||
throw new EntityStorageException('The SQL storage cannot change the schema for an existing entity type (' . $entity_type->id() . ') with data.');
|
||||
}
|
||||
|
||||
// If we have no data just recreate the entity schema from scratch.
|
||||
|
|
@ -311,36 +328,12 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
}
|
||||
}
|
||||
else {
|
||||
$schema_handler = $this->database->schema();
|
||||
|
||||
// Drop original indexes and unique keys.
|
||||
foreach ($this->loadEntitySchemaData($entity_type) as $table_name => $schema) {
|
||||
if (!empty($schema['indexes'])) {
|
||||
foreach ($schema['indexes'] as $name => $specifier) {
|
||||
$schema_handler->dropIndex($table_name, $name);
|
||||
}
|
||||
}
|
||||
if (!empty($schema['unique keys'])) {
|
||||
foreach ($schema['unique keys'] as $name => $specifier) {
|
||||
$schema_handler->dropUniqueKey($table_name, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->deleteEntitySchemaIndexes($this->loadEntitySchemaData($entity_type));
|
||||
|
||||
// Create new indexes and unique keys.
|
||||
$entity_schema = $this->getEntitySchema($entity_type, TRUE);
|
||||
foreach ($this->getEntitySchemaData($entity_type, $entity_schema) as $table_name => $schema) {
|
||||
if (!empty($schema['indexes'])) {
|
||||
foreach ($schema['indexes'] as $name => $specifier) {
|
||||
$schema_handler->addIndex($table_name, $name, $specifier);
|
||||
}
|
||||
}
|
||||
if (!empty($schema['unique keys'])) {
|
||||
foreach ($schema['unique keys'] as $name => $specifier) {
|
||||
$schema_handler->addUniqueKey($table_name, $name, $specifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->createEntitySchemaIndexes($entity_schema);
|
||||
|
||||
// Store the updated entity schema.
|
||||
$this->saveEntitySchemaData($entity_type, $entity_schema);
|
||||
|
|
@ -411,7 +404,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
// @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 with data that cannot be purged.');
|
||||
throw new FieldStorageDefinitionUpdateForbiddenException('Unable to delete a field (' . $storage_definition->getName() . ' in ' . $storage_definition->getTargetEntityTypeId() . ' entity) with data that cannot be purged.');
|
||||
}
|
||||
}
|
||||
catch (DatabaseException $e) {
|
||||
|
|
@ -1138,9 +1131,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
foreach ($schema[$table_name]['indexes'] as $name => $specifier) {
|
||||
// Check if the index exists because it might already have been
|
||||
// created as part of the earlier entity type update event.
|
||||
if (!$schema_handler->indexExists($table_name, $name)) {
|
||||
$schema_handler->addIndex($table_name, $name, $specifier);
|
||||
}
|
||||
$this->addIndex($table_name, $name, $specifier, $schema[$table_name]);
|
||||
}
|
||||
}
|
||||
if (!empty($schema[$table_name]['unique keys'])) {
|
||||
|
|
@ -1154,7 +1145,14 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->saveFieldSchemaData($storage_definition, $schema);
|
||||
|
||||
if (!$only_save) {
|
||||
// Make sure any entity index involving this field is re-created if
|
||||
// needed.
|
||||
$this->createEntitySchemaIndexes($this->getEntitySchema($this->entityType), $storage_definition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1185,6 +1183,9 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
* The storage definition of the field being deleted.
|
||||
*/
|
||||
protected function deleteSharedTableSchema(FieldStorageDefinitionInterface $storage_definition) {
|
||||
// Make sure any entity index involving this field is dropped.
|
||||
$this->deleteEntitySchemaIndexes($this->loadEntitySchemaData($this->entityType), $storage_definition);
|
||||
|
||||
$deleted_field_name = $storage_definition->getName();
|
||||
$table_mapping = $this->storage->getTableMapping(
|
||||
$this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id())
|
||||
|
|
@ -1263,8 +1264,8 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
}
|
||||
}
|
||||
else {
|
||||
if ($storage_definition->getColumns() != $original->getColumns()) {
|
||||
throw new FieldStorageDefinitionUpdateForbiddenException("The SQL storage cannot change the schema for an existing field with data.");
|
||||
if ($this->hasColumnChanges($storage_definition, $original)) {
|
||||
throw new FieldStorageDefinitionUpdateForbiddenException('The SQL storage cannot change the schema for an existing field (' . $storage_definition->getName() . ' in ' . $storage_definition->getTargetEntityTypeId() . ' entity) 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
|
||||
|
|
@ -1273,9 +1274,13 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
$table = $table_mapping->getDedicatedDataTableName($original);
|
||||
$revision_table = $table_mapping->getDedicatedRevisionTableName($original);
|
||||
|
||||
// Get the field schemas.
|
||||
$schema = $storage_definition->getSchema();
|
||||
$original_schema = $original->getSchema();
|
||||
|
||||
// Gets the SQL schema for a dedicated tables.
|
||||
$actual_schema = $this->getDedicatedTableSchema($storage_definition);
|
||||
|
||||
foreach ($original_schema['indexes'] as $name => $columns) {
|
||||
if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) {
|
||||
$real_name = $this->getFieldIndexName($storage_definition, $name);
|
||||
|
|
@ -1302,8 +1307,10 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
$real_columns[] = $table_mapping->getFieldColumnName($storage_definition, $column_name);
|
||||
}
|
||||
}
|
||||
$this->database->schema()->addIndex($table, $real_name, $real_columns);
|
||||
$this->database->schema()->addIndex($revision_table, $real_name, $real_columns);
|
||||
// Check if the index exists because it might already have been
|
||||
// created as part of the earlier entity type update event.
|
||||
$this->addIndex($table, $real_name, $real_columns, $actual_schema[$table]);
|
||||
$this->addIndex($revision_table, $real_name, $real_columns, $actual_schema[$revision_table]);
|
||||
}
|
||||
}
|
||||
$this->saveFieldSchemaData($storage_definition, $this->getDedicatedTableSchema($storage_definition));
|
||||
|
|
@ -1348,8 +1355,8 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
}
|
||||
}
|
||||
else {
|
||||
if ($storage_definition->getColumns() != $original->getColumns()) {
|
||||
throw new FieldStorageDefinitionUpdateForbiddenException("The SQL storage cannot change the schema for an existing field with data.");
|
||||
if ($this->hasColumnChanges($storage_definition, $original)) {
|
||||
throw new FieldStorageDefinitionUpdateForbiddenException('The SQL storage cannot change the schema for an existing field (' . $storage_definition->getName() . ' in ' . $storage_definition->getTargetEntityTypeId() . ' entity) with data.');
|
||||
}
|
||||
|
||||
$updated_field_name = $storage_definition->getName();
|
||||
|
|
@ -1366,6 +1373,20 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
if ($field_name == $updated_field_name) {
|
||||
$schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
|
||||
|
||||
// Handle NOT NULL constraints.
|
||||
foreach ($schema[$table_name]['fields'] as $column_name => $specifier) {
|
||||
$not_null = !empty($specifier['not null']);
|
||||
$original_not_null = !empty($original_schema[$table_name]['fields'][$column_name]['not null']);
|
||||
if ($not_null !== $original_not_null) {
|
||||
if ($not_null && $this->hasNullFieldPropertyData($table_name, $column_name)) {
|
||||
throw new EntityStorageException("The $column_name column cannot have NOT NULL constraints as it holds NULL values.");
|
||||
}
|
||||
$column_schema = $original_schema[$table_name]['fields'][$column_name];
|
||||
$column_schema['not null'] = $not_null;
|
||||
$schema_handler->changeField($table_name, $field_name, $field_name, $column_schema);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop original indexes and unique keys.
|
||||
if (!empty($original_schema[$table_name]['indexes'])) {
|
||||
foreach ($original_schema[$table_name]['indexes'] as $name => $specifier) {
|
||||
|
|
@ -1380,7 +1401,10 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
// Create new indexes and unique keys.
|
||||
if (!empty($schema[$table_name]['indexes'])) {
|
||||
foreach ($schema[$table_name]['indexes'] as $name => $specifier) {
|
||||
$schema_handler->addIndex($table_name, $name, $specifier);
|
||||
// Check if the index exists because it might already have been
|
||||
// created as part of the earlier entity type update event.
|
||||
$this->addIndex($table_name, $name, $specifier, $schema[$table_name]);
|
||||
|
||||
}
|
||||
}
|
||||
if (!empty($schema[$table_name]['unique keys'])) {
|
||||
|
|
@ -1397,6 +1421,120 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the specified entity schema indexes and keys.
|
||||
*
|
||||
* @param array $entity_schema
|
||||
* The entity schema definition.
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface|NULL $storage_definition
|
||||
* (optional) If a field storage definition is specified, only indexes and
|
||||
* keys involving its columns will be processed. Otherwise all defined
|
||||
* entity indexes and keys will be processed.
|
||||
*/
|
||||
protected function createEntitySchemaIndexes(array $entity_schema, FieldStorageDefinitionInterface $storage_definition = NULL) {
|
||||
$schema_handler = $this->database->schema();
|
||||
|
||||
if ($storage_definition) {
|
||||
$table_mapping = $this->storage->getTableMapping();
|
||||
$column_names = $table_mapping->getColumnNames($storage_definition->getName());
|
||||
}
|
||||
|
||||
$index_keys = [
|
||||
'indexes' => 'addIndex',
|
||||
'unique keys' => 'addUniqueKey',
|
||||
];
|
||||
|
||||
foreach ($this->getEntitySchemaData($this->entityType, $entity_schema) as $table_name => $schema) {
|
||||
// Add fields schema because database driver may depend on this data to
|
||||
// perform index normalization.
|
||||
$schema['fields'] = $entity_schema[$table_name]['fields'];
|
||||
|
||||
foreach ($index_keys as $key => $add_method) {
|
||||
if (!empty($schema[$key])) {
|
||||
foreach ($schema[$key] as $name => $specifier) {
|
||||
// If a set of field columns were specified we process only indexes
|
||||
// involving them. Only indexes for which all columns exist are
|
||||
// actually created.
|
||||
$create = FALSE;
|
||||
$specifier_columns = array_map(function($item) { return is_string($item) ? $item : reset($item); }, $specifier);
|
||||
if (!isset($column_names) || array_intersect($specifier_columns, $column_names)) {
|
||||
$create = TRUE;
|
||||
foreach ($specifier_columns as $specifier_column_name) {
|
||||
// This may happen when adding more than one field in the same
|
||||
// update run. Eventually when all field columns have been
|
||||
// created the index will be created too.
|
||||
if (!$schema_handler->fieldExists($table_name, $specifier_column_name)) {
|
||||
$create = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($create) {
|
||||
$this->{$add_method}($table_name, $name, $specifier, $schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the specified entity schema indexes and keys.
|
||||
*
|
||||
* @param array $entity_schema_data
|
||||
* The entity schema data definition.
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface|NULL $storage_definition
|
||||
* (optional) If a field storage definition is specified, only indexes and
|
||||
* keys involving its columns will be processed. Otherwise all defined
|
||||
* entity indexes and keys will be processed.
|
||||
*/
|
||||
protected function deleteEntitySchemaIndexes(array $entity_schema_data, FieldStorageDefinitionInterface $storage_definition = NULL) {
|
||||
$schema_handler = $this->database->schema();
|
||||
|
||||
if ($storage_definition) {
|
||||
$table_mapping = $this->storage->getTableMapping();
|
||||
$column_names = $table_mapping->getColumnNames($storage_definition->getName());
|
||||
}
|
||||
|
||||
$index_keys = [
|
||||
'indexes' => 'dropIndex',
|
||||
'unique keys' => 'dropUniqueKey',
|
||||
];
|
||||
|
||||
foreach ($entity_schema_data as $table_name => $schema) {
|
||||
foreach ($index_keys as $key => $drop_method) {
|
||||
if (!empty($schema[$key])) {
|
||||
foreach ($schema[$key] as $name => $specifier) {
|
||||
$specifier_columns = array_map(function($item) { return is_string($item) ? $item : reset($item); }, $specifier);
|
||||
if (!isset($column_names) || array_intersect($specifier_columns, $column_names)) {
|
||||
$schema_handler->{$drop_method}($table_name, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a field property has NULL values.
|
||||
*
|
||||
* @param string $table_name
|
||||
* The name of the table to inspect.
|
||||
* @param string $column_name
|
||||
* The name of the column holding the field property data.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if NULL data is found, FALSE otherwise.
|
||||
*/
|
||||
protected function hasNullFieldPropertyData($table_name, $column_name) {
|
||||
$query = $this->database->select($table_name, 't')
|
||||
->fields('t', [$column_name])
|
||||
->range(0, 1);
|
||||
$query->isNull('t.' . $column_name);
|
||||
$result = $query->execute()->fetchAssoc();
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the schema for a single field definition.
|
||||
*
|
||||
|
|
@ -1738,6 +1876,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
protected function getFieldIndexName(FieldStorageDefinitionInterface $storage_definition, $index) {
|
||||
return $storage_definition->getName() . '_' . $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a database table is non-existent or empty.
|
||||
*
|
||||
|
|
@ -1758,4 +1897,91 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares schemas to check for changes in the column definitions.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
|
||||
* Current field storage definition.
|
||||
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
|
||||
* The original field storage definition.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if there are schema changes in the column definitions.
|
||||
*/
|
||||
protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
|
||||
if ($storage_definition->getColumns() != $original->getColumns()) {
|
||||
// Base field definitions have schema data stored in the original
|
||||
// definition.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!$storage_definition->hasCustomStorage()) {
|
||||
$keys = array_flip($this->getColumnSchemaRelevantKeys());
|
||||
$definition_schema = $this->getSchemaFromStorageDefinition($storage_definition);
|
||||
foreach ($this->loadFieldSchemaData($original) as $table => $table_schema) {
|
||||
foreach ($table_schema['fields'] as $name => $spec) {
|
||||
$definition_spec = array_intersect_key($definition_schema[$table]['fields'][$name], $keys);
|
||||
$stored_spec = array_intersect_key($spec, $keys);
|
||||
if ($definition_spec != $stored_spec) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of column schema keys affecting data storage.
|
||||
*
|
||||
* When comparing schema definitions, only changes in certain properties
|
||||
* actually affect how data is stored and thus, if applied, may imply data
|
||||
* manipulation.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of key names.
|
||||
*/
|
||||
protected function getColumnSchemaRelevantKeys() {
|
||||
return ['type', 'size', 'length', 'unsigned'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an index, dropping it if already existing.
|
||||
*
|
||||
* @param string $table
|
||||
* The table name.
|
||||
* @param string $name
|
||||
* The index name.
|
||||
* @param array $specifier
|
||||
* The fields to index.
|
||||
* @param array $schema
|
||||
* The table specification.
|
||||
*
|
||||
* @see \Drupal\Core\Database\Schema::addIndex()
|
||||
*/
|
||||
protected function addIndex($table, $name, array $specifier, array $schema) {
|
||||
$schema_handler = $this->database->schema();
|
||||
$schema_handler->dropIndex($table, $name);
|
||||
$schema_handler->addIndex($table, $name, $specifier, $schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique key, dropping it if already existing.
|
||||
*
|
||||
* @param string $table
|
||||
* The table name.
|
||||
* @param string $name
|
||||
* The index name.
|
||||
* @param array $specifier
|
||||
* The unique fields.
|
||||
*
|
||||
* @see \Drupal\Core\Database\Schema::addUniqueKey()
|
||||
*/
|
||||
protected function addUniqueKey($table, $name, array $specifier) {
|
||||
$schema_handler = $this->database->schema();
|
||||
$schema_handler->dropUniqueKey($table, $name);
|
||||
$schema_handler->addUniqueKey($table, $name, $specifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue