“中国要复兴、富强,必须在开源软件领域起到主导作用,为了国家安全和人类发展,责无旁贷,我们须为此而奋斗”——By:云客
在cms中配置信息非常重要,储存管理员及模块的各种设定,以它指导系统行为等等,在系统中是四大类信息(内容、会话、状态、配置)之一,drupal中配置系统是一个比较大且重要的系统,本系列将分多节进行讲解。
配置信息是可以在两个安装实例之间转移的,比如在开发站点导出配置,然后在生产站点导入,而状态信息是不能转移的,可以重置,重置后状态信息消失,配置信息继续有效,针对配置信息可以导入导出,有此功能就可以支持用版本控制系统管理配置了,drupal以统一方式处理配置信息,配置系统是配置信息的处理中枢。
首先要明白Drupal8的配置系统有两大块内容:
简单配置:提供简单配置操作接口Simple Configuration API,管理如站点名称等简单的配置。
复杂配置:又叫做配置实体,提供Configuration entities API,在drupal世界里通常称为配置实体,它处理复杂配置,比如内容类型、角色等等有大量且相互关联度高的复杂配置情况。
简单配置和配置实体有关联,为其提供了一些基础建设,是最直观的配置系统,先讲述简单配置是怎么实现的。
简单配置Simple Configuration API:
就如她的名字那样,她实现的配置信息是比较简单的,有机程度不是太大,往往是单一的互不相关的一些配置项,每个配置项可以用一个php数组来表示,该数组包含这个配置项的一些信息,配置值只能是标量数据类型或数组,在drupal系统中并不是直接使用这些数组,而是由一个叫做配置对象的oop对象来代理它,并提供额外的许多功能,如加载、保存、删除等等,对简单配置系统的使用就围绕着这个配置对象。
配置YAML文件:
当模块需要定义一些配置项来指定行为时怎么处理呢?这里假设模块名叫做“yunke”,在drupal中配置信息是储存在yml文件中的,需要符合yml语法,一个yml文件代表一个配置项,在程序中也对应着一个配置对象,文件名以模块名为前缀,以点号做分隔,后接配置子名字空间名(由模块指定),最后接配置项id名,它们均以点号分隔,如system.maintenance.yml,表示系统模块的维护配置项,它位于:\core\modules\system\config\install,带子名字空间名的如system.menu.footer.yml,系统以文件名作为配置项名(不包括扩展名),通过配置项名来查找配置项,所以系统中配置文件不可以同名,不同目录不同模块的配置文件名必须全局唯一,如果模块不需要细化配置项以避免产生多个配置文件,往往使用settings作为配置项id,如yunke模块则是yunke.settings.yml,配置文件放置位置有两条规则,如果该配置是必须配置,将配置文件放于模块目录下的\config\install目录中,如果是可选的配置,将其放于\config\optional目录中,可选配置是什么意思呢?如果一个配置项依赖于其他组件,被依赖的组件已安装就启用,没有安装则不启用,这样的配置项称为可选配置项,可选配置需在配置项的dependencies中指明依赖关系,参考论坛模块。读者可查阅核心提供的各模块配置文件是什么样子。
以上讲到的yml配置文件的配置信息都是静态确定的,如果配置值需要根据环境来确定怎么办呢?对于这样的动态配置可以使用钩子函数hook_install()来设置:
function modulename_install() {
//从系统中确定配置值
\Drupal::configFactory()->getEditable('modulename.settings')
->set('default_from_address', \Drupal::config('system.site')->get('mail'))
->save();
}
活动配置active config:
以上介绍的配置文件只提供初始配置值,在drupal中有个叫做活动active配置的概念,意思是正在被站点使用的配置,当模块被启用时,会将上面介绍的储存在yml文件中的配置信息导入活动配置里,此后即便再更改yml配置文件也不会对系统产生影响了,如果需要更改的配置文件生效,则需要卸载模块后重新安装,此外在系统运行中程序对配置的更改保存操作也只是作用于活动配置,并不作用于配置文件(储存的yml文件不会变化),管理界面导出操作也是作用于活动配置,活动配置可以储存在数据库、文件或者其他储存系统里面,默认储存在数据库中,模块开发者应该使用配置系统提供的api来操作它,不应该也不需要关注存储的具体细节。
至于启用模块时配置文件是如何导入活动配置的将在后续主题讲解;定义配置文件除了上文介绍的之外还应该在config/schema中定义配置文件的模式schema,它提供配置文件的元数据,描述了配置文件的结构,包括数据类型、可翻译性等等,参见:\core\modules\user\config,详细讲解见后文。
配置工厂及配置对象:
简单配置API的使用接口是配置工厂,用它返回配置对象供开发者在逻辑中使用,一个配置对象对应于模块提供的一个配置文件,以不带扩展名的文件名作为配置对象名,配置工厂信息如下:
容器id:config.factory
类:Drupal\Core\Config\ConfigFactory
获取方法:\Drupal::configFactory()
配置系统的主要代码位于:\core\lib\Drupal\Core\Config
系统提供了两种类型的配置对象:
可编辑配置对象Config:开发者可以对其修改、清空、保存、删除等编辑操作
不可变配置对象ImmutableConfig:继承自可编辑配置对象,但禁用了编辑能力,是一个只读对象
通常可编辑配置对象用于站点配置导入导出、表单修改等,不可编辑配置对象用于读取当前系统的最终配置值,这个配置值是经过配置覆盖层修改的(见后),这两种配置对象需要分情况使用。
先看看可编辑配置对象,它的继承关系如下:
ConfigBase implements RefinableCacheableDependencyInterface
StorableConfigBase extends ConfigBase
Config extends StorableConfigBase
在ConfigBase中对配置名做了些限制:必须带点号做分隔符,否则提示丢失名字空间、长度不能大于250字符,只能小于或等于,不能包含这些字符:: ? * < > " ' / \此外配置项中键名不能含有点号
从配置工厂取得可编辑配置对象示例:
$config = \Drupal::configFactory()->getEditable('system.performance');
从容器中获取:getContainer()->get('config.factory') ->getEditable(' system.performance ');
从配置对象取得配置值:
Echo $config->get('cache.page.enabled');
get的参数如为空,返回整个配置数组,若数组中无值将返回NULL,配置值必须为标量数据类型或数组。
可编辑配置对象可以修改:
$config->set('cache.page.enabled', 1);
注意:配置项是以数组方式存储在配置对象的属性中,可以使用点号代表数组的嵌套层级关系,这也解释了配置项中键名不能含有点号的原因,关于此特性的实现可以参见本系列的数组操作主题。
$page_cache_data = array('enabled' => 1, 'max_age' => 5);
$config->set('cache.page', $page_cache_data);
$config->save();//如果要将修改的值永久保存需要明确的保存它
清空配置设置:
$config->clear('cache.page.max_age')->save();
也可以链式处理:
\Drupal::service('config.factory')->getEditable('system.performance')->set('cache.page.enabled', 1)->save();
如果要修改整个配置对象,使用:
\Drupal::configFactory()->getEditable(' system.performance ')->setData($arr);
合并配置项数据:
\Drupal::configFactory()->getEditable(' system.performance ')-> merge(array $data_to_merge);
移除整个配置对象:
\Drupal::service('config.factory')->getEditable('system.performance')->delete();
注意当移除整个时不需要用save()
在系统管理界面查看某配置对象的内容(配置类型选择简单配置):
/admin/config/development/configuration/single/export
注意:在管理界面看到的配置项值来源于活动配置,非yml文件
不可编辑配置对象:
类:Drupal\Core\Config\ImmutableConfig
它继承自可编辑配置对象 Drupal\Core\Config\Config,将编辑方法进行了重写,尝试编辑将抛出异常,其他方法完全一样,得到不可编辑配置对象:
\Drupal::config($name)等效于getContainer()->get('config.factory')->get($name);
在配置工厂中,不论是可编辑还是不可编辑配置对象,初始化都是这样的过程:先初始化得到一个没有配置项数据的对象,然后通过该对象的initWithData($data)方法将从配置储存里取回的数组形态的配置项数据注入,如果是不可变配置对象则额外注入配置覆写层,
配置储存:
在配置对象中并未实现配置信息的CRUD功能,该功能实际依赖于配置储存服务:
容器id:config.storage
实现接口:
Drupal\Core\Config\StorageInterface
Drupal\Core\Config\StorageCacheInterface
配置储存服务以数组方式对配置信息进行CRUD操作,通过配置名读取的配置信息是一个数组,它被注入到配置对象中。
为了提高性能config.storage服务以代理模式实现,仅提供了缓存能力,使用服务:cache.config做缓存器,缓存的数据储存在数据库表cache_config中,真正执行CRUD操作的是:
Drupal\Core\Config\DatabaseStorage
它作用在活动配置上,默认情况下活动配置信息储存在数据库表config中,在这里读者可能有一个疑问:代理模式实现的缓存也是储存在数据库中,使用缓存还要多出写缓存的步骤,不是多此一举反倒减慢速度吗?答案是一些情况下确实如此,但如果站点配备了高速缓存,情况就不一样了,可以让缓存系统分离出来,这也体现出drupal为中大型系统而生的特性,小型站点默认情况下确实可以进行许多优化。
当查看数据库表config时有一个collection字段,什么意思呢?系统有一个叫做配置集collection的概念,同一个配置项在不同情况下配置信息可能是不同的,因此针对不同情况设置不同的配置集,典型的应用情况是国际化,同一个配置项,根据不同语言,就有不同配置信息,每种语言就相当于一个配置集。
配置事件:
当配置出现改变时,可能会引起系统行为的改变,因此必须通知相关组件配置已经发生了改变,系统使用事件派发机制实现此功能,配置事件定义在Drupal\Core\Config\ConfigEvents中,如下:
const SAVE = 'config.save';
配置保存事件,当保存配置时派发
const DELETE = 'config.delete';
配置删除事件
const RENAME = 'config.rename';
配置对象重命名事件
const IMPORT_VALIDATE = 'config.importer.validate';
导入配置验证事件,允许模块执行额外的验证操作
const IMPORT = 'config.importer.import';
配置导入事件,当有配置导入时派发
const IMPORT_MISSING_CONTENT = 'config.importer.missing_content';
导入故障事件,导入时发现丢失内容时派发
const COLLECTION_INFO = 'config.collection_info';
配置集事件,添加配置集时派发
需要关注配置信息变化的模块注册一个侦探器服务即可,详见本系列的事件派发主题。
配置覆写:Override覆写对象
在某些情况下,我们需要对活动配置里的值进行改写,但又不进行储存,只是在使用中得到经过改写的值,这就出现了配置覆写功能,配置覆写是在原始的配置值之上加了一个覆写层,模块和全局变量$config可以通过配置对象名在该层提供覆写数据,默认情况下可编辑配置对象是不进行覆写的,在配置工厂里没有对其注入覆写数据,只对只读的不可编辑对象注入了覆写数据,不可编辑配置对象的get方法取得的数据是经过覆写的,但不可编辑配置对象也提供了访问未经覆写数据的方法:getRawData()及getOriginal($key = '', $apply_overrides = TRUE)。
一般在配置表单里使用未经覆写的配置信息,也就是可编辑配置对象提供的信息。
可以在代码中设置全局变量$config进行覆盖,也可以在站点配置文件settings.php中设置该变量,下面是一些列子,如下:
global $config;
$config['system.maintenance']['message'] = 'Sorry, our site is down now.';
echo $message = \Drupal::config('system.maintenance')->get('message'); //得到覆写后的值
不可编辑对象也可以直接获取原始不经过覆写的值:
echo $site_name = \Drupal::config('system.site')->getOriginal('name', FALSE);
//返回活动配置原始值,第二个参数表示不进行覆写,注意该原始值是配置储存中的值,而不是配置对象建立后通过setData方法设置的值
可编辑对象总是返回原始值,不进行覆写操作:
echo $site_name = \Drupal::configFactory()->getEditable('system.site')->get('name');
在代码中通过全局变量$config进行覆写有个值得注意的地方:
global $config;
echo $site_name = \Drupal::config('system.site')->get('name');
$config['system.site']['name'] = '站点名';
echo $site_name = \Drupal::config('system.site')->get('name');
这是不起作用的,如果在试图覆写前调用过一次,配置对象会被静态缓存在配置工厂中,因此覆写无效
也可以直接通过配置储存服务来得到活动配置里面的值,它是未经覆写的,以数组方式返回:
\Drupal:: service(“config.storage”)-> read($name);
也可以通过它来判断某配置项是否存在:
\Drupal:: service(“config.storage”)-> exists($name);
覆写优先级:
在覆写层中提供了两种覆盖方式:模块覆写modules 和全局变量$config覆写,$config可以在站点配置文件settings.php中设置,也可以在运行代码中设置,它的优先级高于模块覆写。
模块定义配置覆写:
如果模块需要设置配置覆写,那么需要定义一个服务,该服务类必须实现接口:
Drupal\Core\Config\ConfigFactoryOverrideInterface
服务标签为:config.factory.override
系统默认提供了语言覆写,示例如下:
language.config_factory_override:
class: Drupal\language\Config\LanguageConfigFactoryOverride
arguments: ['@config.storage', '@event_dispatcher', '@config.typed', '@language.default']
tags:
- { name: config.factory.override, priority: -254 }
可以设置优先级,数值越大优先级越高,针对相同配置值的覆写以优先级高的为准,覆写是一个操作数组合并的过程,具体实现可参见本系列数组操作主题。
配置系统还有很多内容,本节介绍到这里,请见后续章节。
补充说明
一、配置对象中以下属性的区别:
$this->data //配置对象中保存的配置项数据,数组格式
$this->originalData //配置对象中从储存加载的未经过改变的配置项数据,数组格式
方法public function setData(array $data)及public function merge(array $data_to_merge)仅作用于$this->data不作用于$this->originalData
方法public function initWithData(array $data)作用于他们两者
二、配置对象中以下方法的区别:
public function getRawData()
public function getOriginal($key = '', $apply_overrides = TRUE)
见上文$this->data和$this->originalData的区别
三、配置对象中以下方法的区别:
public function setData(array $data) //仅仅设置数据,只作用于$this->data不作用于$this->originalData
public function initWithData(array $data) //用储存的值初始化数据,会导致$isNew改变,说明已经被储存过
三、云客发现以下内容有不合理的地方,在后续流程中如无特殊用途,这将被确定为bug,目前暂不明了:
Bug1:
在配置对象的set方法中(代码位于:Drupal\Core\Config\ConfigBase:: set($key, $value); )
函数调用:NestedArray::setValue($this->data, $parts, $value);
应该设置为NestedArray::setValue($this->data, $parts, $value,true);
假设有这样的配置项:
hello:
name: 'yunke'
进行这样的操作将发生错误:
\Drupal::configFactory()->getEditable('yunke.settings')->set('hello.name.firstname',"chen ")->save();
这可能在某些情况下出现意外,修改后则不会,具体错误类型请见本系列的数组操作。
Bug2:
配置工厂类:Drupal\Core\Config\ ConfigFactory::doLoadMultiple(array $names, $immutable = TRUE)方法里将覆写对象的缓存依赖添加到可编辑配置对象中,这是不合理的,修复方法:
将该调用$this->propagateConfigOverrideCacheability($cache_key, $name);
放入if ($immutable) {…}逻辑中
四、官方参考文档:
官方API:https://api.drupal.org/api/drupal/core%21core.api.php/group/config_api/8.3.x
官方文档:https://www.drupal.org/docs/8/api/configuration-api/configuration-api-overview
反馈互动