123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- <?php
- namespace Drupal\Core\Config\Entity;
- use Drupal\Component\Graph\Graph;
- use Drupal\Component\Utility\SortArray;
- /**
- * Provides a class to discover configuration entity dependencies.
- *
- * Configuration entities can depend on modules, themes and other configuration
- * entities. The dependency system is used during configuration installation,
- * uninstallation, and synchronization to ensure that configuration entities are
- * handled in the correct order. For example, node types are created before
- * their fields, and both are created before the view display configuration.
- *
- * The configuration dependency value is structured like this:
- * @code
- * array(
- * 'config' => array(
- * // An array of configuration entity object names. Recalculated on save.
- * ),
- * 'content' => array(
- * // An array of content entity configuration dependency names. The default
- * // format is "ENTITY_TYPE_ID:BUNDLE:UUID". Recalculated on save.
- * ),
- * 'module' => array(
- * // An array of module names. Recalculated on save.
- * ),
- * 'theme' => array(
- * // An array of theme names. Recalculated on save.
- * ),
- * 'enforced' => array(
- * // An array of configuration dependencies that the config entity is
- * // ensured to have regardless of the details of the configuration. These
- * // dependencies are not recalculated on save.
- * 'config' => array(),
- * 'content' => array(),
- * 'module' => array(),
- * 'theme' => array(),
- * ),
- * );
- * @endcode
- *
- * Configuration entity dependencies are recalculated on save based on the
- * current values of the configuration. For example, a filter format will depend
- * on the modules that provide the filter plugins it configures. The filter
- * format can be reconfigured to use a different filter plugin provided by
- * another module. If this occurs, the dependencies will be recalculated on save
- * and the old module will be removed from the list of dependencies and replaced
- * with the new one.
- *
- * Configuration entity classes usually extend
- * \Drupal\Core\Config\Entity\ConfigEntityBase. The base class provides a
- * generic implementation of the calculateDependencies() method that can
- * discover dependencies due to plugins, and third party settings. If the
- * configuration entity has dependencies that cannot be discovered by the base
- * class's implementation, then it needs to implement
- * \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() to
- * calculate the dependencies. In this method, use
- * \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency() to add
- * dependencies. Implementations should call the base class implementation to
- * inherit the generic functionality.
- *
- * Classes for configurable plugins are a special case. They can either declare
- * their configuration dependencies using the calculateDependencies() method
- * described in the paragraph above, or if they have only static dependencies,
- * these can be declared using the 'config_dependencies' annotation key.
- *
- * If an extension author wants a configuration entity to depend on something
- * that is not calculable then they can add these dependencies to the enforced
- * dependencies key. For example, the Forum module provides the forum node type
- * and in order for it to be deleted when the forum module is uninstalled it has
- * an enforced dependency on the module. The dependency on the Forum module can
- * not be calculated since there is nothing inherent in the state of the node
- * type configuration entity that depends on functionality provided by the Forum
- * module.
- *
- * Once declared properly, dependencies are saved to the configuration entity's
- * configuration object so that they can be checked without the module that
- * provides the configuration entity class being installed. This is important
- * for configuration synchronization, which needs to be able to validate
- * configuration in the sync directory before the synchronization has occurred.
- * Also, if you have a configuration entity object and you want to get the
- * current dependencies (without recalculation), you can use
- * \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies().
- *
- * When uninstalling a module or a theme, configuration entities that are
- * dependent will also be removed. This default behavior can lead to undesirable
- * side effects, such as a node view mode being entirely removed when the module
- * defining a field or formatter it uses is uninstalled. To prevent this,
- * configuration entity classes can implement
- * \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval(),
- * which allows the entity class to remove dependencies instead of being deleted
- * themselves. Implementations should save the entity if dependencies have been
- * successfully removed, in order to register the newly cleaned-out dependency
- * list. So, for example, the node view mode configuration entity class
- * should implement this method to remove references to formatters if the plugin
- * that supplies them depends on a module that is being uninstalled.
- *
- * If a configuration entity is provided as default configuration by an
- * extension (module, theme, or profile), the extension has to depend on any
- * modules or themes that the configuration depends on. For example, if a view
- * configuration entity is provided by an installation profile and the view will
- * not work without a certain module, the profile must declare a dependency on
- * this module in its info.yml file. If you do not want your extension to always
- * depend on a particular module that one of its default configuration entities
- * depends on, you can use a sub-module: move the configuration entity to the
- * sub-module instead of including it in the main extension, and declare the
- * module dependency in the sub-module only.
- *
- * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies()
- * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::getDependencies()
- * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval()
- * @see \Drupal\Core\Config\Entity\ConfigEntityBase::addDependency()
- * @see \Drupal\Core\Config\ConfigInstallerInterface::installDefaultConfig()
- * @see \Drupal\Core\Config\ConfigManagerInterface::uninstall()
- * @see \Drupal\Core\Config\Entity\ConfigEntityDependency
- * @see \Drupal\Core\Entity\EntityInterface::getConfigDependencyName()
- * @see \Drupal\Core\Plugin\PluginDependencyTrait
- */
- class ConfigDependencyManager {
- /**
- * The config entity data.
- *
- * @var \Drupal\Core\Config\Entity\ConfigEntityDependency[]
- */
- protected $data = [];
- /**
- * The directed acyclic graph.
- *
- * @var array
- */
- protected $graph;
- /**
- * Gets dependencies.
- *
- * @param string $type
- * The type of dependency being checked. Either 'module', 'theme', 'config'
- * or 'content'.
- * @param string $name
- * The specific name to check. If $type equals 'module' or 'theme' then it
- * should be a module name or theme name. In the case of entity it should be
- * the full configuration object name.
- *
- * @return \Drupal\Core\Config\Entity\ConfigEntityDependency[]
- * An array of config entity dependency objects that are dependent.
- */
- public function getDependentEntities($type, $name) {
- $dependent_entities = [];
- $entities_to_check = [];
- if ($type == 'config') {
- $entities_to_check[] = $name;
- }
- else {
- if ($type == 'module' || $type == 'theme' || $type == 'content') {
- $dependent_entities = array_filter($this->data, function (ConfigEntityDependency $entity) use ($type, $name) {
- return $entity->hasDependency($type, $name);
- });
- }
- // If checking content, module, or theme dependencies, discover which
- // entities are dependent on the entities that have a direct dependency.
- foreach ($dependent_entities as $entity) {
- $entities_to_check[] = $entity->getConfigDependencyName();
- }
- }
- $dependencies = array_merge($this->createGraphConfigEntityDependencies($entities_to_check), $dependent_entities);
- // Sort dependencies in the reverse order of the graph. So the least
- // dependent is at the top. For example, this ensures that fields are
- // always after field storages. This is because field storages need to be
- // created before a field.
- $graph = $this->getGraph();
- $sorts = $this->prepareMultisort($graph, ['weight', 'name']);
- array_multisort($sorts['weight'], SORT_DESC, SORT_NUMERIC, $sorts['name'], SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $graph);
- return array_replace(array_intersect_key($graph, $dependencies), $dependencies);
- }
- /**
- * Extracts data from the graph for use in array_multisort().
- *
- * @param array $graph
- * The graph to extract data from.
- * @param array $keys
- * The keys whose values to extract.
- *
- * @return
- * An array keyed by the $keys passed in. The values are arrays keyed by the
- * row from the graph and the value is the corresponding value for the key
- * from the graph.
- */
- protected function prepareMultisort($graph, $keys) {
- $return = array_fill_keys($keys, []);
- foreach ($graph as $graph_key => $graph_row) {
- foreach ($keys as $key) {
- $return[$key][$graph_key] = $graph_row[$key];
- }
- }
- return $return;
- }
- /**
- * Sorts the dependencies in order of most dependent last.
- *
- * @return array
- * The list of entities in order of most dependent last, otherwise
- * alphabetical.
- */
- public function sortAll() {
- $graph = $this->getGraph();
- // Sort by weight and alphabetically. The most dependent entities
- // are last and entities with the same weight are alphabetically ordered.
- $sorts = $this->prepareMultisort($graph, ['weight', 'name']);
- array_multisort($sorts['weight'], SORT_ASC, SORT_NUMERIC, $sorts['name'], SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $graph);
- // Use array_intersect_key() to exclude modules and themes from the list.
- return array_keys(array_intersect_key($graph, $this->data));
- }
- /**
- * Sorts the dependency graph by weight and alphabetically.
- *
- * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
- * \Drupal\Core\Config\Entity\ConfigDependencyManager::prepareMultisort() and
- * array_multisort() instead.
- *
- * @param array $a
- * First item for comparison. The compared items should be associative
- * arrays that include a 'weight' and a 'name' key.
- * @param array $b
- * Second item for comparison.
- *
- * @return int
- * The comparison result for uasort().
- */
- protected static function sortGraphByWeight(array $a, array $b) {
- $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight');
- if ($weight_cmp === 0) {
- return SortArray::sortByKeyString($a, $b, 'name');
- }
- return $weight_cmp;
- }
- /**
- * Sorts the dependency graph by reverse weight and alphabetically.
- *
- * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
- * \Drupal\Core\Config\Entity\ConfigDependencyManager::prepareMultisort() and
- * array_multisort() instead.
- *
- * @param array $a
- * First item for comparison. The compared items should be associative
- * arrays that include a 'weight' and a 'name' key.
- * @param array $b
- * Second item for comparison.
- *
- * @return int
- * The comparison result for uasort().
- */
- public static function sortGraph(array $a, array $b) {
- $weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight') * -1;
- if ($weight_cmp === 0) {
- return SortArray::sortByKeyString($a, $b, 'name');
- }
- return $weight_cmp;
- }
- /**
- * Creates a graph of config entity dependencies.
- *
- * @param array $entities_to_check
- * The configuration entity full configuration names to determine the
- * dependencies for.
- *
- * @return \Drupal\Core\Config\Entity\ConfigEntityDependency[]
- * A graph of config entity dependency objects that are dependent on the
- * supplied entities to check.
- */
- protected function createGraphConfigEntityDependencies($entities_to_check) {
- $dependent_entities = [];
- $graph = $this->getGraph();
- foreach ($entities_to_check as $entity) {
- if (isset($graph[$entity]) && !empty($graph[$entity]['paths'])) {
- foreach ($graph[$entity]['paths'] as $dependency => $value) {
- if (isset($this->data[$dependency])) {
- $dependent_entities[$dependency] = $this->data[$dependency];
- }
- }
- }
- }
- return $dependent_entities;
- }
- /**
- * Gets the dependency graph of all the config entities.
- *
- * @return array
- * The dependency graph of all the config entities.
- */
- protected function getGraph() {
- if (!isset($this->graph)) {
- $graph = [];
- foreach ($this->data as $entity) {
- $graph_key = $entity->getConfigDependencyName();
- if (!isset($graph[$graph_key])) {
- $graph[$graph_key] = [
- 'edges' => [],
- 'name' => $graph_key,
- ];
- }
- // Include all dependencies in the graph so that topographical sorting
- // works.
- foreach (array_merge($entity->getDependencies('config'), $entity->getDependencies('module'), $entity->getDependencies('theme')) as $dependency) {
- $graph[$dependency]['edges'][$graph_key] = TRUE;
- $graph[$dependency]['name'] = $dependency;
- }
- }
- // Ensure that order of the graph is consistent.
- krsort($graph);
- $graph_object = new Graph($graph);
- $this->graph = $graph_object->searchAndSort();
- }
- return $this->graph;
- }
- /**
- * Sets data to calculate dependencies for.
- *
- * The data is converted into lightweight ConfigEntityDependency objects.
- *
- * @param array $data
- * Configuration data keyed by configuration object name. Typically the
- * output of \Drupal\Core\Config\StorageInterface::loadMultiple().
- *
- * @return $this
- */
- public function setData(array $data) {
- array_walk($data, function (&$config, $name) {
- $config = new ConfigEntityDependency($name, $config);
- });
- $this->data = $data;
- $this->graph = NULL;
- return $this;
- }
- /**
- * Updates one of the lightweight ConfigEntityDependency objects.
- *
- * @param $name
- * The configuration dependency name.
- * @param array $dependencies
- * The configuration dependencies. The array is structured like this:
- * @code
- * array(
- * 'config' => array(
- * // An array of configuration entity object names.
- * ),
- * 'content' => array(
- * // An array of content entity configuration dependency names. The default
- * // format is "ENTITY_TYPE_ID:BUNDLE:UUID".
- * ),
- * 'module' => array(
- * // An array of module names.
- * ),
- * 'theme' => array(
- * // An array of theme names.
- * ),
- * );
- * @endcode
- *
- * @return $this
- */
- public function updateData($name, array $dependencies) {
- $this->graph = NULL;
- $this->data[$name] = new ConfigEntityDependency($name, ['dependencies' => $dependencies]);
- return $this;
- }
- }
|