Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / core / lib / Drupal / Core / Config / Entity / ConfigDependencyManager.php
1 <?php
2
3 namespace Drupal\Core\Config\Entity;
4
5 use Drupal\Component\Graph\Graph;
6 use Drupal\Component\Utility\SortArray;
7
8 /**
9  * Provides a class to discover configuration entity dependencies.
10  *
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.
16  *
17  * The configuration dependency value is structured like this:
18  * @code
19  * array(
20  *   'config' => array(
21  *     // An array of configuration entity object names. Recalculated on save.
22  *   ),
23  *   'content' => array(
24  *     // An array of content entity configuration dependency names. The default
25  *     // format is "ENTITY_TYPE_ID:BUNDLE:UUID". Recalculated on save.
26  *   ),
27  *   'module' => array(
28  *     // An array of module names. Recalculated on save.
29  *   ),
30  *   'theme' => array(
31  *     // An array of theme names. Recalculated on save.
32  *   ),
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(),
40  *     'theme' => array(),
41  *   ),
42  * );
43  * @endcode
44  *
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
51  * with the new one.
52  *
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.
64  *
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.
69  *
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
77  * module.
78  *
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().
87  *
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.
100  *
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.
111  *
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
121  */
122 class ConfigDependencyManager {
123
124   /**
125    * The config entity data.
126    *
127    * @var \Drupal\Core\Config\Entity\ConfigEntityDependency[]
128    */
129   protected $data = [];
130
131   /**
132    * The directed acyclic graph.
133    *
134    * @var array
135    */
136   protected $graph;
137
138   /**
139    * Gets dependencies.
140    *
141    * @param string $type
142    *   The type of dependency being checked. Either 'module', 'theme', 'config'
143    *   or 'content'.
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.
148    *
149    * @return \Drupal\Core\Config\Entity\ConfigEntityDependency[]
150    *   An array of config entity dependency objects that are dependent.
151    */
152   public function getDependentEntities($type, $name) {
153     $dependent_entities = [];
154
155     $entities_to_check = [];
156     if ($type == 'config') {
157       $entities_to_check[] = $name;
158     }
159     else {
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);
163         });
164       }
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();
169       }
170     }
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);
180   }
181
182   /**
183    * Extracts data from the graph for use in array_multisort().
184    *
185    * @param array $graph
186    *   The graph to extract data from.
187    * @param array $keys
188    *   The keys whose values to extract.
189    *
190    * @return
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
193    *   from the graph.
194    */
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];
200       }
201     }
202     return $return;
203   }
204
205   /**
206    * Sorts the dependencies in order of most dependent last.
207    *
208    * @return array
209    *   The list of entities in order of most dependent last, otherwise
210    *   alphabetical.
211    */
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));
220   }
221
222   /**
223    * Sorts the dependency graph by weight and alphabetically.
224    *
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.
228    *
229    * @param array $a
230    *   First item for comparison. The compared items should be associative
231    *   arrays that include a 'weight' and a 'name' key.
232    * @param array $b
233    *   Second item for comparison.
234    *
235    * @return int
236    *   The comparison result for uasort().
237    */
238   protected static function sortGraphByWeight(array $a, array $b) {
239     $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight');
240
241     if ($weight_cmp === 0) {
242       return SortArray::sortByKeyString($a, $b, 'name');
243     }
244     return $weight_cmp;
245   }
246
247   /**
248    * Sorts the dependency graph by reverse weight and alphabetically.
249    *
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.
253    *
254    * @param array $a
255    *   First item for comparison. The compared items should be associative
256    *   arrays that include a 'weight' and a 'name' key.
257    * @param array $b
258    *   Second item for comparison.
259    *
260    * @return int
261    *   The comparison result for uasort().
262    */
263   public static function sortGraph(array $a, array $b) {
264     $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight') * -1;
265
266     if ($weight_cmp === 0) {
267       return SortArray::sortByKeyString($a, $b, 'name');
268     }
269     return $weight_cmp;
270   }
271
272   /**
273    * Creates a graph of config entity dependencies.
274    *
275    * @param array $entities_to_check
276    *   The configuration entity full configuration names to determine the
277    *   dependencies for.
278    *
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.
282    */
283   protected function createGraphConfigEntityDependencies($entities_to_check) {
284     $dependent_entities = [];
285     $graph = $this->getGraph();
286
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];
292           }
293         }
294       }
295     }
296     return $dependent_entities;
297   }
298
299   /**
300    * Gets the dependency graph of all the config entities.
301    *
302    * @return array
303    *   The dependency graph of all the config entities.
304    */
305   protected function getGraph() {
306     if (!isset($this->graph)) {
307       $graph = [];
308       foreach ($this->data as $entity) {
309         $graph_key = $entity->getConfigDependencyName();
310         if (!isset($graph[$graph_key])) {
311           $graph[$graph_key] = [
312             'edges' => [],
313             'name' => $graph_key,
314           ];
315         }
316         // Include all dependencies in the graph so that topographical sorting
317         // works.
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;
321         }
322       }
323       // Ensure that order of the graph is consistent.
324       krsort($graph);
325       $graph_object = new Graph($graph);
326       $this->graph = $graph_object->searchAndSort();
327     }
328     return $this->graph;
329   }
330
331   /**
332    * Sets data to calculate dependencies for.
333    *
334    * The data is converted into lightweight ConfigEntityDependency objects.
335    *
336    * @param array $data
337    *   Configuration data keyed by configuration object name. Typically the
338    *   output of \Drupal\Core\Config\StorageInterface::loadMultiple().
339    *
340    * @return $this
341    */
342   public function setData(array $data) {
343     array_walk($data, function (&$config, $name) {
344       $config = new ConfigEntityDependency($name, $config);
345     });
346     $this->data = $data;
347     $this->graph = NULL;
348     return $this;
349   }
350
351   /**
352    * Updates one of the lightweight ConfigEntityDependency objects.
353    *
354    * @param $name
355    *   The configuration dependency name.
356    * @param array $dependencies
357    *   The configuration dependencies. The array is structured like this:
358    *   @code
359    *   array(
360    *     'config' => array(
361    *       // An array of configuration entity object names.
362    *     ),
363    *     'content' => array(
364    *       // An array of content entity configuration dependency names. The default
365    *       // format is "ENTITY_TYPE_ID:BUNDLE:UUID".
366    *     ),
367    *     'module' => array(
368    *       // An array of module names.
369    *     ),
370    *     'theme' => array(
371    *       // An array of theme names.
372    *     ),
373    *   );
374    *   @endcode
375    *
376    * @return $this
377    */
378   public function updateData($name, array $dependencies) {
379     $this->graph = NULL;
380     $this->data[$name] = new ConfigEntityDependency($name, ['dependencies' => $dependencies]);
381     return $this;
382   }
383
384 }