ConfigDependencyManager.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. <?php
  2. namespace Drupal\Core\Config\Entity;
  3. use Drupal\Component\Graph\Graph;
  4. use Drupal\Component\Utility\SortArray;
  5. /**
  6. * Provides a class to discover configuration entity dependencies.
  7. *
  8. * Configuration entities can depend on modules, themes and other configuration
  9. * entities. The dependency system is used during configuration installation,
  10. * uninstallation, and synchronization to ensure that configuration entities are
  11. * handled in the correct order. For example, node types are created before
  12. * their fields, and both are created before the view display configuration.
  13. *
  14. * The configuration dependency value is structured like this:
  15. * @code
  16. * array(
  17. * 'config' => array(
  18. * // An array of configuration entity object names. Recalculated on save.
  19. * ),
  20. * 'content' => array(
  21. * // An array of content entity configuration dependency names. The default
  22. * // format is "ENTITY_TYPE_ID:BUNDLE:UUID". Recalculated on save.
  23. * ),
  24. * 'module' => array(
  25. * // An array of module names. Recalculated on save.
  26. * ),
  27. * 'theme' => array(
  28. * // An array of theme names. Recalculated on save.
  29. * ),
  30. * 'enforced' => array(
  31. * // An array of configuration dependencies that the config entity is
  32. * // ensured to have regardless of the details of the configuration. These
  33. * // dependencies are not recalculated on save.
  34. * 'config' => array(),
  35. * 'content' => array(),
  36. * 'module' => array(),
  37. * 'theme' => array(),
  38. * ),
  39. * );
  40. * @endcode
  41. *
  42. * Configuration entity dependencies are recalculated on save based on the
  43. * current values of the configuration. For example, a filter format will depend
  44. * on the modules that provide the filter plugins it configures. The filter
  45. * format can be reconfigured to use a different filter plugin provided by
  46. * another module. If this occurs, the dependencies will be recalculated on save
  47. * and the old module will be removed from the list of dependencies and replaced
  48. * with the new one.
  49. *
  50. * Configuration entity classes usually extend
  51. * \Drupal\Core\Config\Entity\ConfigEntityBase. The base class provides a
  52. * generic implementation of the calculateDependencies() method that can
  53. * discover dependencies due to plugins, and third party settings. If the
  54. * configuration entity has dependencies that cannot be discovered by the base
  55. * class's implementation, then it needs to implement
  56. * \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() to
  57. * calculate the dependencies. In this method, use
  58. * \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency() to add
  59. * dependencies. Implementations should call the base class implementation to
  60. * inherit the generic functionality.
  61. *
  62. * Classes for configurable plugins are a special case. They can either declare
  63. * their configuration dependencies using the calculateDependencies() method
  64. * described in the paragraph above, or if they have only static dependencies,
  65. * these can be declared using the 'config_dependencies' annotation key.
  66. *
  67. * If an extension author wants a configuration entity to depend on something
  68. * that is not calculable then they can add these dependencies to the enforced
  69. * dependencies key. For example, the Forum module provides the forum node type
  70. * and in order for it to be deleted when the forum module is uninstalled it has
  71. * an enforced dependency on the module. The dependency on the Forum module can
  72. * not be calculated since there is nothing inherent in the state of the node
  73. * type configuration entity that depends on functionality provided by the Forum
  74. * module.
  75. *
  76. * Once declared properly, dependencies are saved to the configuration entity's
  77. * configuration object so that they can be checked without the module that
  78. * provides the configuration entity class being installed. This is important
  79. * for configuration synchronization, which needs to be able to validate
  80. * configuration in the sync directory before the synchronization has occurred.
  81. * Also, if you have a configuration entity object and you want to get the
  82. * current dependencies (without recalculation), you can use
  83. * \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies().
  84. *
  85. * When uninstalling a module or a theme, configuration entities that are
  86. * dependent will also be removed. This default behavior can lead to undesirable
  87. * side effects, such as a node view mode being entirely removed when the module
  88. * defining a field or formatter it uses is uninstalled. To prevent this,
  89. * configuration entity classes can implement
  90. * \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval(),
  91. * which allows the entity class to remove dependencies instead of being deleted
  92. * themselves. Implementations should save the entity if dependencies have been
  93. * successfully removed, in order to register the newly cleaned-out dependency
  94. * list. So, for example, the node view mode configuration entity class
  95. * should implement this method to remove references to formatters if the plugin
  96. * that supplies them depends on a module that is being uninstalled.
  97. *
  98. * If a configuration entity is provided as default configuration by an
  99. * extension (module, theme, or profile), the extension has to depend on any
  100. * modules or themes that the configuration depends on. For example, if a view
  101. * configuration entity is provided by an installation profile and the view will
  102. * not work without a certain module, the profile must declare a dependency on
  103. * this module in its info.yml file. If you do not want your extension to always
  104. * depend on a particular module that one of its default configuration entities
  105. * depends on, you can use a sub-module: move the configuration entity to the
  106. * sub-module instead of including it in the main extension, and declare the
  107. * module dependency in the sub-module only.
  108. *
  109. * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies()
  110. * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies()
  111. * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval()
  112. * @see \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency()
  113. * @see \Drupal\Core\Config\ConfigInstallerInterface::installDefaultConfig()
  114. * @see \Drupal\Core\Config\ConfigManagerInterface::uninstall()
  115. * @see \Drupal\Core\Config\Entity\ConfigEntityDependency
  116. * @see \Drupal\Core\Entity\EntityInterface::getConfigDependencyName()
  117. * @see \Drupal\Core\Plugin\PluginDependencyTrait
  118. */
  119. class ConfigDependencyManager {
  120. /**
  121. * The config entity data.
  122. *
  123. * @var \Drupal\Core\Config\Entity\ConfigEntityDependency[]
  124. */
  125. protected $data = [];
  126. /**
  127. * The directed acyclic graph.
  128. *
  129. * @var array
  130. */
  131. protected $graph;
  132. /**
  133. * Gets dependencies.
  134. *
  135. * @param string $type
  136. * The type of dependency being checked. Either 'module', 'theme', 'config'
  137. * or 'content'.
  138. * @param string $name
  139. * The specific name to check. If $type equals 'module' or 'theme' then it
  140. * should be a module name or theme name. In the case of entity it should be
  141. * the full configuration object name.
  142. *
  143. * @return \Drupal\Core\Config\Entity\ConfigEntityDependency[]
  144. * An array of config entity dependency objects that are dependent.
  145. */
  146. public function getDependentEntities($type, $name) {
  147. $dependent_entities = [];
  148. $entities_to_check = [];
  149. if ($type == 'config') {
  150. $entities_to_check[] = $name;
  151. }
  152. else {
  153. if ($type == 'module' || $type == 'theme' || $type == 'content') {
  154. $dependent_entities = array_filter($this->data, function (ConfigEntityDependency $entity) use ($type, $name) {
  155. return $entity->hasDependency($type, $name);
  156. });
  157. }
  158. // If checking content, module, or theme dependencies, discover which
  159. // entities are dependent on the entities that have a direct dependency.
  160. foreach ($dependent_entities as $entity) {
  161. $entities_to_check[] = $entity->getConfigDependencyName();
  162. }
  163. }
  164. $dependencies = array_merge($this->createGraphConfigEntityDependencies($entities_to_check), $dependent_entities);
  165. // Sort dependencies in the reverse order of the graph. So the least
  166. // dependent is at the top. For example, this ensures that fields are
  167. // always after field storages. This is because field storages need to be
  168. // created before a field.
  169. $graph = $this->getGraph();
  170. $sorts = $this->prepareMultisort($graph, ['weight', 'name']);
  171. array_multisort($sorts['weight'], SORT_DESC, SORT_NUMERIC, $sorts['name'], SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $graph);
  172. return array_replace(array_intersect_key($graph, $dependencies), $dependencies);
  173. }
  174. /**
  175. * Extracts data from the graph for use in array_multisort().
  176. *
  177. * @param array $graph
  178. * The graph to extract data from.
  179. * @param array $keys
  180. * The keys whose values to extract.
  181. *
  182. * @return
  183. * An array keyed by the $keys passed in. The values are arrays keyed by the
  184. * row from the graph and the value is the corresponding value for the key
  185. * from the graph.
  186. */
  187. protected function prepareMultisort($graph, $keys) {
  188. $return = array_fill_keys($keys, []);
  189. foreach ($graph as $graph_key => $graph_row) {
  190. foreach ($keys as $key) {
  191. $return[$key][$graph_key] = $graph_row[$key];
  192. }
  193. }
  194. return $return;
  195. }
  196. /**
  197. * Sorts the dependencies in order of most dependent last.
  198. *
  199. * @return array
  200. * The list of entities in order of most dependent last, otherwise
  201. * alphabetical.
  202. */
  203. public function sortAll() {
  204. $graph = $this->getGraph();
  205. // Sort by weight and alphabetically. The most dependent entities
  206. // are last and entities with the same weight are alphabetically ordered.
  207. $sorts = $this->prepareMultisort($graph, ['weight', 'name']);
  208. array_multisort($sorts['weight'], SORT_ASC, SORT_NUMERIC, $sorts['name'], SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $graph);
  209. // Use array_intersect_key() to exclude modules and themes from the list.
  210. return array_keys(array_intersect_key($graph, $this->data));
  211. }
  212. /**
  213. * Sorts the dependency graph by weight and alphabetically.
  214. *
  215. * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
  216. * \Drupal\Core\Config\Entity\ConfigDependencyManager::prepareMultisort() and
  217. * array_multisort() instead.
  218. *
  219. * @param array $a
  220. * First item for comparison. The compared items should be associative
  221. * arrays that include a 'weight' and a 'name' key.
  222. * @param array $b
  223. * Second item for comparison.
  224. *
  225. * @return int
  226. * The comparison result for uasort().
  227. */
  228. protected static function sortGraphByWeight(array $a, array $b) {
  229. $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight');
  230. if ($weight_cmp === 0) {
  231. return SortArray::sortByKeyString($a, $b, 'name');
  232. }
  233. return $weight_cmp;
  234. }
  235. /**
  236. * Sorts the dependency graph by reverse weight and alphabetically.
  237. *
  238. * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
  239. * \Drupal\Core\Config\Entity\ConfigDependencyManager::prepareMultisort() and
  240. * array_multisort() instead.
  241. *
  242. * @param array $a
  243. * First item for comparison. The compared items should be associative
  244. * arrays that include a 'weight' and a 'name' key.
  245. * @param array $b
  246. * Second item for comparison.
  247. *
  248. * @return int
  249. * The comparison result for uasort().
  250. */
  251. public static function sortGraph(array $a, array $b) {
  252. $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight') * -1;
  253. if ($weight_cmp === 0) {
  254. return SortArray::sortByKeyString($a, $b, 'name');
  255. }
  256. return $weight_cmp;
  257. }
  258. /**
  259. * Creates a graph of config entity dependencies.
  260. *
  261. * @param array $entities_to_check
  262. * The configuration entity full configuration names to determine the
  263. * dependencies for.
  264. *
  265. * @return \Drupal\Core\Config\Entity\ConfigEntityDependency[]
  266. * A graph of config entity dependency objects that are dependent on the
  267. * supplied entities to check.
  268. */
  269. protected function createGraphConfigEntityDependencies($entities_to_check) {
  270. $dependent_entities = [];
  271. $graph = $this->getGraph();
  272. foreach ($entities_to_check as $entity) {
  273. if (isset($graph[$entity]) && !empty($graph[$entity]['paths'])) {
  274. foreach ($graph[$entity]['paths'] as $dependency => $value) {
  275. if (isset($this->data[$dependency])) {
  276. $dependent_entities[$dependency] = $this->data[$dependency];
  277. }
  278. }
  279. }
  280. }
  281. return $dependent_entities;
  282. }
  283. /**
  284. * Gets the dependency graph of all the config entities.
  285. *
  286. * @return array
  287. * The dependency graph of all the config entities.
  288. */
  289. protected function getGraph() {
  290. if (!isset($this->graph)) {
  291. $graph = [];
  292. foreach ($this->data as $entity) {
  293. $graph_key = $entity->getConfigDependencyName();
  294. if (!isset($graph[$graph_key])) {
  295. $graph[$graph_key] = [
  296. 'edges' => [],
  297. 'name' => $graph_key,
  298. ];
  299. }
  300. // Include all dependencies in the graph so that topographical sorting
  301. // works.
  302. foreach (array_merge($entity->getDependencies('config'), $entity->getDependencies('module'), $entity->getDependencies('theme')) as $dependency) {
  303. $graph[$dependency]['edges'][$graph_key] = TRUE;
  304. $graph[$dependency]['name'] = $dependency;
  305. }
  306. }
  307. // Ensure that order of the graph is consistent.
  308. krsort($graph);
  309. $graph_object = new Graph($graph);
  310. $this->graph = $graph_object->searchAndSort();
  311. }
  312. return $this->graph;
  313. }
  314. /**
  315. * Sets data to calculate dependencies for.
  316. *
  317. * The data is converted into lightweight ConfigEntityDependency objects.
  318. *
  319. * @param array $data
  320. * Configuration data keyed by configuration object name. Typically the
  321. * output of \Drupal\Core\Config\StorageInterface::loadMultiple().
  322. *
  323. * @return $this
  324. */
  325. public function setData(array $data) {
  326. array_walk($data, function (&$config, $name) {
  327. $config = new ConfigEntityDependency($name, $config);
  328. });
  329. $this->data = $data;
  330. $this->graph = NULL;
  331. return $this;
  332. }
  333. /**
  334. * Updates one of the lightweight ConfigEntityDependency objects.
  335. *
  336. * @param $name
  337. * The configuration dependency name.
  338. * @param array $dependencies
  339. * The configuration dependencies. The array is structured like this:
  340. * @code
  341. * array(
  342. * 'config' => array(
  343. * // An array of configuration entity object names.
  344. * ),
  345. * 'content' => array(
  346. * // An array of content entity configuration dependency names. The default
  347. * // format is "ENTITY_TYPE_ID:BUNDLE:UUID".
  348. * ),
  349. * 'module' => array(
  350. * // An array of module names.
  351. * ),
  352. * 'theme' => array(
  353. * // An array of theme names.
  354. * ),
  355. * );
  356. * @endcode
  357. *
  358. * @return $this
  359. */
  360. public function updateData($name, array $dependencies) {
  361. $this->graph = NULL;
  362. $this->data[$name] = new ConfigEntityDependency($name, ['dependencies' => $dependencies]);
  363. return $this;
  364. }
  365. }