diff --git a/core/core.services.yml b/core/core.services.yml index 680c23b80840d1bf8647d6acd3e4446b47c56076..de408ad9f30e4659d03201cafabc2a5fa8988ed5 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -575,7 +575,7 @@ services: - { name: event_subscriber } entity.definition_update_manager: class: Drupal\Core\Entity\EntityDefinitionUpdateManager - arguments: ['@entity.manager'] + arguments: ['@entity.manager', '@entity.last_installed_schema.repository'] entity.last_installed_schema.repository: class: Drupal\Core\Entity\EntityLastInstalledSchemaRepository arguments: ['@keyvalue'] diff --git a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php index d9a4d843f517e8accc3aa89fbefdfaacf57e5e76..c5d0abd5028ae456ad6b1c417781afe66e604493 100644 --- a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php +++ b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php @@ -21,14 +21,29 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte */ protected $entityManager; + /** + * The last installed schema repository. + * + * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface + */ + protected $entityLastInstalledSchemaRepository; + /** * Constructs a new EntityDefinitionUpdateManager. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository + * The last installed schema repository service. */ - public function __construct(EntityManagerInterface $entity_manager) { + public function __construct(EntityManagerInterface $entity_manager, EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository = NULL) { $this->entityManager = $entity_manager; + + if (!isset($entity_last_installed_schema_repository)) { + @trigger_error('The $entity_last_installed_schema_repository parameter was added in Drupal 8.6.x and will be required in 9.0.0. See https://www.drupal.org/node/2973262.', E_USER_DEPRECATED); + $entity_last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository'); + } + $this->entityLastInstalledSchemaRepository = $entity_last_installed_schema_repository; } /** @@ -63,7 +78,7 @@ public function getChangeSummary() { // 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); + $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id); foreach ($change_list['field_storage_definitions'] as $field_name => $change) { switch ($change) { @@ -108,7 +123,7 @@ public function applyUpdates() { // 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); + $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id); foreach ($change_list['field_storage_definitions'] as $field_name => $change) { $storage_definition = isset($storage_definitions[$field_name]) ? $storage_definitions[$field_name] : NULL; @@ -123,10 +138,17 @@ public function applyUpdates() { * {@inheritdoc} */ public function getEntityType($entity_type_id) { - $entity_type = $this->entityManager->getLastInstalledDefinition($entity_type_id); + $entity_type = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id); return $entity_type ? clone $entity_type : NULL; } + /** + * {@inheritdoc} + */ + public function getEntityTypes() { + return $this->entityLastInstalledSchemaRepository->getLastInstalledDefinitions(); + } + /** * {@inheritdoc} */ @@ -173,7 +195,7 @@ public function installFieldStorageDefinition($name, $entity_type_id, $provider, * {@inheritdoc} */ public function getFieldStorageDefinition($name, $entity_type_id) { - $storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id); + $storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id); return isset($storage_definitions[$name]) ? clone $storage_definitions[$name] : NULL; } @@ -211,7 +233,7 @@ protected function doEntityUpdate($op, $entity_type_id) { break; case static::DEFINITION_UPDATED: - $original = $this->entityManager->getLastInstalledDefinition($entity_type_id); + $original = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id); $this->entityManager->onEntityTypeUpdate($entity_type, $original); break; } @@ -262,7 +284,7 @@ protected function getChangeList() { $change_list = []; foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { - $original = $this->entityManager->getLastInstalledDefinition($entity_type_id); + $original = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id); // @todo Support non-storage-schema-changing definition updates too: // https://www.drupal.org/node/2336895. @@ -277,7 +299,7 @@ protected function getChangeList() { if ($this->entityManager->getStorage($entity_type_id) instanceof DynamicallyFieldableEntityStorageInterface) { $field_changes = []; $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); - $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id); + $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id); // Detect created field storage definitions. foreach (array_diff_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) { diff --git a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManagerInterface.php index 7754170dbb4410f39455ec2bd086735b35bf1b02..a219f3c046ceba8a229555990f390f657f1fa1ad 100644 --- a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManagerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManagerInterface.php @@ -108,6 +108,18 @@ public function applyUpdates(); */ public function getEntityType($entity_type_id); + /** + * Returns all the entity type definitions, ready to be manipulated. + * + * When needing to apply updates to existing entity type definitions, this + * method should always be used to retrieve all the definitions ready to be + * manipulated. + * + * @return \Drupal\Core\Entity\EntityTypeInterface[] + * The last installed entity type definitions, keyed by the entity type ID. + */ + public function getEntityTypes(); + /** * Installs a new entity type definition. * diff --git a/core/lib/Drupal/Core/Entity/EntityLastInstalledSchemaRepository.php b/core/lib/Drupal/Core/Entity/EntityLastInstalledSchemaRepository.php index 61300125c9948380f37491974bf0cc91005f8629..70e552c742ff4ead98756d5d936c25d8fa0d9b7e 100644 --- a/core/lib/Drupal/Core/Entity/EntityLastInstalledSchemaRepository.php +++ b/core/lib/Drupal/Core/Entity/EntityLastInstalledSchemaRepository.php @@ -34,6 +34,28 @@ public function getLastInstalledDefinition($entity_type_id) { return $this->keyValueFactory->get('entity.definitions.installed')->get($entity_type_id . '.entity_type'); } + /** + * {@inheritdoc} + */ + public function getLastInstalledDefinitions() { + $all_definitions = $this->keyValueFactory->get('entity.definitions.installed')->getAll(); + + // Filter out field storage definitions. + $filtered_keys = array_filter(array_keys($all_definitions), function ($key) { + return substr($key, -12) === '.entity_type'; + }); + $entity_type_definitions = array_intersect_key($all_definitions, array_flip($filtered_keys)); + + // Ensure that the returned array is keyed by the entity type ID. + $keys = array_keys($entity_type_definitions); + $keys = array_map(function ($key) { + $parts = explode('.', $key); + return $parts[0]; + }, $keys); + + return array_combine($keys, $entity_type_definitions); + } + /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Entity/EntityLastInstalledSchemaRepositoryInterface.php b/core/lib/Drupal/Core/Entity/EntityLastInstalledSchemaRepositoryInterface.php index 53f02a00063f30d7f969559f5137cfc712288ff7..af910bb69004350caaffb54dc432f4d6d1bc8520 100644 --- a/core/lib/Drupal/Core/Entity/EntityLastInstalledSchemaRepositoryInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityLastInstalledSchemaRepositoryInterface.php @@ -41,6 +41,34 @@ interface EntityLastInstalledSchemaRepositoryInterface { */ public function getLastInstalledDefinition($entity_type_id); + /** + * Gets the entity type definitions in their most recently installed state. + * + * During the application lifetime, entity type definitions can change. For + * example, updated code can be deployed. The + * \Drupal\Core\Entity\EntityTypeManagerInterface::getDefinitions() method + * will always return the definitions as determined by the current codebase. + * This method returns the definitions from the last time that a + * \Drupal\Core\Entity\EntityTypeListener event was completed. 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 definition for which database tables were created. + * + * Application management code can check if + * \Drupal\Core\Entity\EntityTypeManagerInterface::getDefinitions() differs + * from getLastInstalledDefinitions() and decide whether to: + * - Invoke the appropriate \Drupal\Core\Entity\EntityTypeListenerInterface + * event so that handlers react to the new definitions. + * - Raise a warning that the application state is incompatible with the + * codebase. + * - Perform some other action. + * + * @return \Drupal\Core\Entity\EntityTypeInterface[] + * An array containing the installed definition for all entity types, keyed + * by the entity type ID. + */ + public function getLastInstalledDefinitions(); + /** * Stores the entity type definition in the application state. * diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php index 01044f724a1d314060ed3f2efa3b86244676b396..1a743eceebed491d2e95ad1e5c5d71f9389fc550 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php @@ -20,6 +20,8 @@ /** * Tests EntityDefinitionUpdateManager functionality. * + * @coversDefaultClass \Drupal\Core\Entity\EntityDefinitionUpdateManager + * * @group Entity */ class EntityDefinitionUpdateTest extends EntityKernelTestBase { @@ -1180,4 +1182,18 @@ public function testInitialValueFromFieldErrorHandling() { } } + /** + * @covers ::getEntityTypes + */ + public function testGetEntityTypes() { + $entity_type_definitions = $this->entityDefinitionUpdateManager->getEntityTypes(); + + // Ensure that we have at least one entity type to check below. + $this->assertGreaterThanOrEqual(1, count($entity_type_definitions)); + + foreach ($entity_type_definitions as $entity_type_id => $entity_type) { + $this->assertEquals($this->entityDefinitionUpdateManager->getEntityType($entity_type_id), $entity_type); + } + } + }