147. 配置的安装与卸载
本文讲述配置的安装与卸载,这发生在模块安装和卸载时,注意这与配置同步不同,配置同步是指在被安装实例的两个副本间同步配置数据,即配置的导入或导入,详见本系列配置同步主题。
默认配置与可选配置:
模块“config/install”目录中的配置称为默认配置,“config/optional”目录中的配置称为可选配置,不管是何配置,其文件名的第一段(第一个点号前的内容)均是某个扩展的机器名,指示该配置属于哪一个扩展,注意:属于某个扩展的配置,并不一定是该扩展提供的,任何一个模块均可在默认配置或可选配置中提供属于其他扩展的配置;换句话说,在扩展的默认配置和可选配置目录里均可包含属于其他模块的配置文件,在扩展安装时:
默认配置:
均会被安装,其是模块运行必须要的配置,如果其依赖得不到满足(比如配置所属的模块没有被安装),那么扩展不能被安装(安装时抛出异常结束安装过程)。
可选配置:
仅在条件满足时才被安装,条件如下:
1、可选的配置尚不存在于活动配置中
2、可选配置须是一个配置实体,简单配置不能作为可选配置
3、可选配置声明的其他依赖能被满足,即“dependencies”根键中申明的依赖能被满足
同时仅安装对当前正在安装的扩展有依赖的可选配置(即:如无当前正在被安装的扩展那么这些可选配置无法工作),这些可选配置来自全站所有启用的扩展和核心,举个例子:已被安装的A模块在其可选配置目录中提供了一个配置对象X,X依赖于模块B,如果模块B没有被安装,那么X将不被安装,一旦模块B被安装,则在安装B时,系统会找出模块A中的这个X,并进行安装。
不管是已安装模块中的可选配置,还是正在被安装的模块中的可选配置,其安装时机均是在模块安装时。
简单配置与配置实体的区别:
区别联系如下:
1、通常配置实体有uuid,而简单配置没有,可参考以下方法:
\Drupal\Core\Entity\EntityStorageBase::create
\Drupal\Core\Config\ConfigManager::getConfigDependencyManager
但有另外,比如简单配置“system.site”就有uuid(默认安装中也仅该简单配置有),所以这是一个潜规则,不是强制规则,我们不能以此去进行判断
2、配置实体必有依赖声明,即存在“dependencies”根键,其值为依赖数组,依赖数组的键名为module、theme、config、content,对应的键值为依赖名构成的数组,在配置安装时不检查content依赖, “dependencies”键在简单配置中是可选的,默认安装中,没有简单配置用到该键;系统仅对配置实体进行依赖处理,简单配置不处理依赖,换句话说仅配置实体可以有依赖,简单配置不应使用依赖。
3、简单配置不能作为可选配置,仅配置实体可以。
4、准确的讲:不满足配置实体的配置对象命名规则的即是简单配置。
判断两者可简单使用以下代码:
\Drupal::service('config.manager')->getEntityTypeIdByName($name)
传递配置对象名,返回值如果等效为true,那么是配置实体,否则为简单配置
查询系统中所有的简单配置可使用以下代码:
$definitions = [];
foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type => $definition) {
if ($definition->entityClassImplements(\Drupal\Core\Config\Entity\ConfigEntityInterface::class)) {
$definitions[$entity_type] = $definition;
}
}
//找出所有配置实体的配置名前缀
$config_prefixes = array_map(function ($definition) {
return $definition->getConfigPrefix() . '.';
}, $definitions);
$names = \Drupal::service('config.storage')->listAll();
$names = array_combine($names, $names);
foreach ($names as $config_name) {
foreach ($config_prefixes as $config_prefix) {
if (strpos($config_name, $config_prefix) === 0) {
unset($names[$config_name]); //去除所有配置实体,剩下的就是简单配置
}
}
}
$configurations = \Drupal::configFactory()->loadMultiple($names);
云客语:关于简单配置和配置实体的硬性界定将可能在未来版本进行规划,极有可能是通过判断是否存在“dependencies”根键来实现,目前仅能通过以上方法判断。
配置安装器:
用于安装扩展的默认和可选配置,并提供配置验证等辅助功能,注意其并不提供配置卸载功能,配置卸载在配置管理器中进行,见下文。
服务id:config.installer
类:Drupal\Core\Config\ConfigInstaller
各方法解释如下:
public function installDefaultConfig($type, $name)
该方法在模块安装时被调用,用于安装扩展的默认配置;同时会调用可选配置安装方法去安装可选配置;如果安装的是安装配置扩展,那么已被安装的简单配置不会被再次安装;安装后会重置配置工厂以保证配置是新的;注意该方法并不安装配置schema,但在扩展是主题或者扩展存在schema配置目录时,会清理schema缓存;注意:安装配置扩展提供的配置将覆写所有模块提供的配置,而不管是默认还是可选配置。
public function installOptionalConfig(StorageInterface $storage = NULL, $dependency = [])
安装可选配置,这些可选配置由传入的储存器提供,参数$dependency是一个依赖表示数组,键名为module、theme或config,键值为配置依赖名,其含义是仅安装有这些依赖的可选配置,没有这些依赖的可选配置即使满足条件也不安装,如果没有传递,那么只要满足条件就都安装。
系统提供了以下储存器来读取所有已启用模块、主题和核心的可选配置目录:
\Drupal\Core\Config\ExtensionInstallStorage
该储存器的介绍详见本系列配置同步主题;可选配置仅在以下条件满足时才被安装:
1、尚未存在于活动配置中
2、是一个配置实体,简单配置不能作为可选配置
3、在module、theme和config方面的依赖能够被满足
在安装时会考虑依赖关系,被依赖的可选配置先安装
protected function getConfigToCreate(StorageInterface $storage, $collection, $prefix = '', array $profile_storages = [])
从一个配置存储器中得到将要安装创建的配置数据,这些配置数据是经过安装配置扩展(profile)提供的配置数据覆写过的
protected function createConfiguration($collection, array $config_to_create)
传入某个集里面的配置去创建,即添加到活动配置中,参数$collection为集名,参数$config_to_create是配置数据数组,键名为配置名,键值为对应的配置内容数据;在创建时会考虑依赖关系,被依赖的配置先创建;如果被创建的配置在活动配置中已经存在了,那么将进行更新;会为默认集中的配置添加默认配置哈希,该哈希能对配置数据起到验证作用;如果是配置实体,那么将在实体层面进行更新或保存,值得注意的是如果实体的不可安装的,那么将无法创建或更新,但也不会引起错误,换句话说被安装的模块在配置目录中携带的不可安装的配置实体类型的配置将不会起作用。
public function installCollectionDefaultConfig($collection)
一次性安装已启用模块的所有默认配置,这用在系统安装时
protected function findPreExistingConfiguration(StorageInterface $storage)
传入一个配置储存器,检查其中是否有配置已经存在于活动配置中,将在所有配置集上进行检查,如果没有存在的,那么将返回空数组,否则返回一个数组,键名为集名,键值为已经存在的配置的配置名构成的数组。
public function checkConfigurationToInstall($type, $name)
传入一个将被安装的扩展,参数为扩展类型和机器名,检查其默认配置(不检查可选配置)中是否有无效配置,如有将抛出异常,如无将返回NULL值,这在模块安装开始阶段调用,如果抛出异常将终止模块安装流程,无效配置是指默认配置出现以下情况:
1、配置中申明了依赖(仅限module、theme、config类型的依赖),但依赖不能被满足(即依赖缺失)
2、在默认配置的所有配置集中,已经有一个或多个存在于活动配置中了
protected function findDefaultConfigWithUnmetDependencies(StorageInterface $storage, array $enabled_extensions, array $profile_storages = [])
传入扩展的默认配置储存器、已开启的所有扩展、安装配置扩展的配置储存器(包含可选配置);找出扩展默认配置中依赖得不到满足的配置,仅在默认集上检查,返回一个有两个元素的数组,两元素均是数组,其键名均是依赖得不到满足的配置的配置名,区别是:
第一个元素:键名为配置名,键值为配置数据
第二个元素:键名为配置名,键值为丢失的依赖(仅包含module、theme、config类型的依赖)
protected function validateDependencies($config_name, array $data, array $enabled_extensions, array $all_config)
验证一个配置的依赖是否能得到满足,返回布尔值,其中该配置所属的扩展必须已安装
protected function getMissingDependencies($config_name, array $data, array $enabled_extensions, array $all_config)
得到配置对象丢失的依赖,仅考虑module、theme、config类型的依赖(不包含内容),返回一个数组,其由依赖名构成,不区分类别。
protected function getEnabledExtensions()
得到系统中全部已经安装的扩展(profile、模块、主题),返回一个由扩展机器名构成的数组
protected function getProfileStorages($installing_name = '')
得到安装配置扩展(profile)的配置储存器,返回一个由两个配置储存器构成的数组,这是因为默认配置和可选配置分别对应一个储存处理器,有了这些配置储存器就可以读取profile提供的默认配置和可选配置了,在模块安装时,profile扩展用这些配置去覆写模块提供的配置,参数指示正在安装的模块,如果就是profile扩展本身那么将返回空数组(自己不需要覆写自己)。
protected function drupalInstallationAttempted()
返回布尔值,指示当前系统是否处于系统安装过程中,系统安装指整个Drupal系统安装,并非指某个模块安装
配置管理器:
服务id:config.manager
类:Drupal\Core\Config\ConfigManager
实现接口:\Drupal\Core\Config\ConfigManagerInterface
用于为配置系统提供辅助功能,即提供如下这些辅助方法:
public function getEntityTypeIdByName($name);
依据配置对象的名字,返回配置实体类型,如果配置对象属于简单配置,那么返回NULL
public function loadConfigEntityByName($name);
依据配置对象的名字,返回配置实体,如果配置对象属于简单配置,那么返回NULL
public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL, $collection = StorageInterface::DEFAULT_COLLECTION);
从两个配置储存中读取同一个配置进行比较,返回差异对象,其用法见:
\Drupal\config\Controller\ConfigController::diff
public function createSnapshot(StorageInterface $source_storage, StorageInterface $snapshot_storage);
为某个配置储存器创建配置快照
public function uninstall($type, $name);
卸载一个给定扩展的配置,参数$type为扩展类型,如module或theme,参数$name为扩展机器名,卸载操作会删除属于该扩展的所有配置,包括简单配置、配置实体、各种配置集,只要配置对象以该扩展机器名为前缀就属于该扩展,如果扩展提供了不属于自己的配置,那么这些配置不会被卸载;如果有提供配置schema那么会同时清理,在卸载时会考虑依赖问题,但仅配置实体才能使用依赖,简单配置的依赖不被处理,所有直接或间接依赖传入扩展的配置实体都被处理,要么删除要么重新保存,这取决于配置实体的onDependencyRemoval方法如何处理(见下文)。
在模块安装器中卸载模块时将调用以下代码:
\Drupal::service('config.manager')->uninstall('module', $module);
public function getConfigDependencyManager()
返回配置依赖管理器,关于此管理器的介绍详见本系列《有向无环图及依赖处理》主题,由该方法可见系统默认把有uuid的配置当做配置实体,也仅配置实体才可以使用依赖
public function findConfigEntityDependents($type, array $names, ConfigDependencyManager $dependency_manager = NULL)
传递某些即将被删除的被依赖项,返回依赖在这些被依赖项上的配置实体(的依赖表示对象),即传递一些依赖图中的根节点,返回依赖节点,在返回数组中叶子节点靠前,即遍历数组时会被先遍历。
bug提示:
当$names数组参数有多个元素时,该方法内部使用array_merge函数合并依赖数组,这将可能破坏依赖顺序,举个例子,假设有两个依赖数组:
$a=['a'=>'1','b'=>2,'c'=>3,'d'=>4];
$b=['x'=>'1','b'=>2,'c'=>3,'d'=>4];
在$a中,a依赖b, b依赖c,c依赖d,在$b中,同样的x依赖b,b依赖c,c依赖d,如果采用array_merge函数合并,那么结果是:
['a'=>'1','b'=>2,'c'=>3,'d'=>4,'x'=>'1'];
显然这是不对的,正确的结果应该是:
['a'=>'1','x'=>'1','b'=>2,'c'=>3,'d'=>4];
或者:
['x'=>'1', 'a'=>'1','b'=>2,'c'=>3,'d'=>4];
该bug已提交给官网
public function findConfigEntityDependentsAsEntities($type, array $names, ConfigDependencyManager $dependency_manager = NULL)
该方法将findConfigEntityDependents方法返回的依赖表示对象转化为配置实体对象
bug提示:
该方法打破了依赖的顺序,需要对返回值继续做如下处理:
$entities=[];
foreach ($entities_to_return as $configEntity){
$entities[$configEntity->getConfigDependencyName()]=$configEntity;
}
$entities_to_return=array_merge(array_intersect_key($dependencies, $entities),$entities);
已经提交官网
public function getConfigEntitiesToChangeOnDependencyRemoval($type, array $names, $dry_run = TRUE)
传递某些即将被删除的被依赖项,返回一个数组,包含依赖在这些被依赖项上的配置实体;
参数含义如下:
$type:被依赖项类型,有module、theme、config、content
$names:一个数组,对应类型下即将被删除的被依赖项的名字(依赖名数组)
$dry_run:布尔值,表明是否在进行“演习”,即在该方法调用后,调用者是否真的会对返回的配置实体执行更新或删除动作,当为false时表明不是在演习,会真的执行更新或删除,当为true时表示在演习,返回的实际上是新克隆的配置实体对象,这样将保证原对象不受修改影响。
返回数组有以下三个键(键值均为和将被删除的依赖相关的配置实体对象):
'update':值为需要更新的实体对象,即需要重新保存
'delete' :值为需要删除的实体对象
'unchanged':值为无需改变的实体对象
该方法在配置实体基类的预删除方法等地方被调用,为什么会有以上三种类型的返回呢?这是因为配置实体可以通过她的onDependencyRemoval方法表明她所依赖的对象被移除后自己应被删除还是更新,当应被更新时,在依赖树中,依赖于本配置实体的其他依赖可能就无需改变了。
protected function callOnDependencyRemoval(ConfigEntityInterface $entity, array $dependent_entities, $type, array $names)
该方法辅助调用配置实体的onDependencyRemoval方法。
仅配置实体有onDependencyRemoval方法,该方法返回布尔值,当返回false时表示依赖如果被移除则该配置实体应被删除,当返回true时,表示不应被删除,但需要重新保存,此时在内部需要依据被删除的依赖进行自我修复调整,在该方法被调用后,系统会依据返回值进行删除或重新保存。
该方法的参数中,config和content类型的依赖已经被转化成了实体对象,键名为依赖名,什么意思呢?举个例子:
假设配置实体的依赖结构如下:
array(
'config' => array('user.role.anonymous', 'user.role.authenticated'),
'content' => array('node:article:f0a189e6-55fb-47fb-8005-5bef81c44d6d'),
'module' => array('node', 'user'),
'theme' => array('seven'),
);
得到的参数将如下:
array(
'config' => array('user.role.anonymous'=>$userRoleEntity1, 'user.role.authenticated'=>$userRoleEntity2),
'content' => array('node:article:f0a189e6-55fb-47fb-8005-5bef81c44d6d'=>$ nodeEntity),
'module' => array('node', 'user'),
'theme' => array('seven'),
);
当然仅传递处于卸载过程中的依赖,不被卸载的依赖不会被传递,在内部进行修复时,只需要对属性做调整即可,无需去保存,也不用调用计算依赖方法,这些在系统流程中会被执行
public function getConfigCollectionInfo()
通过事件派发机制获取配置集信息,默认安装中仅语言提供了配置集信息
public function findMissingContentDependencies()
遍历整个活动配置储存,找出丢失了的类型为“content”的依赖,换句话说:有些配置依赖于某些内容实体,但是内容实体被删除了,该方法就是找出那些已经被删除的内容实体