3 namespace Drupal\Core\Config\Entity;
5 use Drupal\Component\Graph\Graph;
6 use Drupal\Component\Utility\SortArray;
9 * Provides a class to discover configuration entity dependencies.
11 * Configuration entities can depend on modules, themes and other configuration
12 * entities. The dependency system is used during configuration installation,
13 * uninstallation, and synchronization to ensure that configuration entities are
14 * handled in the correct order. For example, node types are created before
15 * their fields, and both are created before the view display configuration.
17 * The configuration dependency value is structured like this:
21 * // An array of configuration entity object names. Recalculated on save.
24 * // An array of content entity configuration dependency names. The default
25 * // format is "ENTITY_TYPE_ID:BUNDLE:UUID". Recalculated on save.
28 * // An array of module names. Recalculated on save.
31 * // An array of theme names. Recalculated on save.
33 * 'enforced' => array(
34 * // An array of configuration dependencies that the config entity is
35 * // ensured to have regardless of the details of the configuration. These
36 * // dependencies are not recalculated on save.
37 * 'config' => array(),
38 * 'content' => array(),
39 * 'module' => array(),
45 * Configuration entity dependencies are recalculated on save based on the
46 * current values of the configuration. For example, a filter format will depend
47 * on the modules that provide the filter plugins it configures. The filter
48 * format can be reconfigured to use a different filter plugin provided by
49 * another module. If this occurs, the dependencies will be recalculated on save
50 * and the old module will be removed from the list of dependencies and replaced
53 * Configuration entity classes usually extend
54 * \Drupal\Core\Config\Entity\ConfigEntityBase. The base class provides a
55 * generic implementation of the calculateDependencies() method that can
56 * discover dependencies due to plugins, and third party settings. If the
57 * configuration entity has dependencies that cannot be discovered by the base
58 * class's implementation, then it needs to implement
59 * \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() to
60 * calculate the dependencies. In this method, use
61 * \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency() to add
62 * dependencies. Implementations should call the base class implementation to
63 * inherit the generic functionality.
65 * Classes for configurable plugins are a special case. They can either declare
66 * their configuration dependencies using the calculateDependencies() method
67 * described in the paragraph above, or if they have only static dependencies,
68 * these can be declared using the 'config_dependencies' annotation key.
70 * If an extension author wants a configuration entity to depend on something
71 * that is not calculable then they can add these dependencies to the enforced
72 * dependencies key. For example, the Forum module provides the forum node type
73 * and in order for it to be deleted when the forum module is uninstalled it has
74 * an enforced dependency on the module. The dependency on the Forum module can
75 * not be calculated since there is nothing inherent in the state of the node
76 * type configuration entity that depends on functionality provided by the Forum
79 * Once declared properly, dependencies are saved to the configuration entity's
80 * configuration object so that they can be checked without the module that
81 * provides the configuration entity class being installed. This is important
82 * for configuration synchronization, which needs to be able to validate
83 * configuration in the sync directory before the synchronization has occurred.
84 * Also, if you have a configuration entity object and you want to get the
85 * current dependencies (without recalculation), you can use
86 * \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies().
88 * When uninstalling a module or a theme, configuration entities that are
89 * dependent will also be removed. This default behavior can lead to undesirable
90 * side effects, such as a node view mode being entirely removed when the module
91 * defining a field or formatter it uses is uninstalled. To prevent this,
92 * configuration entity classes can implement
93 * \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval(),
94 * which allows the entity class to remove dependencies instead of being deleted
95 * themselves. Implementations should save the entity if dependencies have been
96 * successfully removed, in order to register the newly cleaned-out dependency
97 * list. So, for example, the node view mode configuration entity class
98 * should implement this method to remove references to formatters if the plugin
99 * that supplies them depends on a module that is being uninstalled.
101 * If a configuration entity is provided as default configuration by an
102 * extension (module, theme, or profile), the extension has to depend on any
103 * modules or themes that the configuration depends on. For example, if a view
104 * configuration entity is provided by an installation profile and the view will
105 * not work without a certain module, the profile must declare a dependency on
106 * this module in its info.yml file. If you do not want your extension to always
107 * depend on a particular module that one of its default configuration entities
108 * depends on, you can use a sub-module: move the configuration entity to the
109 * sub-module instead of including it in the main extension, and declare the
110 * module dependency in the sub-module only.
112 * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies()
113 * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies()
114 * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval()
115 * @see \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency()
116 * @see \Drupal\Core\Config\ConfigInstallerInterface::installDefaultConfig()
117 * @see \Drupal\Core\Config\ConfigManagerInterface::uninstall()
118 * @see \Drupal\Core\Config\Entity\ConfigEntityDependency
119 * @see \Drupal\Core\Entity\EntityInterface::getConfigDependencyName()
120 * @see \Drupal\Core\Plugin\PluginDependencyTrait
122 class ConfigDependencyManager {
125 * The config entity data.
127 * @var \Drupal\Core\Config\Entity\ConfigEntityDependency[]
129 protected $data = [];
132 * The directed acyclic graph.
141 * @param string $type
142 * The type of dependency being checked. Either 'module', 'theme', 'config'
144 * @param string $name
145 * The specific name to check. If $type equals 'module' or 'theme' then it
146 * should be a module name or theme name. In the case of entity it should be
147 * the full configuration object name.
149 * @return \Drupal\Core\Config\Entity\ConfigEntityDependency[]
150 * An array of config entity dependency objects that are dependent.
152 public function getDependentEntities($type, $name) {
153 $dependent_entities = [];
155 $entities_to_check = [];
156 if ($type == 'config') {
157 $entities_to_check[] = $name;
160 if ($type == 'module' || $type == 'theme' || $type == 'content') {
161 $dependent_entities = array_filter($this->data, function (ConfigEntityDependency $entity) use ($type, $name) {
162 return $entity->hasDependency($type, $name);
165 // If checking content, module, or theme dependencies, discover which
166 // entities are dependent on the entities that have a direct dependency.
167 foreach ($dependent_entities as $entity) {
168 $entities_to_check[] = $entity->getConfigDependencyName();
171 $dependencies = array_merge($this->createGraphConfigEntityDependencies($entities_to_check), $dependent_entities);
172 // Sort dependencies in the reverse order of the graph. So the least
173 // dependent is at the top. For example, this ensures that fields are
174 // always after field storages. This is because field storages need to be
175 // created before a field.
176 $graph = $this->getGraph();
177 $sorts = $this->prepareMultisort($graph, ['weight', 'name']);
178 array_multisort($sorts['weight'], SORT_DESC, SORT_NUMERIC, $sorts['name'], SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $graph);
179 return array_replace(array_intersect_key($graph, $dependencies), $dependencies);
183 * Extracts data from the graph for use in array_multisort().
185 * @param array $graph
186 * The graph to extract data from.
188 * The keys whose values to extract.
191 * An array keyed by the $keys passed in. The values are arrays keyed by the
192 * row from the graph and the value is the corresponding value for the key
195 protected function prepareMultisort($graph, $keys) {
196 $return = array_fill_keys($keys, []);
197 foreach ($graph as $graph_key => $graph_row) {
198 foreach ($keys as $key) {
199 $return[$key][$graph_key] = $graph_row[$key];
206 * Sorts the dependencies in order of most dependent last.
209 * The list of entities in order of most dependent last, otherwise
212 public function sortAll() {
213 $graph = $this->getGraph();
214 // Sort by weight and alphabetically. The most dependent entities
215 // are last and entities with the same weight are alphabetically ordered.
216 $sorts = $this->prepareMultisort($graph, ['weight', 'name']);
217 array_multisort($sorts['weight'], SORT_ASC, SORT_NUMERIC, $sorts['name'], SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $graph);
218 // Use array_intersect_key() to exclude modules and themes from the list.
219 return array_keys(array_intersect_key($graph, $this->data));
223 * Sorts the dependency graph by weight and alphabetically.
225 * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
226 * \Drupal\Core\Config\Entity\ConfigDependencyManager::prepareMultisort() and
227 * array_multisort() instead.
230 * First item for comparison. The compared items should be associative
231 * arrays that include a 'weight' and a 'name' key.
233 * Second item for comparison.
236 * The comparison result for uasort().
238 protected static function sortGraphByWeight(array $a, array $b) {
239 $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight');
241 if ($weight_cmp === 0) {
242 return SortArray::sortByKeyString($a, $b, 'name');
248 * Sorts the dependency graph by reverse weight and alphabetically.
250 * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
251 * \Drupal\Core\Config\Entity\ConfigDependencyManager::prepareMultisort() and
252 * array_multisort() instead.
255 * First item for comparison. The compared items should be associative
256 * arrays that include a 'weight' and a 'name' key.
258 * Second item for comparison.
261 * The comparison result for uasort().
263 public static function sortGraph(array $a, array $b) {
264 $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight') * -1;
266 if ($weight_cmp === 0) {
267 return SortArray::sortByKeyString($a, $b, 'name');
273 * Creates a graph of config entity dependencies.
275 * @param array $entities_to_check
276 * The configuration entity full configuration names to determine the
279 * @return \Drupal\Core\Config\Entity\ConfigEntityDependency[]
280 * A graph of config entity dependency objects that are dependent on the
281 * supplied entities to check.
283 protected function createGraphConfigEntityDependencies($entities_to_check) {
284 $dependent_entities = [];
285 $graph = $this->getGraph();
287 foreach ($entities_to_check as $entity) {
288 if (isset($graph[$entity]) && !empty($graph[$entity]['paths'])) {
289 foreach ($graph[$entity]['paths'] as $dependency => $value) {
290 if (isset($this->data[$dependency])) {
291 $dependent_entities[$dependency] = $this->data[$dependency];
296 return $dependent_entities;
300 * Gets the dependency graph of all the config entities.
303 * The dependency graph of all the config entities.
305 protected function getGraph() {
306 if (!isset($this->graph)) {
308 foreach ($this->data as $entity) {
309 $graph_key = $entity->getConfigDependencyName();
310 if (!isset($graph[$graph_key])) {
311 $graph[$graph_key] = [
313 'name' => $graph_key,
316 // Include all dependencies in the graph so that topographical sorting
318 foreach (array_merge($entity->getDependencies('config'), $entity->getDependencies('module'), $entity->getDependencies('theme')) as $dependency) {
319 $graph[$dependency]['edges'][$graph_key] = TRUE;
320 $graph[$dependency]['name'] = $dependency;
323 // Ensure that order of the graph is consistent.
325 $graph_object = new Graph($graph);
326 $this->graph = $graph_object->searchAndSort();
332 * Sets data to calculate dependencies for.
334 * The data is converted into lightweight ConfigEntityDependency objects.
337 * Configuration data keyed by configuration object name. Typically the
338 * output of \Drupal\Core\Config\StorageInterface::loadMultiple().
342 public function setData(array $data) {
343 array_walk($data, function (&$config, $name) {
344 $config = new ConfigEntityDependency($name, $config);
352 * Updates one of the lightweight ConfigEntityDependency objects.
355 * The configuration dependency name.
356 * @param array $dependencies
357 * The configuration dependencies. The array is structured like this:
361 * // An array of configuration entity object names.
363 * 'content' => array(
364 * // An array of content entity configuration dependency names. The default
365 * // format is "ENTITY_TYPE_ID:BUNDLE:UUID".
368 * // An array of module names.
371 * // An array of theme names.
378 public function updateData($name, array $dependencies) {
380 $this->data[$name] = new ConfigEntityDependency($name, ['dependencies' => $dependencies]);