annotate core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Entity;
Chris@0 4
Chris@18 5 use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
Chris@0 6 use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
Chris@0 7 use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
Chris@0 8 use Drupal\Core\Field\BaseFieldDefinition;
Chris@0 9 use Drupal\Core\Field\FieldStorageDefinitionInterface;
Chris@18 10 use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
Chris@0 11 use Drupal\Core\StringTranslation\StringTranslationTrait;
Chris@0 12
Chris@0 13 /**
Chris@0 14 * Manages entity definition updates.
Chris@0 15 */
Chris@0 16 class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInterface {
Chris@0 17 use StringTranslationTrait;
Chris@18 18 use DeprecatedServicePropertyTrait;
Chris@0 19
Chris@0 20 /**
Chris@18 21 * {@inheritdoc}
Chris@18 22 */
Chris@18 23 protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
Chris@18 24
Chris@18 25 /**
Chris@18 26 * The entity field manager service.
Chris@0 27 *
Chris@18 28 * @var \Drupal\Core\Entity\EntityFieldManagerInterface
Chris@0 29 */
Chris@18 30 protected $entityFieldManager;
Chris@18 31
Chris@18 32 /**
Chris@18 33 * The entity type listener service.
Chris@18 34 *
Chris@18 35 * @var \Drupal\Core\Entity\EntityTypeListenerInterface
Chris@18 36 */
Chris@18 37 protected $entityTypeListener;
Chris@18 38
Chris@18 39 /**
Chris@18 40 * The entity type manager service.
Chris@18 41 *
Chris@18 42 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
Chris@18 43 */
Chris@18 44 protected $entityTypeManager;
Chris@18 45
Chris@18 46 /**
Chris@18 47 * The field storage definition listener service.
Chris@18 48 *
Chris@18 49 * @var \Drupal\Core\Field\FieldStorageDefinitionListenerInterface
Chris@18 50 */
Chris@18 51 protected $fieldStorageDefinitionListener;
Chris@0 52
Chris@0 53 /**
Chris@17 54 * The last installed schema repository.
Chris@17 55 *
Chris@17 56 * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
Chris@17 57 */
Chris@17 58 protected $entityLastInstalledSchemaRepository;
Chris@17 59
Chris@17 60 /**
Chris@0 61 * Constructs a new EntityDefinitionUpdateManager.
Chris@0 62 *
Chris@18 63 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
Chris@18 64 * The entity type manager service.
Chris@17 65 * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository
Chris@17 66 * The last installed schema repository service.
Chris@18 67 * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
Chris@18 68 * The entity field manager service.
Chris@18 69 * @param \Drupal\Core\Entity\EntityTypeListenerInterface $entity_type_listener
Chris@18 70 * The entity type listener interface.
Chris@18 71 * @param \Drupal\Core\Field\FieldStorageDefinitionListenerInterface $field_storage_definition_listener
Chris@18 72 * The field storage definition listener service.
Chris@0 73 */
Chris@18 74 public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository = NULL, EntityFieldManagerInterface $entity_field_manager = NULL, EntityTypeListenerInterface $entity_type_listener = NULL, FieldStorageDefinitionListenerInterface $field_storage_definition_listener = NULL) {
Chris@18 75 if ($entity_type_manager instanceof EntityManagerInterface) {
Chris@18 76 @trigger_error('Passing the entity.manager service to EntityDefinitionUpdateManager::__construct() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Pass the new dependencies instead. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
Chris@18 77 $this->entityTypeManager = \Drupal::entityTypeManager();
Chris@18 78 }
Chris@18 79 else {
Chris@18 80 $this->entityTypeManager = $entity_type_manager;
Chris@18 81 }
Chris@17 82
Chris@18 83 if ($entity_last_installed_schema_repository) {
Chris@18 84 $this->entityLastInstalledSchemaRepository = $entity_last_installed_schema_repository;
Chris@17 85 }
Chris@18 86 else {
Chris@18 87 @trigger_error('The entity.last_installed_schema.repository service must be passed to EntityDefinitionUpdateManager::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
Chris@18 88 $this->entityLastInstalledSchemaRepository = \Drupal::service('entity.last_installed_schema.repository');
Chris@18 89 }
Chris@18 90
Chris@18 91 if ($entity_field_manager) {
Chris@18 92 $this->entityFieldManager = $entity_field_manager;
Chris@18 93 }
Chris@18 94 else {
Chris@18 95 @trigger_error('The entity_field.manager service must be passed to EntityDefinitionUpdateManager::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
Chris@18 96 $this->entityFieldManager = \Drupal::service('entity_field.manager');
Chris@18 97 }
Chris@18 98
Chris@18 99 if ($entity_type_listener) {
Chris@18 100 $this->entityTypeListener = $entity_type_listener;
Chris@18 101 }
Chris@18 102 else {
Chris@18 103 @trigger_error('The entity_type.listener service must be passed to EntityDefinitionUpdateManager::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
Chris@18 104 $this->entityTypeListener = \Drupal::service('entity_type.listener');
Chris@18 105 }
Chris@18 106
Chris@18 107 if ($field_storage_definition_listener) {
Chris@18 108 $this->fieldStorageDefinitionListener = $field_storage_definition_listener;
Chris@18 109 }
Chris@18 110 else {
Chris@18 111 @trigger_error('The field_storage_definition.listener service must be passed to EntityDefinitionUpdateManager::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
Chris@18 112 $this->fieldStorageDefinitionListener = \Drupal::service('field_storage_definition.listener');
Chris@18 113 }
Chris@0 114 }
Chris@0 115
Chris@0 116 /**
Chris@0 117 * {@inheritdoc}
Chris@0 118 */
Chris@0 119 public function needsUpdates() {
Chris@0 120 return (bool) $this->getChangeList();
Chris@0 121 }
Chris@0 122
Chris@0 123 /**
Chris@0 124 * {@inheritdoc}
Chris@0 125 */
Chris@0 126 public function getChangeSummary() {
Chris@0 127 $summary = [];
Chris@0 128
Chris@0 129 foreach ($this->getChangeList() as $entity_type_id => $change_list) {
Chris@0 130 // Process entity type definition changes.
Chris@0 131 if (!empty($change_list['entity_type'])) {
Chris@18 132 $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
Chris@0 133
Chris@0 134 switch ($change_list['entity_type']) {
Chris@0 135 case static::DEFINITION_CREATED:
Chris@0 136 $summary[$entity_type_id][] = $this->t('The %entity_type entity type needs to be installed.', ['%entity_type' => $entity_type->getLabel()]);
Chris@0 137 break;
Chris@0 138
Chris@0 139 case static::DEFINITION_UPDATED:
Chris@0 140 $summary[$entity_type_id][] = $this->t('The %entity_type entity type needs to be updated.', ['%entity_type' => $entity_type->getLabel()]);
Chris@0 141 break;
Chris@0 142 }
Chris@0 143 }
Chris@0 144
Chris@0 145 // Process field storage definition changes.
Chris@0 146 if (!empty($change_list['field_storage_definitions'])) {
Chris@18 147 $storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id);
Chris@17 148 $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
Chris@0 149
Chris@0 150 foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
Chris@0 151 switch ($change) {
Chris@0 152 case static::DEFINITION_CREATED:
Chris@0 153 $summary[$entity_type_id][] = $this->t('The %field_name field needs to be installed.', ['%field_name' => $storage_definitions[$field_name]->getLabel()]);
Chris@0 154 break;
Chris@0 155
Chris@0 156 case static::DEFINITION_UPDATED:
Chris@0 157 $summary[$entity_type_id][] = $this->t('The %field_name field needs to be updated.', ['%field_name' => $storage_definitions[$field_name]->getLabel()]);
Chris@0 158 break;
Chris@0 159
Chris@0 160 case static::DEFINITION_DELETED:
Chris@0 161 $summary[$entity_type_id][] = $this->t('The %field_name field needs to be uninstalled.', ['%field_name' => $original_storage_definitions[$field_name]->getLabel()]);
Chris@0 162 break;
Chris@0 163 }
Chris@0 164 }
Chris@0 165 }
Chris@0 166 }
Chris@0 167
Chris@0 168 return $summary;
Chris@0 169 }
Chris@0 170
Chris@0 171 /**
Chris@0 172 * {@inheritdoc}
Chris@0 173 */
Chris@0 174 public function applyUpdates() {
Chris@18 175 trigger_error('EntityDefinitionUpdateManagerInterface::applyUpdates() is deprecated in 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::getChangeList() and execute each entity type and field storage update manually instead. See https://www.drupal.org/node/3034742.', E_USER_DEPRECATED);
Chris@0 176 }
Chris@0 177
Chris@0 178 /**
Chris@0 179 * {@inheritdoc}
Chris@0 180 */
Chris@0 181 public function getEntityType($entity_type_id) {
Chris@17 182 $entity_type = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
Chris@0 183 return $entity_type ? clone $entity_type : NULL;
Chris@0 184 }
Chris@0 185
Chris@0 186 /**
Chris@0 187 * {@inheritdoc}
Chris@0 188 */
Chris@17 189 public function getEntityTypes() {
Chris@17 190 return $this->entityLastInstalledSchemaRepository->getLastInstalledDefinitions();
Chris@17 191 }
Chris@17 192
Chris@17 193 /**
Chris@17 194 * {@inheritdoc}
Chris@17 195 */
Chris@0 196 public function installEntityType(EntityTypeInterface $entity_type) {
Chris@18 197 $this->clearCachedDefinitions();
Chris@18 198 $this->entityTypeListener->onEntityTypeCreate($entity_type);
Chris@0 199 }
Chris@0 200
Chris@0 201 /**
Chris@0 202 * {@inheritdoc}
Chris@0 203 */
Chris@0 204 public function updateEntityType(EntityTypeInterface $entity_type) {
Chris@0 205 $original = $this->getEntityType($entity_type->id());
Chris@18 206 $this->clearCachedDefinitions();
Chris@18 207 $this->entityTypeListener->onEntityTypeUpdate($entity_type, $original);
Chris@0 208 }
Chris@0 209
Chris@0 210 /**
Chris@0 211 * {@inheritdoc}
Chris@0 212 */
Chris@0 213 public function uninstallEntityType(EntityTypeInterface $entity_type) {
Chris@18 214 $this->clearCachedDefinitions();
Chris@18 215 $this->entityTypeListener->onEntityTypeDelete($entity_type);
Chris@18 216 }
Chris@18 217
Chris@18 218 /**
Chris@18 219 * {@inheritdoc}
Chris@18 220 */
Chris@18 221 public function updateFieldableEntityType(EntityTypeInterface $entity_type, array $field_storage_definitions, array &$sandbox = NULL) {
Chris@18 222 $original = $this->getEntityType($entity_type->id());
Chris@18 223
Chris@18 224 if ($this->requiresEntityDataMigration($entity_type, $original) && $sandbox === NULL) {
Chris@18 225 throw new \InvalidArgumentException('The entity schema update for the ' . $entity_type->id() . ' entity type requires a data migration.');
Chris@18 226 }
Chris@18 227
Chris@18 228 $original_field_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type->id());
Chris@18 229 $this->entityTypeListener->onFieldableEntityTypeUpdate($entity_type, $original, $field_storage_definitions, $original_field_storage_definitions, $sandbox);
Chris@18 230 $this->clearCachedDefinitions();
Chris@0 231 }
Chris@0 232
Chris@0 233 /**
Chris@0 234 * {@inheritdoc}
Chris@0 235 */
Chris@0 236 public function installFieldStorageDefinition($name, $entity_type_id, $provider, FieldStorageDefinitionInterface $storage_definition) {
Chris@0 237 // @todo Pass a mutable field definition interface when we have one. See
Chris@0 238 // https://www.drupal.org/node/2346329.
Chris@0 239 if ($storage_definition instanceof BaseFieldDefinition) {
Chris@0 240 $storage_definition
Chris@0 241 ->setName($name)
Chris@0 242 ->setTargetEntityTypeId($entity_type_id)
Chris@0 243 ->setProvider($provider)
Chris@0 244 ->setTargetBundle(NULL);
Chris@0 245 }
Chris@18 246 $this->clearCachedDefinitions();
Chris@18 247 $this->fieldStorageDefinitionListener->onFieldStorageDefinitionCreate($storage_definition);
Chris@0 248 }
Chris@0 249
Chris@0 250 /**
Chris@0 251 * {@inheritdoc}
Chris@0 252 */
Chris@0 253 public function getFieldStorageDefinition($name, $entity_type_id) {
Chris@17 254 $storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
Chris@0 255 return isset($storage_definitions[$name]) ? clone $storage_definitions[$name] : NULL;
Chris@0 256 }
Chris@0 257
Chris@0 258 /**
Chris@0 259 * {@inheritdoc}
Chris@0 260 */
Chris@0 261 public function updateFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
Chris@0 262 $original = $this->getFieldStorageDefinition($storage_definition->getName(), $storage_definition->getTargetEntityTypeId());
Chris@18 263 $this->clearCachedDefinitions();
Chris@18 264 $this->fieldStorageDefinitionListener->onFieldStorageDefinitionUpdate($storage_definition, $original);
Chris@0 265 }
Chris@0 266
Chris@0 267 /**
Chris@0 268 * {@inheritdoc}
Chris@0 269 */
Chris@0 270 public function uninstallFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
Chris@18 271 $this->clearCachedDefinitions();
Chris@18 272 $this->fieldStorageDefinitionListener->onFieldStorageDefinitionDelete($storage_definition);
Chris@0 273 }
Chris@0 274
Chris@0 275 /**
Chris@18 276 * {@inheritdoc}
Chris@0 277 */
Chris@18 278 public function getChangeList() {
Chris@18 279 $this->entityTypeManager->useCaches(FALSE);
Chris@18 280 $this->entityFieldManager->useCaches(FALSE);
Chris@0 281 $change_list = [];
Chris@0 282
Chris@18 283 foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
Chris@17 284 $original = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
Chris@0 285
Chris@0 286 // @todo Support non-storage-schema-changing definition updates too:
Chris@0 287 // https://www.drupal.org/node/2336895.
Chris@0 288 if (!$original) {
Chris@0 289 $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_CREATED;
Chris@0 290 }
Chris@0 291 else {
Chris@0 292 if ($this->requiresEntityStorageSchemaChanges($entity_type, $original)) {
Chris@0 293 $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_UPDATED;
Chris@0 294 }
Chris@0 295
Chris@18 296 if ($this->entityTypeManager->getStorage($entity_type_id) instanceof DynamicallyFieldableEntityStorageInterface) {
Chris@0 297 $field_changes = [];
Chris@18 298 $storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id);
Chris@17 299 $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
Chris@0 300
Chris@0 301 // Detect created field storage definitions.
Chris@0 302 foreach (array_diff_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
Chris@0 303 $field_changes[$field_name] = static::DEFINITION_CREATED;
Chris@0 304 }
Chris@0 305
Chris@0 306 // Detect deleted field storage definitions.
Chris@0 307 foreach (array_diff_key($original_storage_definitions, $storage_definitions) as $field_name => $original_storage_definition) {
Chris@0 308 $field_changes[$field_name] = static::DEFINITION_DELETED;
Chris@0 309 }
Chris@0 310
Chris@0 311 // Detect updated field storage definitions.
Chris@0 312 foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
Chris@0 313 // @todo Support non-storage-schema-changing definition updates too:
Chris@0 314 // https://www.drupal.org/node/2336895. So long as we're checking
Chris@0 315 // based on schema change requirements rather than definition
Chris@0 316 // equality, skip the check if the entity type itself needs to be
Chris@0 317 // updated, since that can affect the schema of all fields, so we
Chris@0 318 // want to process that update first without reporting false
Chris@0 319 // positives here.
Chris@0 320 if (!isset($change_list[$entity_type_id]['entity_type']) && $this->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) {
Chris@0 321 $field_changes[$field_name] = static::DEFINITION_UPDATED;
Chris@0 322 }
Chris@0 323 }
Chris@0 324
Chris@0 325 if ($field_changes) {
Chris@0 326 $change_list[$entity_type_id]['field_storage_definitions'] = $field_changes;
Chris@0 327 }
Chris@0 328 }
Chris@0 329 }
Chris@0 330 }
Chris@0 331
Chris@0 332 // @todo Support deleting entity definitions when we support base field
Chris@14 333 // purging.
Chris@14 334 // @see https://www.drupal.org/node/2907779
Chris@0 335
Chris@18 336 $this->entityTypeManager->useCaches(TRUE);
Chris@18 337 $this->entityFieldManager->useCaches(TRUE);
Chris@0 338
Chris@0 339 return array_filter($change_list);
Chris@0 340 }
Chris@0 341
Chris@0 342 /**
Chris@0 343 * Checks if the changes to the entity type requires storage schema changes.
Chris@0 344 *
Chris@0 345 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
Chris@0 346 * The updated entity type definition.
Chris@0 347 * @param \Drupal\Core\Entity\EntityTypeInterface $original
Chris@0 348 * The original entity type definition.
Chris@0 349 *
Chris@0 350 * @return bool
Chris@0 351 * TRUE if storage schema changes are required, FALSE otherwise.
Chris@0 352 */
Chris@0 353 protected function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
Chris@18 354 $storage = $this->entityTypeManager->getStorage($entity_type->id());
Chris@0 355 return ($storage instanceof EntityStorageSchemaInterface) && $storage->requiresEntityStorageSchemaChanges($entity_type, $original);
Chris@0 356 }
Chris@0 357
Chris@0 358 /**
Chris@0 359 * Checks if the changes to the storage definition requires schema changes.
Chris@0 360 *
Chris@0 361 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
Chris@0 362 * The updated field storage definition.
Chris@0 363 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
Chris@0 364 * The original field storage definition.
Chris@0 365 *
Chris@0 366 * @return bool
Chris@0 367 * TRUE if storage schema changes are required, FALSE otherwise.
Chris@0 368 */
Chris@0 369 protected function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
Chris@18 370 $storage = $this->entityTypeManager->getStorage($storage_definition->getTargetEntityTypeId());
Chris@0 371 return ($storage instanceof DynamicallyFieldableEntityStorageSchemaInterface) && $storage->requiresFieldStorageSchemaChanges($storage_definition, $original);
Chris@0 372 }
Chris@0 373
Chris@18 374 /**
Chris@18 375 * Checks if existing data would be lost if the schema changes were applied.
Chris@18 376 *
Chris@18 377 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
Chris@18 378 * The updated entity type definition.
Chris@18 379 * @param \Drupal\Core\Entity\EntityTypeInterface $original
Chris@18 380 * The original entity type definition.
Chris@18 381 *
Chris@18 382 * @return bool
Chris@18 383 * TRUE if data migration is required, FALSE otherwise.
Chris@18 384 */
Chris@18 385 protected function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
Chris@18 386 $storage = $this->entityTypeManager->getStorage($entity_type->id());
Chris@18 387 return ($storage instanceof EntityStorageSchemaInterface) && $storage->requiresEntityDataMigration($entity_type, $original);
Chris@18 388 }
Chris@18 389
Chris@18 390 /**
Chris@18 391 * Clears necessary caches to apply entity/field definition updates.
Chris@18 392 */
Chris@18 393 protected function clearCachedDefinitions() {
Chris@18 394 $this->entityTypeManager->clearCachedDefinitions();
Chris@18 395 $this->entityFieldManager->clearCachedFieldDefinitions();
Chris@18 396 }
Chris@18 397
Chris@0 398 }