ConfigDependencyTest.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. <?php
  2. namespace Drupal\KernelTests\Core\Config;
  3. use Drupal\entity_test\Entity\EntityTest;
  4. use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
  5. /**
  6. * Tests for configuration dependencies.
  7. *
  8. * @coversDefaultClass \Drupal\Core\Config\ConfigManager
  9. *
  10. * @group config
  11. */
  12. class ConfigDependencyTest extends EntityKernelTestBase {
  13. /**
  14. * Modules to enable.
  15. *
  16. * The entity_test module is enabled to provide content entity types.
  17. *
  18. * @var array
  19. */
  20. public static $modules = ['config_test', 'entity_test', 'user'];
  21. /**
  22. * Tests that calculating dependencies for system module.
  23. */
  24. public function testNonEntity() {
  25. $this->installConfig(['system']);
  26. $config_manager = \Drupal::service('config.manager');
  27. $dependents = $config_manager->findConfigEntityDependents('module', ['system']);
  28. $this->assertTrue(isset($dependents['system.site']), 'Simple configuration system.site has a UUID key even though it is not a configuration entity and therefore is found when looking for dependencies of the System module.');
  29. // Ensure that calling
  30. // \Drupal\Core\Config\ConfigManager::findConfigEntityDependentsAsEntities()
  31. // does not try to load system.site as an entity.
  32. $config_manager->findConfigEntityDependentsAsEntities('module', ['system']);
  33. }
  34. /**
  35. * Tests creating dependencies on configuration entities.
  36. */
  37. public function testDependencyManagement() {
  38. /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
  39. $config_manager = \Drupal::service('config.manager');
  40. $storage = $this->container->get('entity.manager')->getStorage('config_test');
  41. // Test dependencies between modules.
  42. $entity1 = $storage->create(
  43. [
  44. 'id' => 'entity1',
  45. 'dependencies' => [
  46. 'enforced' => [
  47. 'module' => ['node']
  48. ]
  49. ]
  50. ]
  51. );
  52. $entity1->save();
  53. $dependents = $config_manager->findConfigEntityDependents('module', ['node']);
  54. $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the Node module.');
  55. $dependents = $config_manager->findConfigEntityDependents('module', ['config_test']);
  56. $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the config_test module.');
  57. $dependents = $config_manager->findConfigEntityDependents('module', ['views']);
  58. $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Views module.');
  59. // Ensure that the provider of the config entity is not actually written to
  60. // the dependencies array.
  61. $raw_config = $this->config('config_test.dynamic.entity1');
  62. $root_module_dependencies = $raw_config->get('dependencies.module');
  63. $this->assertTrue(empty($root_module_dependencies), 'Node module is not written to the root dependencies array as it is enforced.');
  64. // Create additional entities to test dependencies on config entities.
  65. $entity2 = $storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]);
  66. $entity2->save();
  67. $entity3 = $storage->create(['id' => 'entity3', 'dependencies' => ['enforced' => ['config' => [$entity2->getConfigDependencyName()]]]]);
  68. $entity3->save();
  69. $entity4 = $storage->create(['id' => 'entity4', 'dependencies' => ['enforced' => ['config' => [$entity3->getConfigDependencyName()]]]]);
  70. $entity4->save();
  71. // Test getting $entity1's dependencies as configuration dependency objects.
  72. $dependents = $config_manager->findConfigEntityDependents('config', [$entity1->getConfigDependencyName()]);
  73. $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on itself.');
  74. $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.');
  75. $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.');
  76. $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.');
  77. // Test getting $entity2's dependencies as entities.
  78. $dependents = $config_manager->findConfigEntityDependentsAsEntities('config', [$entity2->getConfigDependencyName()]);
  79. $dependent_ids = $this->getDependentIds($dependents);
  80. $this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on config_test.dynamic.entity1.');
  81. $this->assertFalse(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 does not have a dependency on itself.');
  82. $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity2.');
  83. $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity2.');
  84. // Test getting node module's dependencies as configuration dependency
  85. // objects.
  86. $dependents = $config_manager->findConfigEntityDependents('module', ['node']);
  87. $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the Node module.');
  88. $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on the Node module.');
  89. $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the Node module.');
  90. $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the Node module.');
  91. // Test getting node module's dependencies as configuration dependency
  92. // objects after making $entity3 also dependent on node module but $entity1
  93. // no longer depend on node module.
  94. $entity1->setEnforcedDependencies([])->save();
  95. $entity3->setEnforcedDependencies(['module' => ['node'], 'config' => [$entity2->getConfigDependencyName()]])->save();
  96. $dependents = $config_manager->findConfigEntityDependents('module', ['node']);
  97. $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Node module.');
  98. $this->assertFalse(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 does not have a dependency on the Node module.');
  99. $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the Node module.');
  100. $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the Node module.');
  101. // Test dependency on a content entity.
  102. $entity_test = EntityTest::create([
  103. 'name' => $this->randomString(),
  104. 'type' => 'entity_test',
  105. ]);
  106. $entity_test->save();
  107. $entity2->setEnforcedDependencies(['config' => [$entity1->getConfigDependencyName()], 'content' => [$entity_test->getConfigDependencyName()]])->save();;
  108. $dependents = $config_manager->findConfigEntityDependents('content', [$entity_test->getConfigDependencyName()]);
  109. $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the content entity.');
  110. $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on the content entity.');
  111. $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the content entity (via entity2).');
  112. $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the content entity (via entity3).');
  113. // Create a configuration entity of a different type with the same ID as one
  114. // of the entities already created.
  115. $alt_storage = $this->container->get('entity.manager')->getStorage('config_query_test');
  116. $alt_storage->create(['id' => 'entity1', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]])->save();
  117. $alt_storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['module' => ['views']]]])->save();
  118. $dependents = $config_manager->findConfigEntityDependentsAsEntities('config', [$entity1->getConfigDependencyName()]);
  119. $dependent_ids = $this->getDependentIds($dependents);
  120. $this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on itself.');
  121. $this->assertTrue(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.');
  122. $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.');
  123. $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.');
  124. $this->assertTrue(in_array('config_query_test:entity1', $dependent_ids), 'config_query_test.dynamic.entity1 has a dependency on config_test.dynamic.entity1.');
  125. $this->assertFalse(in_array('config_query_test:entity2', $dependent_ids), 'config_query_test.dynamic.entity2 does not have a dependency on config_test.dynamic.entity1.');
  126. $dependents = $config_manager->findConfigEntityDependentsAsEntities('module', ['node', 'views']);
  127. $dependent_ids = $this->getDependentIds($dependents);
  128. $this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on Views or Node.');
  129. $this->assertFalse(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 does not have a dependency on Views or Node.');
  130. $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on Views or Node.');
  131. $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on Views or Node.');
  132. $this->assertFalse(in_array('config_query_test:entity1', $dependent_ids), 'config_test.query.entity1 does not have a dependency on Views or Node.');
  133. $this->assertTrue(in_array('config_query_test:entity2', $dependent_ids), 'config_test.query.entity2 has a dependency on Views or Node.');
  134. $dependents = $config_manager->findConfigEntityDependentsAsEntities('module', ['config_test']);
  135. $dependent_ids = $this->getDependentIds($dependents);
  136. $this->assertTrue(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 has a dependency on config_test module.');
  137. $this->assertTrue(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 has a dependency on config_test module.');
  138. $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test module.');
  139. $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test module.');
  140. $this->assertTrue(in_array('config_query_test:entity1', $dependent_ids), 'config_test.query.entity1 has a dependency on config_test module.');
  141. $this->assertTrue(in_array('config_query_test:entity2', $dependent_ids), 'config_test.query.entity2 has a dependency on config_test module.');
  142. // Test the ability to find missing content dependencies.
  143. $missing_dependencies = $config_manager->findMissingContentDependencies();
  144. $this->assertEqual([], $missing_dependencies);
  145. $expected = [
  146. $entity_test->uuid() => [
  147. 'entity_type' => 'entity_test',
  148. 'bundle' => $entity_test->bundle(),
  149. 'uuid' => $entity_test->uuid(),
  150. ],
  151. ];
  152. // Delete the content entity so that is it now missing.
  153. $entity_test->delete();
  154. $missing_dependencies = $config_manager->findMissingContentDependencies();
  155. $this->assertEqual($expected, $missing_dependencies);
  156. // Add a fake missing dependency to ensure multiple missing dependencies
  157. // work.
  158. $entity1->setEnforcedDependencies(['content' => [$entity_test->getConfigDependencyName(), 'entity_test:bundle:uuid']])->save();;
  159. $expected['uuid'] = [
  160. 'entity_type' => 'entity_test',
  161. 'bundle' => 'bundle',
  162. 'uuid' => 'uuid',
  163. ];
  164. $missing_dependencies = $config_manager->findMissingContentDependencies();
  165. $this->assertEqual($expected, $missing_dependencies);
  166. }
  167. /**
  168. * Tests ConfigManager::uninstall() and config entity dependency management.
  169. */
  170. public function testConfigEntityUninstall() {
  171. /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
  172. $config_manager = \Drupal::service('config.manager');
  173. /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
  174. $storage = $this->container->get('entity.manager')
  175. ->getStorage('config_test');
  176. // Test dependencies between modules.
  177. $entity1 = $storage->create(
  178. [
  179. 'id' => 'entity1',
  180. 'dependencies' => [
  181. 'enforced' => [
  182. 'module' => ['node', 'config_test']
  183. ],
  184. ],
  185. ]
  186. );
  187. $entity1->save();
  188. $entity2 = $storage->create(
  189. [
  190. 'id' => 'entity2',
  191. 'dependencies' => [
  192. 'enforced' => [
  193. 'config' => [$entity1->getConfigDependencyName()],
  194. ],
  195. ],
  196. ]
  197. );
  198. $entity2->save();
  199. // Perform a module rebuild so we can know where the node module is located
  200. // and uninstall it.
  201. // @todo Remove as part of https://www.drupal.org/node/2186491
  202. system_rebuild_module_data();
  203. // Test that doing a config uninstall of the node module deletes entity2
  204. // since it is dependent on entity1 which is dependent on the node module.
  205. $config_manager->uninstall('module', 'node');
  206. $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
  207. $this->assertFalse($storage->load('entity2'), 'Entity 2 deleted');
  208. }
  209. /**
  210. * Data provider for self::testConfigEntityUninstallComplex().
  211. */
  212. public function providerConfigEntityUninstallComplex() {
  213. // Ensure that alphabetical order has no influence on dependency fixing and
  214. // removal.
  215. return [
  216. [['a', 'b', 'c', 'd']],
  217. [['d', 'c', 'b', 'a']],
  218. [['c', 'd', 'a', 'b']],
  219. ];
  220. }
  221. /**
  222. * Tests complex configuration entity dependency handling during uninstall.
  223. *
  224. * Configuration entities can be deleted or updated during module uninstall
  225. * because they have dependencies on the module.
  226. *
  227. * @param array $entity_id_suffixes
  228. * The suffixes to add to the 4 entities created by the test.
  229. *
  230. * @dataProvider providerConfigEntityUninstallComplex
  231. */
  232. public function testConfigEntityUninstallComplex(array $entity_id_suffixes) {
  233. /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
  234. $config_manager = \Drupal::service('config.manager');
  235. /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
  236. $storage = $this->container->get('entity.manager')
  237. ->getStorage('config_test');
  238. // Entity 1 will be deleted because it depends on node.
  239. $entity_1 = $storage->create(
  240. [
  241. 'id' => 'entity_' . $entity_id_suffixes[0],
  242. 'dependencies' => [
  243. 'enforced' => [
  244. 'module' => ['node', 'config_test']
  245. ],
  246. ],
  247. ]
  248. );
  249. $entity_1->save();
  250. // Entity 2 has a dependency on entity 1 but it can be fixed because
  251. // \Drupal\config_test\Entity::onDependencyRemoval() will remove the
  252. // dependency before config entities are deleted.
  253. $entity_2 = $storage->create(
  254. [
  255. 'id' => 'entity_' . $entity_id_suffixes[1],
  256. 'dependencies' => [
  257. 'enforced' => [
  258. 'config' => [$entity_1->getConfigDependencyName()],
  259. ],
  260. ],
  261. ]
  262. );
  263. $entity_2->save();
  264. // Entity 3 will be unchanged because it is dependent on entity 2 which can
  265. // be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
  266. // not be called for this entity.
  267. $entity_3 = $storage->create(
  268. [
  269. 'id' => 'entity_' . $entity_id_suffixes[2],
  270. 'dependencies' => [
  271. 'enforced' => [
  272. 'config' => [$entity_2->getConfigDependencyName()],
  273. ],
  274. ],
  275. ]
  276. );
  277. $entity_3->save();
  278. // Entity 4's config dependency will be fixed but it will still be deleted
  279. // because it also depends on the node module.
  280. $entity_4 = $storage->create(
  281. [
  282. 'id' => 'entity_' . $entity_id_suffixes[3],
  283. 'dependencies' => [
  284. 'enforced' => [
  285. 'config' => [$entity_1->getConfigDependencyName()],
  286. 'module' => ['node', 'config_test']
  287. ],
  288. ],
  289. ]
  290. );
  291. $entity_4->save();
  292. // Set a more complicated test where dependencies will be fixed.
  293. \Drupal::state()->set('config_test.fix_dependencies', [$entity_1->getConfigDependencyName()]);
  294. \Drupal::state()->set('config_test.on_dependency_removal_called', []);
  295. // Do a dry run using
  296. // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
  297. $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
  298. $this->assertEqual($entity_1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.');
  299. $this->assertEqual($entity_2->uuid(), reset($config_entities['update'])->uuid(), 'Entity 2 will be updated.');
  300. $this->assertEqual($entity_3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
  301. $this->assertEqual($entity_4->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 4 will be deleted.');
  302. $called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
  303. $this->assertFalse(in_array($entity_3->id(), $called), 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
  304. $this->assertSame([$entity_1->id(), $entity_4->id(), $entity_2->id()], $called, 'The most dependent entites have ConfigEntityInterface::onDependencyRemoval() called first.');
  305. // Perform a module rebuild so we can know where the node module is located
  306. // and uninstall it.
  307. // @todo Remove as part of https://www.drupal.org/node/2186491
  308. system_rebuild_module_data();
  309. // Perform the uninstall.
  310. $config_manager->uninstall('module', 'node');
  311. // Test that expected actions have been performed.
  312. $this->assertFalse($storage->load($entity_1->id()), 'Entity 1 deleted');
  313. $entity_2 = $storage->load($entity_2->id());
  314. $this->assertTrue($entity_2, 'Entity 2 not deleted');
  315. $this->assertEqual($entity_2->calculateDependencies()->getDependencies()['config'], [], 'Entity 2 dependencies updated to remove dependency on entity 1.');
  316. $entity_3 = $storage->load($entity_3->id());
  317. $this->assertTrue($entity_3, 'Entity 3 not deleted');
  318. $this->assertEqual($entity_3->calculateDependencies()->getDependencies()['config'], [$entity_2->getConfigDependencyName()], 'Entity 3 still depends on entity 2.');
  319. $this->assertFalse($storage->load($entity_4->id()), 'Entity 4 deleted');
  320. }
  321. /**
  322. * @covers ::uninstall
  323. * @covers ::getConfigEntitiesToChangeOnDependencyRemoval
  324. */
  325. public function testConfigEntityUninstallThirdParty() {
  326. /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
  327. $config_manager = \Drupal::service('config.manager');
  328. /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
  329. $storage = $this->container->get('entity_type.manager')
  330. ->getStorage('config_test');
  331. // Entity 1 will be fixed because it only has a dependency via third-party
  332. // settings, which are fixable.
  333. $entity_1 = $storage->create([
  334. 'id' => 'entity_1',
  335. 'dependencies' => [
  336. 'enforced' => [
  337. 'module' => ['config_test'],
  338. ],
  339. ],
  340. 'third_party_settings' => [
  341. 'node' => [
  342. 'foo' => 'bar',
  343. ],
  344. ],
  345. ]);
  346. $entity_1->save();
  347. // Entity 2 has a dependency on entity 1.
  348. $entity_2 = $storage->create([
  349. 'id' => 'entity_2',
  350. 'dependencies' => [
  351. 'enforced' => [
  352. 'config' => [$entity_1->getConfigDependencyName()],
  353. ],
  354. ],
  355. 'third_party_settings' => [
  356. 'node' => [
  357. 'foo' => 'bar',
  358. ],
  359. ],
  360. ]);
  361. $entity_2->save();
  362. // Entity 3 will be unchanged because it is dependent on entity 2 which can
  363. // be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
  364. // not be called for this entity.
  365. $entity_3 = $storage->create([
  366. 'id' => 'entity_3',
  367. 'dependencies' => [
  368. 'enforced' => [
  369. 'config' => [$entity_2->getConfigDependencyName()],
  370. ],
  371. ],
  372. ]);
  373. $entity_3->save();
  374. // Entity 4's config dependency will be fixed but it will still be deleted
  375. // because it also depends on the node module.
  376. $entity_4 = $storage->create([
  377. 'id' => 'entity_4',
  378. 'dependencies' => [
  379. 'enforced' => [
  380. 'config' => [$entity_1->getConfigDependencyName()],
  381. 'module' => ['node', 'config_test'],
  382. ],
  383. ],
  384. ]);
  385. $entity_4->save();
  386. \Drupal::state()->set('config_test.fix_dependencies', []);
  387. \Drupal::state()->set('config_test.on_dependency_removal_called', []);
  388. // Do a dry run using
  389. // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
  390. $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
  391. $config_entity_ids = [
  392. 'update' => [],
  393. 'delete' => [],
  394. 'unchanged' => [],
  395. ];
  396. foreach ($config_entities as $type => $config_entities_by_type) {
  397. foreach ($config_entities_by_type as $config_entity) {
  398. $config_entity_ids[$type][] = $config_entity->id();
  399. }
  400. }
  401. $expected = [
  402. 'update' => [$entity_1->id(), $entity_2->id()],
  403. 'delete' => [$entity_4->id()],
  404. 'unchanged' => [$entity_3->id()],
  405. ];
  406. $this->assertSame($expected, $config_entity_ids);
  407. $called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
  408. $this->assertFalse(in_array($entity_3->id(), $called), 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
  409. $this->assertSame([$entity_1->id(), $entity_4->id(), $entity_2->id()], $called, 'The most dependent entities have ConfigEntityInterface::onDependencyRemoval() called first.');
  410. // Perform a module rebuild so we can know where the node module is located
  411. // and uninstall it.
  412. // @todo Remove as part of https://www.drupal.org/node/2186491
  413. system_rebuild_module_data();
  414. // Perform the uninstall.
  415. $config_manager->uninstall('module', 'node');
  416. // Test that expected actions have been performed.
  417. $entity_1 = $storage->load($entity_1->id());
  418. $this->assertTrue($entity_1, 'Entity 1 not deleted');
  419. $this->assertSame($entity_1->getThirdPartySettings('node'), [], 'Entity 1 third party settings updated.');
  420. $entity_2 = $storage->load($entity_2->id());
  421. $this->assertTrue($entity_2, 'Entity 2 not deleted');
  422. $this->assertSame($entity_2->getThirdPartySettings('node'), [], 'Entity 2 third party settings updated.');
  423. $this->assertSame($entity_2->calculateDependencies()->getDependencies()['config'], [$entity_1->getConfigDependencyName()], 'Entity 2 still depends on entity 1.');
  424. $entity_3 = $storage->load($entity_3->id());
  425. $this->assertTrue($entity_3, 'Entity 3 not deleted');
  426. $this->assertSame($entity_3->calculateDependencies()->getDependencies()['config'], [$entity_2->getConfigDependencyName()], 'Entity 3 still depends on entity 2.');
  427. $this->assertFalse($storage->load($entity_4->id()), 'Entity 4 deleted');
  428. }
  429. /**
  430. * Tests deleting a configuration entity and dependency management.
  431. */
  432. public function testConfigEntityDelete() {
  433. /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
  434. $config_manager = \Drupal::service('config.manager');
  435. /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
  436. $storage = $this->container->get('entity.manager')->getStorage('config_test');
  437. // Test dependencies between configuration entities.
  438. $entity1 = $storage->create(
  439. [
  440. 'id' => 'entity1'
  441. ]
  442. );
  443. $entity1->save();
  444. $entity2 = $storage->create(
  445. [
  446. 'id' => 'entity2',
  447. 'dependencies' => [
  448. 'enforced' => [
  449. 'config' => [$entity1->getConfigDependencyName()],
  450. ],
  451. ],
  452. ]
  453. );
  454. $entity2->save();
  455. // Do a dry run using
  456. // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
  457. $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity1->getConfigDependencyName()]);
  458. $this->assertEqual($entity2->uuid(), reset($config_entities['delete'])->uuid(), 'Entity 2 will be deleted.');
  459. $this->assertTrue(empty($config_entities['update']), 'No dependent configuration entities will be updated.');
  460. $this->assertTrue(empty($config_entities['unchanged']), 'No dependent configuration entities will be unchanged.');
  461. // Test that doing a delete of entity1 deletes entity2 since it is dependent
  462. // on entity1.
  463. $entity1->delete();
  464. $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
  465. $this->assertFalse($storage->load('entity2'), 'Entity 2 deleted');
  466. // Set a more complicated test where dependencies will be fixed.
  467. \Drupal::state()->set('config_test.fix_dependencies', [$entity1->getConfigDependencyName()]);
  468. // Entity1 will be deleted by the test.
  469. $entity1 = $storage->create(
  470. [
  471. 'id' => 'entity1',
  472. ]
  473. );
  474. $entity1->save();
  475. // Entity2 has a dependency on Entity1 but it can be fixed because
  476. // \Drupal\config_test\Entity::onDependencyRemoval() will remove the
  477. // dependency before config entities are deleted.
  478. $entity2 = $storage->create(
  479. [
  480. 'id' => 'entity2',
  481. 'dependencies' => [
  482. 'enforced' => [
  483. 'config' => [$entity1->getConfigDependencyName()],
  484. ],
  485. ],
  486. ]
  487. );
  488. $entity2->save();
  489. // Entity3 will be unchanged because it is dependent on Entity2 which can
  490. // be fixed.
  491. $entity3 = $storage->create(
  492. [
  493. 'id' => 'entity3',
  494. 'dependencies' => [
  495. 'enforced' => [
  496. 'config' => [$entity2->getConfigDependencyName()],
  497. ],
  498. ],
  499. ]
  500. );
  501. $entity3->save();
  502. // Do a dry run using
  503. // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
  504. $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity1->getConfigDependencyName()]);
  505. $this->assertTrue(empty($config_entities['delete']), 'No dependent configuration entities will be deleted.');
  506. $this->assertEqual($entity2->uuid(), reset($config_entities['update'])->uuid(), 'Entity 2 will be updated.');
  507. $this->assertEqual($entity3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
  508. // Perform the uninstall.
  509. $entity1->delete();
  510. // Test that expected actions have been performed.
  511. $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
  512. $entity2 = $storage->load('entity2');
  513. $this->assertTrue($entity2, 'Entity 2 not deleted');
  514. $this->assertEqual($entity2->calculateDependencies()->getDependencies()['config'], [], 'Entity 2 dependencies updated to remove dependency on Entity1.');
  515. $entity3 = $storage->load('entity3');
  516. $this->assertTrue($entity3, 'Entity 3 not deleted');
  517. $this->assertEqual($entity3->calculateDependencies()->getDependencies()['config'], [$entity2->getConfigDependencyName()], 'Entity 3 still depends on Entity 2.');
  518. }
  519. /**
  520. * Tests getConfigEntitiesToChangeOnDependencyRemoval() with content entities.
  521. *
  522. * At the moment there is no runtime code that calculates configuration
  523. * dependencies on content entity delete because this calculation is expensive
  524. * and all content dependencies are soft. This test ensures that the code
  525. * works for content entities.
  526. *
  527. * @see \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval()
  528. */
  529. public function testContentEntityDelete() {
  530. $this->installEntitySchema('entity_test');
  531. /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
  532. $config_manager = \Drupal::service('config.manager');
  533. $content_entity = EntityTest::create();
  534. $content_entity->save();
  535. /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
  536. $storage = $this->container->get('entity.manager')->getStorage('config_test');
  537. $entity1 = $storage->create(
  538. [
  539. 'id' => 'entity1',
  540. 'dependencies' => [
  541. 'enforced' => [
  542. 'content' => [$content_entity->getConfigDependencyName()]
  543. ],
  544. ],
  545. ]
  546. );
  547. $entity1->save();
  548. $entity2 = $storage->create(
  549. [
  550. 'id' => 'entity2',
  551. 'dependencies' => [
  552. 'enforced' => [
  553. 'config' => [$entity1->getConfigDependencyName()]
  554. ],
  555. ],
  556. ]
  557. );
  558. $entity2->save();
  559. // Create a configuration entity that is not in the dependency chain.
  560. $entity3 = $storage->create(['id' => 'entity3']);
  561. $entity3->save();
  562. $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('content', [$content_entity->getConfigDependencyName()]);
  563. $this->assertEqual($entity1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.');
  564. $this->assertEqual($entity2->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 2 will be deleted.');
  565. $this->assertTrue(empty($config_entities['update']), 'No dependencies of the content entity will be updated.');
  566. $this->assertTrue(empty($config_entities['unchanged']), 'No dependencies of the content entity will be unchanged.');
  567. }
  568. /**
  569. * Gets a list of identifiers from an array of configuration entities.
  570. *
  571. * @param \Drupal\Core\Config\Entity\ConfigEntityInterface[] $dependents
  572. * An array of configuration entities.
  573. *
  574. * @return array
  575. * An array with values of entity_type_id:ID
  576. */
  577. protected function getDependentIds(array $dependents) {
  578. $dependent_ids = [];
  579. foreach ($dependents as $dependent) {
  580. $dependent_ids[] = $dependent->getEntityTypeId() . ':' . $dependent->id();
  581. }
  582. return $dependent_ids;
  583. }
  584. }