ConfigDependencyManager.php

Same filename in other branches
  1. 9 core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php
  2. 10 core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php
  3. 11.x core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php

Namespace

Drupal\Core\Config\Entity

File

core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php

View source
<?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 and is removed from 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 and is removed from 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;
    }

}

Classes

Title Deprecated Summary
ConfigDependencyManager Provides a class to discover configuration entity dependencies.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.