Loading core/lib/Drupal/Core/Config/ConfigManager.php +10 −6 Original line number Diff line number Diff line Loading @@ -294,17 +294,21 @@ public function findConfigEntityDependenciesAsEntities($type, array $names, ?Con // a UUID key. if ($entity_type_id) { $id = substr($config_name, strlen($definitions[$entity_type_id]->getConfigPrefix()) + 1); $entities[$entity_type_id][] = $id; $entities[$entity_type_id][$config_name] = $id; } } $entities_to_return = []; // Align the order of entities returned to the dependency order by first // populating the keys in the same order. $entities_to_return = array_fill_keys(array_keys($dependencies), NULL); foreach ($entities as $entity_type_id => $entities_to_load) { $storage = $this->entityTypeManager->getStorage($entity_type_id); // Remove the keys since there are potential ID clashes from different // configuration entity types. $entities_to_return[] = array_values($storage->loadMultiple($entities_to_load)); $loaded_entities = $storage->loadMultiple($entities_to_load); foreach ($loaded_entities as $loaded_entity) { $entities_to_return[$loaded_entity->getConfigDependencyName()] = $loaded_entity; } return array_merge(...$entities_to_return); } // Return entities list with NULL entries removed. return array_filter($entities_to_return); } /** Loading core/modules/config/tests/config_test/config_test.permissions.yml 0 → 100644 +2 −0 Original line number Diff line number Diff line permission_callbacks: - \Drupal\config_test\ConfigTestPermissions::configTestPermissions core/modules/config/tests/config_test/src/ConfigTestPermissions.php 0 → 100644 +52 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\config_test; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provide dynamic permissions for testing permission dependencies on config. */ class ConfigTestPermissions implements ContainerInjectionInterface { use StringTranslationTrait; public function __construct(protected EntityTypeManagerInterface $entityTypeManager) {} /** * {@inheritdoc} */ public static function create(ContainerInterface $container): static { return new static( $container->get('entity_type.manager'), ); } /** * Permissions callback. * * @return array * The list of permissions. */ public function configTestPermissions(): array { /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface[] $entities */ $entities = $this->entityTypeManager->getStorage('config_test')->loadMultiple(); $permissions = []; foreach ($entities as $entity) { $config_name = $entity->getConfigDependencyName(); $permissions["permission with $config_name dependency"] = [ 'title' => $this->t('Permission with a dependency on config test entity %id', [ '%id' => $entity->id(), ]), 'dependencies' => [$entity->getConfigDependencyKey() => [$config_name]], ]; } return $permissions; } } core/tests/Drupal/KernelTests/Core/Config/ConfigDependencyTest.php +52 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ use Drupal\entity_test\Entity\EntityTest; use Drupal\KernelTests\Core\Entity\EntityKernelTestBase; use Drupal\user\Entity\Role; /** * Tests for configuration dependencies. Loading Loading @@ -640,6 +641,57 @@ public function testContentEntityDelete(): void { $this->assertEmpty($config_entities['unchanged'], 'No dependencies of the content entity will be unchanged.'); } /** * Tests that config dependency ordering. */ public function testDependencyOrder(): void { $storage = $this->container->get('entity_type.manager')->getStorage('config_test'); // Test dependencies between modules. $entity1 = $storage->create(['id' => 'entity1']); $entity1->save(); // Create additional entities to test dependencies on config entities. $entity2 = $storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]); $entity2->save(); $entity3 = $storage->create(['id' => 'entity3', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]); $entity3->save(); // Include a role entity to test ordering when dependencies have multiple // entity types. $role = Role::create([ 'id' => 'test_role', 'label' => 'Test role', // This adds an implicit dependency on $entity 2, and hence also $entity1, // to the role. 'permissions' => ["permission with {$entity2->getConfigDependencyName()} dependency"], ]); $role->save(); $entity4 = $storage->create([ 'id' => 'entity4', 'dependencies' => [ 'enforced' => [ 'config' => [ // Add dependencies to $entity3 and the role so that the $entity4 // should be last to be processed when handling dependency removal. // The role should be processed after $entity1 and $entity2, but // before $entity4. $entity3->getConfigDependencyName(), $role->getConfigDependencyName(), ], ], ], ]); $entity4->save(); // Create scenario where entity1 is deleted, but all the config_test // entities depending on entity1 are fixed instead of being deleted. This // means that entity2 is not deleted, so the role should not lose the // permission depending on entity2. \Drupal::state()->set('config_test.fix_dependencies', ['config_test.dynamic.entity1']); $entity1->delete(); $role = Role::load('test_role'); $this->assertNotNull($role); $this->assertTrue($role->hasPermission("permission with {$entity2->getConfigDependencyName()} dependency")); } /** * Gets a list of identifiers from an array of configuration entities. * Loading Loading
core/lib/Drupal/Core/Config/ConfigManager.php +10 −6 Original line number Diff line number Diff line Loading @@ -294,17 +294,21 @@ public function findConfigEntityDependenciesAsEntities($type, array $names, ?Con // a UUID key. if ($entity_type_id) { $id = substr($config_name, strlen($definitions[$entity_type_id]->getConfigPrefix()) + 1); $entities[$entity_type_id][] = $id; $entities[$entity_type_id][$config_name] = $id; } } $entities_to_return = []; // Align the order of entities returned to the dependency order by first // populating the keys in the same order. $entities_to_return = array_fill_keys(array_keys($dependencies), NULL); foreach ($entities as $entity_type_id => $entities_to_load) { $storage = $this->entityTypeManager->getStorage($entity_type_id); // Remove the keys since there are potential ID clashes from different // configuration entity types. $entities_to_return[] = array_values($storage->loadMultiple($entities_to_load)); $loaded_entities = $storage->loadMultiple($entities_to_load); foreach ($loaded_entities as $loaded_entity) { $entities_to_return[$loaded_entity->getConfigDependencyName()] = $loaded_entity; } return array_merge(...$entities_to_return); } // Return entities list with NULL entries removed. return array_filter($entities_to_return); } /** Loading
core/modules/config/tests/config_test/config_test.permissions.yml 0 → 100644 +2 −0 Original line number Diff line number Diff line permission_callbacks: - \Drupal\config_test\ConfigTestPermissions::configTestPermissions
core/modules/config/tests/config_test/src/ConfigTestPermissions.php 0 → 100644 +52 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\config_test; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provide dynamic permissions for testing permission dependencies on config. */ class ConfigTestPermissions implements ContainerInjectionInterface { use StringTranslationTrait; public function __construct(protected EntityTypeManagerInterface $entityTypeManager) {} /** * {@inheritdoc} */ public static function create(ContainerInterface $container): static { return new static( $container->get('entity_type.manager'), ); } /** * Permissions callback. * * @return array * The list of permissions. */ public function configTestPermissions(): array { /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface[] $entities */ $entities = $this->entityTypeManager->getStorage('config_test')->loadMultiple(); $permissions = []; foreach ($entities as $entity) { $config_name = $entity->getConfigDependencyName(); $permissions["permission with $config_name dependency"] = [ 'title' => $this->t('Permission with a dependency on config test entity %id', [ '%id' => $entity->id(), ]), 'dependencies' => [$entity->getConfigDependencyKey() => [$config_name]], ]; } return $permissions; } }
core/tests/Drupal/KernelTests/Core/Config/ConfigDependencyTest.php +52 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ use Drupal\entity_test\Entity\EntityTest; use Drupal\KernelTests\Core\Entity\EntityKernelTestBase; use Drupal\user\Entity\Role; /** * Tests for configuration dependencies. Loading Loading @@ -640,6 +641,57 @@ public function testContentEntityDelete(): void { $this->assertEmpty($config_entities['unchanged'], 'No dependencies of the content entity will be unchanged.'); } /** * Tests that config dependency ordering. */ public function testDependencyOrder(): void { $storage = $this->container->get('entity_type.manager')->getStorage('config_test'); // Test dependencies between modules. $entity1 = $storage->create(['id' => 'entity1']); $entity1->save(); // Create additional entities to test dependencies on config entities. $entity2 = $storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]); $entity2->save(); $entity3 = $storage->create(['id' => 'entity3', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]); $entity3->save(); // Include a role entity to test ordering when dependencies have multiple // entity types. $role = Role::create([ 'id' => 'test_role', 'label' => 'Test role', // This adds an implicit dependency on $entity 2, and hence also $entity1, // to the role. 'permissions' => ["permission with {$entity2->getConfigDependencyName()} dependency"], ]); $role->save(); $entity4 = $storage->create([ 'id' => 'entity4', 'dependencies' => [ 'enforced' => [ 'config' => [ // Add dependencies to $entity3 and the role so that the $entity4 // should be last to be processed when handling dependency removal. // The role should be processed after $entity1 and $entity2, but // before $entity4. $entity3->getConfigDependencyName(), $role->getConfigDependencyName(), ], ], ], ]); $entity4->save(); // Create scenario where entity1 is deleted, but all the config_test // entities depending on entity1 are fixed instead of being deleted. This // means that entity2 is not deleted, so the role should not lose the // permission depending on entity2. \Drupal::state()->set('config_test.fix_dependencies', ['config_test.dynamic.entity1']); $entity1->delete(); $role = Role::load('test_role'); $this->assertNotNull($role); $this->assertTrue($role->hasPermission("permission with {$entity2->getConfigDependencyName()} dependency")); } /** * Gets a list of identifiers from an array of configuration entities. * Loading