diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php index 700ad04445f779fd2e6bcdd5d564dbacf2d3ea37..1d66dc3a0f102041aaca34b9700d6044a25618f3 100644 --- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php @@ -352,6 +352,7 @@ private function doInstall(array $module_list, array $installed_modules, bool $s // @todo should this be in the loop? \Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions(); + $entity_type_providers_to_install = $module_list; foreach ($module_list as $module) { // Set the schema version to the number of the last update provided by // the module, or the minimum core schema version. @@ -361,6 +362,10 @@ private function doInstall(array $module_list, array $installed_modules, bool $s $version = max(max($versions), $version); } + // Remove the module from the list of possible entity type providers to + // install. + array_shift($entity_type_providers_to_install); + // Notify interested components that this module's entity types and // field storage definitions are new. For example, a SQL-based storage // handler can use this as an opportunity to create the necessary @@ -381,10 +386,10 @@ private function doInstall(array $module_list, array $installed_modules, bool $s $update_manager->installEntityType($entity_type); } } - elseif ($is_fieldable_entity_type) { + elseif ($is_fieldable_entity_type && !in_array($entity_type->getProvider(), $entity_type_providers_to_install, TRUE)) { // The module being installed may be adding new fields to existing // entity types. Field definitions for any entity type defined by - // the module are handled in the if branch. + // modules being installed are handled in the if branch. foreach ($entity_field_manager->getFieldStorageDefinitions($entity_type->id()) as $storage_definition) { if ($storage_definition->getProvider() == $module) { // If the module being installed is also defining a storage key diff --git a/core/modules/system/tests/modules/field_storage_entity_type_dependency_test/field_storage_entity_type_dependency_test.info.yml b/core/modules/system/tests/modules/field_storage_entity_type_dependency_test/field_storage_entity_type_dependency_test.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..c0c6defce6833945ae0bfee97acf409741bfe865 --- /dev/null +++ b/core/modules/system/tests/modules/field_storage_entity_type_dependency_test/field_storage_entity_type_dependency_test.info.yml @@ -0,0 +1,5 @@ +name: 'Field storage entity type dependency test' +type: module +description: 'Introduces a module dependency where entity types need to be installed before field storage definitions.' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/field_storage_entity_type_dependency_test/src/Hook/FieldStorageEntityTypeDependencyTestHook.php b/core/modules/system/tests/modules/field_storage_entity_type_dependency_test/src/Hook/FieldStorageEntityTypeDependencyTestHook.php new file mode 100644 index 0000000000000000000000000000000000000000..ff7ab50de67a0b931f761c60e01425502fd2e68c --- /dev/null +++ b/core/modules/system/tests/modules/field_storage_entity_type_dependency_test/src/Hook/FieldStorageEntityTypeDependencyTestHook.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\field_storage_entity_type_dependency_test\Hook; + +use Drupal\Core\Extension\Extension; +use Drupal\Core\Hook\Attribute\Hook; + +/** + * Hook implementations. + */ +class FieldStorageEntityTypeDependencyTestHook { + + /** + * Implements hook_system_info_alter(). + */ + #[Hook('system_info_alter')] + public function systemInfoAlter(array &$info, Extension $file, string $type): void { + if ($file->getName() === 'taxonomy') { + $info['dependencies'][] = 'drupal:workspaces'; + } + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php index f2dddc48770d3a60ab86095bda8ce2f2a7509ed3..22ac70ddbe84459f28ddd297ac4c1af0324c801b 100644 --- a/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php +++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php @@ -5,11 +5,15 @@ namespace Drupal\KernelTests\Core\Extension; use Drupal\Core\Database\Database; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Extension\MissingDependencyException; use Drupal\Core\Extension\Exception\ObsoleteExtensionException; use Drupal\Core\Extension\ModuleInstaller; use Drupal\Core\Extension\ModuleUninstallValidatorInterface; +use Drupal\Core\Logger\RfcLoggerTrait; +use Drupal\Core\Logger\RfcLogLevel; use Drupal\KernelTests\KernelTestBase; +use Psr\Log\LoggerInterface; use Symfony\Component\Routing\Exception\RouteNotFoundException; /** @@ -19,7 +23,9 @@ * * @group Extension */ -class ModuleInstallerTest extends KernelTestBase { +class ModuleInstallerTest extends KernelTestBase implements LoggerInterface { + + use RfcLoggerTrait; /** * Tests that routes are rebuilt during install and uninstall of modules. @@ -239,4 +245,50 @@ public function testUninstallValidatorsBC(): void { $module_installer->addUninstallValidator($this->createMock(ModuleUninstallValidatorInterface::class)); } + /** + * Tests field storage definitions are installed only if entity types exist. + */ + public function testFieldStorageEntityTypeDependencies(): void { + $profile = 'minimal'; + $this->setInstallProfile($profile); + // Install a module that will make workspaces a dependency of taxonomy. + \Drupal::service('module_installer')->install(['field_storage_entity_type_dependency_test']); + // Installing taxonomy will install workspaces first. During installation of + // workspaces, the storage for 'workspace' field should not be attempted + // before the taxonomy term entity storage has been created, so there + // should not be a EntityStorageException logged. + \Drupal::service('module_installer')->install(['taxonomy']); + $this->assertTrue(\Drupal::moduleHandler()->moduleExists('workspaces')); + $this->assertTrue(\Drupal::moduleHandler()->moduleExists('taxonomy')); + $this->assertArrayHasKey('workspace', \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('taxonomy_term')); + } + + /** + * {@inheritdoc} + */ + public function register(ContainerBuilder $container): void { + parent::register($container); + + $container + ->register(__CLASS__, __CLASS__) + ->setSynthetic(TRUE) + ->addTag('logger'); + $container->set(__CLASS__, $this); + } + + /** + * {@inheritdoc} + */ + public function log($level, \Stringable|string $message, array $context = []): void { + if ($level > RfcLogLevel::ERROR) { + return; + } + + // Fails the test if an error or more severe message is logged. + $message = (string) $message; + $placeholders = \Drupal::service('logger.log_message_parser')->parseMessagePlaceholders($message, $context); + $message = empty($placeholders) ? $message : strtr($message, $placeholders); + $this->fail($message); + } + }