Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
28.57% |
2 / 7 |
CRAP | |
73.33% |
33 / 45 |
ConfigDependencyManager | |
0.00% |
0 / 1 |
|
37.50% |
3 / 8 |
31.18 | |
73.33% |
33 / 45 |
getDependentEntities | |
100.00% |
1 / 1 |
6 | |
100.00% |
11 / 11 |
|||
anonymous function | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
sortAll | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
sortGraph | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
createGraphConfigEntityDependencies | |
0.00% |
0 / 1 |
5.07 | |
85.71% |
6 / 7 |
|||
getGraph | |
0.00% |
0 / 1 |
5.01 | |
91.67% |
11 / 12 |
|||
setData | |
100.00% |
1 / 1 |
1 | |
100.00% |
0 / 0 |
|||
updateData | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\Core\Config\Entity\ConfigDependencyManager. | |
*/ | |
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 = array(); | |
/** | |
* 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 = array(); | |
$entities_to_check = array(); | |
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. | |
return array_reverse(array_intersect_key($this->graph, $dependencies)); | |
} | |
/** | |
* 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 reverse weight and alphabetically. The most dependent entities | |
// are last and entities with the same weight are alphabetically ordered. | |
uasort($graph, array($this, 'sortGraph')); | |
return array_keys($graph); | |
} | |
/** | |
* Sorts the dependency graph by reverse weight and alphabetically. | |
* | |
* @param array $a | |
* First item for comparison. The compared items should be associative | |
* arrays that include a 'weight' and a 'component' key. | |
* @param array $b | |
* Second item for comparison. | |
* | |
* @return int | |
* The comparison result for uasort(). | |
*/ | |
public function sortGraph(array $a, array $b) { | |
$weight_cmp = SortArray::sortByKeyInt($a, $b, 'weight') * -1; | |
if ($weight_cmp === 0) { | |
return SortArray::sortByKeyString($a, $b, 'component'); | |
} | |
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 = array(); | |
$graph = $this->getGraph(); | |
foreach ($entities_to_check as $entity) { | |
if (isset($graph[$entity]) && !empty($graph[$entity]['reverse_paths'])){ | |
foreach ($graph[$entity]['reverse_paths'] as $dependency => $value) { | |
$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 = array(); | |
foreach ($this->data as $entity) { | |
$graph_key = $entity->getConfigDependencyName(); | |
$graph[$graph_key]['edges'] = array(); | |
$dependencies = $entity->getDependencies('config'); | |
if (!empty($dependencies)) { | |
foreach ($dependencies as $dependency) { | |
$graph[$graph_key]['edges'][$dependency] = TRUE; | |
} | |
} | |
} | |
$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; | |
} | |
} |