152. Drupal系统初始安装逻辑
本篇讲解Drupal系统安装原理(即系统初始安装,非模块安装),以及如何制作发行版,在阅读本篇前建议你先阅读本系列的以下已发布内容(但不是必须的):
《配置同步(导入、导出)》
《模块安装与卸载过程》
《接口翻译导入导出与删除》
概述:
如果你是一位有多年经验的开发老手,那么或多或少会接触到一些CMS系统,通常它们的安装程序是一套独立的程序,数据库的安装则是提前准备好的SQL文件,而Drupal则完全不一样,所以请暂时忘记那些CMS的安装方式,以便准备好理解新事物,Drupal的安装方式更现代、更高级,是未来的发展方向。
安装配置扩展(installation profiles):
系统的安装完全由安装配置扩展控制,简称“profile”,该扩展和模块、主题是Drupal世界最主要的三大扩展类型(此外还有模板引擎扩展),profile几乎和模块一样,实际上在安装后系统将profiles当做模块来对待,她具备模块拥有的所有性质和能力,安装后她像被启用的模块一样参与系统运行,如提供各类钩子、菜单、路由等,和模块不同的是profile具备和安装相关的额外功能,用来控制系统如何安装,在安装后提供配置覆写等
Drupal发行版(Drupal distribution):
也称为分发版,即建立在Drupal上针对某种用途或领域制作的系统安装版本,安装后开箱即用,比如一个企业官网、一个电子商城系统等,其打包好了所需的全部功能,成为独立发行的Drupal版本;制作发行版是通过profile(安装配置扩展)来实现的,下载的发行版安装包中一定包含了实现该发行版的profile扩展,但反过来,存在profile却不一定就是在建立发行版,一个profile扩展只代表了一种安装方式而已,比如官方下载的安装包中就存在三个默认的profile扩展:
standard(标准安装)、minimal(最小化安装)、demo_umami(建立一个演示站点)
在安装时用户选择一个profile安装即可,但如果通过profile将安装包声明成了发行版(声明方式见下文),那么会跳过选择直接安装,前文已讲到安装完全由profile扩展控制,比如安装过程要执行的工作、安装界面等,因此发行版有高度自定义性,可以做到在浏览器界面上完全看不出是由drupal制作的
安装方式:
从被安装系统的初始状态来说,有两种安装方式:
一、选择某个profile安装一个全新的站点
二、将现有站点的配置导出,通过配置安装一个和现有站点一样的实例
从程序角度来说,也有两种安装方式:
一、交互式安装,通过浏览器安装就属于该方式,安装程序会一步一步的和用户交互,在多个请求中逐步完成系统安装,本篇着重以该方式进行讲解
二、非交互式安装,通过命令行或程序调用方式的安装就属于此,这种方式下,需要提前将安装所用的参数一次性完整的传递给安装程序
安装原理:
传统上我们熟悉的安装过程是将安装所需任务线性排开,从前向后执行,Drupal的安装要更抽象一些,抽象往往意味着更灵活更强大,可以将Drupal的安装系统看做是一个状态系统,在安装期间的每一个请求中,她会首先侦测当前处于什么状态,再以此状态决定要做什么事情,比如侦测到系统缺乏安装语言信息,就会首先给出安装语言选择表单,如果侦测到没有配置文件,那么就给出数据库配置表单,可以说所有工作均是围绕当前时间点的状态展开的,因此系统以变量$install_state来保存当前的状态信息,该变量贯穿整个安装系统,非常重要。安装无非就是完成一件一件的工作,每一件工作称为一个任务,在Drupal安装系统中,任务有三种类型:
每个安装请求都会运行的任务:如准备运行环境;
依据当前状态断定不运行的任务:如呈现数据库连接信息收集表单,在已经有配置文件时,就无须执行;
运行过就不再运行的任务:如安装主题,已安装自然不用再安装,这是最多任务的类型;
同一个任务在不同系统状态下,可能类型不同,任务到底是哪一种类型,保存在任务的运行旗标中(见下文)。在每一个安装请求中,系统都会初始化一个任务列表,系统以当前状态判断在本请求中执行或不执行哪些任务,从而一步步走完安装流程;profile也正是通过控制这个初始化的任务列表来控制系统安装的。
程序上,在每个安装请求中,确定当前状态后安装系统都会启动一个核心,核心初始化的容器包含了核心提供的所有服务,这样在核心启动后,在任意地方都可以通过\Drupal::service()方法获取到特定功能,从而能调用核心各种现存代码去完成任务,在初期的那些安装请求中,由于还不存在数据库,系统依据状态信息知道这一点后,会将依赖于数据库的那些服务替换为内存存储或做特殊处理;在此我们需要明白:核心的启动不依赖于数据库,也不依赖于任何模块,包括核心模块,安装初期的核心是零模块的,没有模块参与不影响核心启动。
启动安装:
在将安装包放入服务器根目录后,开始安装可以直接访问站点首页,在系统开始执行后,发现没有配置数据库,那么会重定向到安装脚本,这在以下代码中执行:
\Drupal\Core\DrupalKernel::handle
安装脚本路径为(也可以直接访问该脚本启动安装):
/core/install.php
前文已经讲到安装有两种方式:
交互式:
通常就是浏览器,通过多个页面请求和用户交互式的一步一步完成
非交互式:
或称为一次性安装,这通常是命令行安装,或其他系统调用安装,一次性提供安装所需信息,安装在一个请求中完成,没有交互输出
不管是交互式还是一次性安装,均从一个入口函数完成:
install_drupal($class_loader, $settings = [], callable $callback = NULL)
参数含义如下:
$class_loader:类加载器
$settings:用于一次性安装时传递所需安装信息,详见install_state_defaults()函数,交互式安装时不传递
$callback:一个回调,用于在一次性安装时更新进度
在交互式安装方式下,当每一个请求到来时,总是先从当前安装状态确定安装到了哪一个步骤(或称任务task),然后将控制权传给任务回调。
通常前端显示的安装步骤(安装任务task)如下(有许多任务是后端静默执行的,并不包含在这里):
Choose language (active):选择语言
Choose profile:选择安装配置扩展
Verify requirements:需求检查
Set up database:填数据库信息
Install site:执行安装
Configure site:配置站点
各步骤对应的URL如下:
选择语言:/core/install.php
选择安装:/core/install.php?rewrite=ok&langcode=en
需求检查:/core/install.php?rewrite=ok&langcode=en&profile=standard
填数据库:/core/install.php?rewrite=ok&langcode=en&profile=standard&continue=1
执行安装:/core/install.php?rewrite=ok&langcode=en&profile=standard&continue=1&id=1&op=start
配置站点:/core/install.php?rewrite=ok&langcode=en&profile=standard&continue=1
系统安装状态数组:
前文已讲到整个安装过程是围绕当前系统的状态信息进行的,状态信息被储存在一个数组中,该数组是一个全局变量,变量名为$install_state,初始化值来自以下函数:
install_state_defaults()
安装状态数组各键含义如下:
$install_state['site_path']
储存站点路径,一个相对于根目录的相对路径,通常为“sites/default”
$install_state['active_task']
当前正在执行的任务,默认为NULL
$install_state['completed_task']
安装期间,在前一个安装请求中完成的最后一个任务的任务名,默认为NULL,这被储存在状态系统中,代码获取方式:\Drupal::state()->get('install_task');
$install_state['config']
一个数组值,储存安装期间所用到的部分配置,键名为配置对象名,键值为经过解码的配置文件内容
$install_state['config_install_path']
当从配置安装时,配置目录的路径,详见install_load_profile函数,默认为NULL
$install_state['config_verified']
布尔值,指示配置同步目录是否已经存在
$install_state['database_verified']
布尔值,指示是否已经具备有效的数据库连接,有效是指能通过该连接在其中储存数据,如果为true意味着站点配置文件已建立,且数据库信息已经被收集了
$install_state['settings_verified']
布尔值,指示是否已经建立了有效的站点配置文件,即“settings.php”,有效是指不但文件存在,且里面有设置数据库连接信息和哈希盐值
$install_state['base_system_verified']
布尔值,默认为false,指示基础系统是否已经安装,即系统模块是否已经安装,依靠判断会话数据表的存在性来确定
$install_state['download_translation']
布尔值,默认为false,指示是否需要从翻译服务器下载翻译文件,当非英语语言被指定,又不存在对应的翻译文件时,就需要下载,实际上系统并未使用到该项,而是直接进行了判断
$install_state['forms']
在非交互式安装中,用来传递表单提交的值,是一个数组,键名为表单id,键值为要提交给表单的值,这个值会直接调用$form_state->setValues($install_state['forms'][$install_form_id]);传递给系统,然后进行表单的程序方式提交,如果验证出现错误,安装将抛出异常
$install_state['installation_finished']
布尔值,默认为FALSE,指示安装是否完成,TRUE意味着所有任务执行完成,系统被完全安装
$install_state['interactive']
布尔值,默认为true,指示安装是否处于交互模式,即安装是通过浏览器进行的(多请求交互执行)
$install_state['parameters']
储存URL中的查询参数,来自$request->query->all();
$install_state['parameters_changed']
布尔值,默认为false,指示url参数是否已经改变,当有改变时,在交互式安装情况下,当前请求需要停止,安装进程需要重定向跳转到新的请求上
$install_state['profile_info']
安装配置扩展的info文件信息,已经经过默认合并处理,详见:install_profile_info函数
$install_state['profiles']
数组,储存全部安装配置扩展的扩展对象,键名为扩展名,键值为:\Drupal\Core\Extension\Extension
$install_state['theme']
可选存在,字符串值,安装期间所采用主题扩展的机器名,默认为seven
$install_state['server_pattern']
值为用于下载翻译文件的服务器URL,可以自定义,默认为官方翻译服务器:
“http://ftp.drupal.org/files/translations/%core/%project/%project-%versi…”
里面以百分号开始的为占位符,系统会用strtr函数替换成相应的值:
'%project' => 'drupal',
'%version' => $version,//drupal版本号
'%core' => 'all',
'%language' => $langcode, //语言代码
$install_state['stop_page_request']
布尔值,默认为false,任务可以设置该项为true去强制终止当前请求(即使没有返回值),使用该项是很罕见的,比如去打印一个json输出,而不是主题化输出,批处理期间及是如此,但系统在批处理时会自动设置该项,不必人为设置,其他情况如需终止则需要自行设置
$install_state['task_not_complete']
布尔值,默认为false,如果任务将该项设置为TRUE,则表示任务没有完成,需要再次执行,通常批处理和表单提交会用到,但这两个情况系统会自动设置该项,任务本身不需要处理她,其他情况则需要任务自行设置
$install_state['tasks_performed']
储存当前请求中已经被执行的安装任务,值为由任务名构成的一个索引数组
$install_state['translations']
一个数组值,默认为空数组,储存系统翻译目录(默认为sites\default\files\translations)中已存翻译文件的uri,键名为语言代码,键值为uri,默认追加了英语作为第一个数组元素,但英语的uri为空,该选项的赋值详见选择语言任务,即函数install_select_language(&$install_state),选择语言任务是首个将执行的任务,且每次请求都会运行,因此该项总会被初始化
安装相关重要函数说明:
install_begin_request($class_loader, &$install_state)
在安装过程中的每一个请求开始执行时,均会运行该函数,她的作用是侦测系统当前的状态,从而确定状态数组的值,并为后续执行准备运行环境,即启动核心并创建容器。在数据库可用前,涉及到数据库存储的服务(如缓存、状态等),会被替换成采用内存存储或空储存,替换的服务详见以下服务提供器:
Drupal\Core\Installer\InstallerServiceProvider
此外,该函数还进行以下工作:
在数据库可用前,会给翻译服务添加文件型翻译器,以便从翻译目录读取翻译数据并显示;
如果能够唯一确定安装配置扩展、语言则确定下来,这样后续将不会让用户选择;
将系统模块和安装配置扩展注入到模块处理器中(不论是否已安装),并加载她们的主扩展文件;
确定安装使用的主题扩展;
确定上一个请求最后完成的任务;
install_run_tasks(&$install_state, callable $callback = NULL)
循环执行任务,直到有以下条件满足时才停止:
1、全部任务执行完成
2、在交互式安装中,任务有需要发送给浏览器的输出信息,即任务回调返回了渲染数组
3、在交互式安装中,URL参数发生改变,这表示需要重定向
4、在交互式安装中,$install_state['stop_page_request']被设置为真,比如批处理请求
install_tasks($install_state)
初始化任务列表,关于任务定义详见本文任务定义一节,全部安装任务来自核心以及被选中的配置扩展profile,该函数返回所有的任务,而不管其是否已经执行,但在任务的运行旗标中给出了执行方式的信息(详见任务定义中的run选项]),返回数组,键名为任务机器名,键值为对应的任务定义数组;注意返回的安装任务会基于$install_state信息而变化
install_tasks_to_perform($install_state)
从install_tasks函数取得系统中所有的任务,过滤并返回当前需要真正执行的任务列表,如果任务满足以下三种条件之一,那么将被去除:
1、运行旗标$task['run']为INSTALL_TASK_SKIP的任务被去除
2、在本次请求中已执行的任务被去除
3、如果在上一个请求结束时设置了最后被运行的任务(其任务名储存在\Drupal::state()->get('install_task');中),那么她和她之前的任务均会被去除(这意味着在整个安装流程中已执行过的任务不会再重复执行),但任务旗标$task['run']为INSTALL_TASK_RUN_IF_REACHED的任务除外(因为具备这样旗标的任务在每个安装请求中都需要运行,但也只运行一次)
install_run_task($task, &$install_state)
运行单个任务,根据任务类型进入不同的执行流程,逻辑较简单,请参考本系列批处理及表单相关主题
安装任务定义及任务回调开发:
任务定义来自安装系统(见install_tasks($install_state)函数)以及profile扩展的任务钩子,以下以任务钩子的形式对其进行详细说明:
任务钩子:
/**
* 必须放置在所用安装配置扩展的install文件中
* 即安装配置扩展根目录的$profile_name.install文件中
*
* @param $install_state
*/
function hook_install_tasks(&$install_state) {
$tasks = [];
//键名表示任务名
$tasks['yunke_task_one'] = [//以下所有项都是可选的
//一个字符串值,表示任务的类型,有三种可能的值:
//normal:表明是一个常规任务,任务回调做处理并可选的返回主题化输出,这是默认值
//batch:表明任务回调将返回批处理定义(batch_set()的参数),这种情况下,待返回后,安装器将自动运行批处理任务
//form:表明任务涉及表单流程,这种情况下,任务回调不是一个回调,而是一个表单类,安装器将启动表单处理流程
'type' => 'normal',
//值为执行任务的回调,只要是回调均可(可以是数组、class::method、函数名等形式)
//如果本设置项省略,将采用本任务的键名(比如这里是yunke_task_one)
//如果是需要多个任务采用相同回调,那么就必须设置该项了,根据任务类型不同,回调有所区别
//如果任务类型为批处理batch:
// 则回调仅有一个参数$install_state,如需改变则应以引用接收,但通常无需改变
// 须返回一个批处理定义,或多个批处理定义构成的数组,也可以返回NULL或FALSE
//如果任务类型为normal:
// 则回调仅有一个参数$install_state,通常应以引用接收
// 如果回调改变了$install_state['parameters']则当前请求会结束并重定向到新页面
// 如果回调设置$install_state['task_not_complete']为true,则表示工作未结束,会在本次或后续请求中再次执行
// 如有再次执行的情况,中间数据可以保存到状态系统或会话中
// 如果回调设置$install_state['stop_page_request']为true,则当前请求会结束,这很少见,一般用在批处理中
// 如果回调不返回任何值,也不修改$install_state,则系统继续执行下一个任务
// 如果有消息需要显示给用户,可以以渲染数组方式返回,此时当前请求会停止,如果同时设置了重定向那么重定向优先
//如果任务类型为表单form:
// 此时该项值应为表单类的类名而不是回调,表单类和普通表单类无异;$install_state通过表单状态对象以引用方式传递
// 得到方法为:$formState->getBuildInfo()['args'][0],得到的是全局变量$install_state的引用,所以可以修改
// 也可以在表单方法中使用“global $install_state;”取得
// 在表单中不能通过表单方式设置重定向,而应以$install_state['parameters']方式设置
'function' => 'yunke',
//在安装页面(默认在左侧边栏)显示给用户的人类可读的任务名,不设置将不显示,默认值为NULL
'display_name' => t('Choose language'),
//一个布尔值,控制任务是否显示给用户,比如设置了display_name项,但在某些条件下又不需要显示,就需要用到该项了
//默认值视display_name项而定,为:!empty($task['display_name'])
//详见函数:install_tasks_to_display($install_state)
'display' => TRUE,
//值为一个常量,表示任务以哪种方式运行,有三种可能的值:
//INSTALL_TASK_RUN_IF_NOT_COMPLETED:默认值,表明任务将运行一次
//INSTALL_TASK_SKIP:指示在当前请求中,任务将不运行,当某条件满足时可以设置该值跳过任务
//INSTALL_TASK_RUN_IF_REACHED:指示任务在每个安装请求期间到达时都会运行,这很少用,主要用她执行启动相关的任务
'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED,
];
}
任务修改钩子:
安装配置扩展profile可以通过任务修改钩子修改定义好的全部任务,包括安装系统提供的任务,这使得profile可以完全控制系统安装,任务修改钩子如下:
/**
* 用于修改完整的任务列表
* 应放置在安装配置扩展的主文件中
* 即安装配置扩展根目录的$profile_name.profile文件中
*
* @param $tasks 完整任务列表
* @param $install_state 安装状态数组
*/
function hook_install_tasks_alter(&$tasks, $install_state) {
//对任务列表数组做修改
}
安装配置扩展profile的info文件结构:
前文已经提到profile扩展的制作和模块类似,她们拥有相同的文件、目录结构,为了进一步讲解安装,这里先介绍一下profile的info文件结构。
这和模块一样,有name、description、dependencies等,但有也区别,以下是一个完整示例和解释:
name: Yunke
#type:值为profile
type: profile
# 描述
description: 'Install profile example.'
# install值为需要安装的模块的机器名构成的数组,如果是多语言网站会自动合并locale模块,且会自动合并核心必须模块,即required为true的模块
# 如果这里设置了,但系统中不存在模块文件,那么在安装的需求验证阶段会报错,无法继续安装
install:
- node
- history
- block
- breakpoint
- ckeditor
- color
- config
- comment
- contextual
- contact
- menu_link_content
- datetime
- block_content
- quickedit
- editor
- help
- image
- menu_ui
- options
- path
- page_cache
- dynamic_page_cache
- big_pipe
- taxonomy
- dblog
- search
- shortcut
- toolbar
- field_ui
- file
- rdf
- views
- views_ui
- tour
- automated_cron
#需要安装的主题扩展,可选,默认为['stark']
themes:
- bartik
- seven
# 通过该键将本profile声明为发行版
distribution:
#发行版的名称,默认为“Drupal”,如果设置了该项,那么安装过程将自动选择本profile,而跳过让用户选择
- name: 'yunke'
# 可选,值为数组 在安装过程中提供一些覆写
- install:
# 安装过程中可以使用自定义的主题,在该处提供自定义主题的扩展机器名,默认为seven
- theme: 'seven'
# 一个url路径,指示安装完成后跳转到哪里
- finish_url: '/'
# 可选,指定站点的安装语言,如果设置了则安装过程中会跳过语言选择步骤
- langcode: 'en'
# 可选,用于指定配置同步目录,默认值为:如果目录:$profile_path . '/config/sync'存在,将设置为该值,否则为NULL
config_install_path: null
version: '9.0.2'
project: 'drupal'
datestamp: 1594237482
系统安装流程:
系统默认安装任务列表按顺序如下(详见install_tasks($install_state)函数):
- 选择语言
- 下载翻译,如果不需要下载则跳过,比如语言为英语
- 选择安装配置扩展profile,如果设置了发行版或系统中仅有一个profile,那么跳过
- 加载profile
- 需求验证,执行需求钩子,见下文
- 填写提交数据库连接信息,生成站点哈希值、配置同步目录,如果已存在有效的配置文件则跳过
- 验证数据库已经可用
- 安装基础系统,这包括安装系统、用户模块等,详见下文,已安装则跳过
- 完整启动系统,启动session,此后的任务均是在一个完整的系统中运行了
- 安装profile中指定的模块,包括所依赖的模块,详见下文
- 安装profile中指定的主题
- 安装profile扩展本身,这将直接调用模块安装器,在此任务中会安装全站可选配置
- 导入翻译,不需要则跳过
- 配置站点,显示站点配置表单,以供设置站点名称、维护账户用户名和密码、系统安装时间等,见下
- 如果指定了从配置安装系统(即指定了$install_state['config_install_path']),则以上步骤中会取消下载翻译,以及profile中指定模块、主题和profile本身的安装,取而代之的是配置导入、配置翻译下载
- 执行profile中的install_tasks钩子,取得profile定义的安装任务,并依次执行
- 进行安装完成后的翻译处理
- 执行安装完成的善后工作
以上任务和顺序均可在安装配置扩展的任务修改钩子(install_tasks)中进行调整
在每一个安装请求中都会初始化出以上任务列表,系统会以从前到后的顺序,根据当前请求的系统状态选择性执行列表中的任务,换句话说,状态可以决定任务是否被执行,这就导致任务列表的顺序不一定就是安装执行流程的顺序,任务顺序和执行流程是不一样的概念,比如交互式安装是在多个请求中执行,就导致执行流程可以再次回到任务列表中之前的任务;总而言之,任务顺序和不停变化的状态一起决定着执行流程;用户界面所看到的流程和程序上的流程也不一样。
有些任务会在每个安装请求中都执行(如选择语言,执行不代表会有输出,这由任务回调决定),有些任务可以反复执行多次。
安装任务:
这里我们挑选一些比较重要的任务来看一看:
下载翻译:
任务回调:install_download_translation(&$install_state)
如果用户选择了一个非英语的安装语言,且翻译目录(默认为站点路径下的files/translations)不存在或其中不存在对应的翻译文件,那么系统将确保翻译目录存在(不存在则创建),并启动下载,如果已经存在翻译文件了则什么也不做,重定向当前页面到自己(因为任务已经执行过,所以重定向后不会再次执行),相当于刷新,以便让页面用对应语言显示
如果系统不能自动下载翻译文件,将抛出错误并暂停,此时需要我们人工下载后放入翻译目录中,并刷新安装页面继续安装,这可能在网络故障或服务器繁忙时发生
选择安装配置扩展:
任务回调:install_select_profile(&$install_state)
如果能够自动选择,那么将自动选择,如果不能则显示选择表单给用户选择,表单类如下:
Drupal\Core\Installer\Form\SelectProfileForm
自动选择按以下次序进行:
1、如果只有一个有效的profile,那么就选她
2、在URL参数中指定了一个有效的profile,那么就选她
3、如果profile被声明为分发版(info文件中有distribution键),那么就选她,如果有多个profile均被声明为分发版,那么采用扫描到的第一个
4、如果仅有一个可见的profile,那么就选她,可见是指其info文件中没有设置hidden键,或设置了但值为false
验证系统需求:
任务回调:install_verify_requirements(&$install_state)
验证将执行以下项目:
1、profile 声明要安装的模块(即info文件中install项值,含依赖和必要模块)是否都存在于文件系统中
2、执行profile 以及profile 声明要安装的模块的requirements钩子,钩子参数为“install”,注意:模块的安装可能发生在安装系统期间,也可能在系统安装后的正常使用期,这两种情况下,模块的该钩子执行环境是完全不一样的,如果在系统安装期,则站点配置文件、数据库均还不存在,也没有模块被安装,要判断这个时期可以用静态方法:\Drupal\Core\Installer\InstallerKernel::installationAttempted
3、是否能建立站点的配置文件,站点默认配置文件(settings.php)就是在该任务中建立的,但在其中还尚未加入数据库信息
收集数据库信息(任务名:install_settings_form):
任务回调:属于表单类型的任务,没有回调,表单类:
Drupal\Core\Installer\Form\SiteSettingsForm
Drupal默认支持三种数据库:mysql、pgsql、sqlite,我们可以在以下目录:
根目录/drivers/lib/Drupal/Driver/Database
或模块的/src/Driver/Database目录中放入数据库驱动以扩展更多所支持的类型,详见以下函数:
drupal_get_database_types()
该任务依据数据库类型产生配置表单,提交验证(实测可连接)通过后将连接信息写入配置文件
此外该任务还产生配置文件中的哈希盐值、配置同步目录
关于配置文件写入,这里有一个工具函数:
drupal_rewrite_settings($settings = [], $settings_file = NULL)
该函数很通用,且常用,几乎是CMS系统必备,使用方法示例如下:
require_once DRUPAL_ROOT . '/core/includes/install.inc';
$settings['settings']['name'] = (object) [
'value' => 'yunke', //变量值,可以是多维数组
'required' => TRUE, //是否真实写入,通常应该设置为true
'comment' => 'some comment',//可选,一个关于该变量的注释
];
$var1 = ['a' => 'x', 'b' => ['m', 'n'], 'c' => 3];
$settings['var1'] = (object) [
'value' => $var1,
'required' => TRUE,
'comment' => '多维数组的注释',
];
$settings['var2'] = ['a' => $settings['var1']];//将产生变量$var2['a']
$settings['var3'] = ['x', 'y']; //这种方式无效
$settings_file = DRUPAL_ROOT . '/yunke.php'; //配置文件需要预先创建
drupal_rewrite_settings($settings, $settings_file);
//写入时会尽可能覆写存在的值,否则以追加方式添加变量
安装基本系统:
任务回调:function install_base_system(&$install_state)
安装基本系统,为后续安装打下基础
- 首先创建数据库表 “config”,这是系统创建的第一个数据库表,然后安装核心提供的配置,其中的配置对象“core.extension”将用来记录模块、主题的安装情况,为后续安装提供基础,就是在此时将profile信息保存到其中的
- 安装系统模块,这是被安装的第一个模块,数据库中将创建这些数据库表:“key_value、key_value_expire、sequences、sessions”,这将为状态储存、批处理、会话提供必须的储存能力,模块安装直接调用模块安装器进行,详见本系列模块安装篇
- 重建路由,这将创建锁系统所需的“semaphore”数据表、储存路由所需的“router” 数据表
- 在配置中保存系统所用的默认语言
- 为敏感目录创建“.htaccess”文件,防止被终端直接访问
- 安装用户user模块,这是被安装的第二个模块,将创建用户、角色相关数据库表
笔者语:在国内很多系统的安装,数据库都是由安装程序直接提供SQL,但Drupal完全不一样,其是由相关程序自行完成,全部建立在数据库操作API之上,这带来极大的灵活性和简洁性,且易于维护
安装profile中指定的模块:
任务回调:install_profile_modules(&$install_state)
以批处理方式安装profile中指定的模块,包含其依赖的模块,在安装过程中首先安装核心所必须要的模块,即info文件中required项为true的模块;直接调用模块安装器安装:
\Drupal::service('module_installer')->install([$module], FALSE);
注意:第二个参数为false,这意味着在安装过程中并不会考虑模块间的依赖关系;在整个安装过程中,模块的需求检查钩子会被运行两次,一次是系统安装初期,第二次是在该步骤中,因此注意在钩子中一定不要去检查模块依赖,这些系统会自动检查
配置站点(任务名:install_configure_form):
任务回调:属于表单类型的任务,没有回调,表单类:
Drupal\Core\Installer\Form\SiteConfigureForm
用于在安装后,设置站点基本属性,有:站点名、邮件、维护账户的用户名和密码、国家、时区;在配置文件是可写的情况下还会提醒将其设置为只读
如果选择了自动检查更新,那么会安装更新update模块,如果进一步点选了接受邮件通知,那么会设置更新模块在有更新时去发送邮件
此外,会在状态系统中记录系统安装的时间:
\Drupal::state()->set('install_time', $_SERVER['REQUEST_TIME']);
\Drupal::state()->get('install_time');
安装完成的善后工作:
任务回调:function install_finished(&$install_state)
这是安装过程执行的最后一个任务,处理以下内容:
- 在核心扩展配置对象中,设置profile的权重为1000,这样可以让其定义的修改钩子最后执行(除非人为设置其他模块的权重超过1000),意味着拥有最高优先级
- 重建路由,让所有路由信息完备可用
- 运行一次计划任务,让定义了计划任务的模块在安装后及时执行一次
- 登录维护账户,让用户在安装后可以直接登录系统
- 通过状态消息,显示一个恭喜消息“Congratulations, you installed @drupal!”
完整制作profile扩展和分发版:
前文已讲到分发版就是在profile扩展的info文件中有特殊声明而已,不必多讲;再一次总结,profile的开发制作和模块无异,文件、目录结构都相同,区别如下:
- 分发版自定义的profile扩展放置在系统根目录的profiles文件夹中
- info文件有额外功能,详见前文的注释
- profile提供安装任务钩子(install_tasks),其位于profile 根目录的“$profile .install”文件中,其修改钩子位于主文件中,即“$profile . profile”,模块无法提供该钩子
- profile配置目录提供的配置对所有模块具有覆写功能,但不能覆写活动配置,详见配置安装器的方法:\Drupal\Core\Config\ConfigInstaller::getConfigToCreate,这种覆写不只发生在系统安装期间,在系统安装后的站点运行期间,新安装模块时,依然会覆写;在系统安装期间,模块提供的简单配置的覆写被立即应用,配置实体的覆写更新发生在profile安装时
- 在一个已安装drupal实例上安装模块时,安装默认配置时会一并安装可选配置,但在实例安装过程中并不会这样做,而是全部模块安装结束后再统一安装所有模块的可选配置
- 在核心扩展配置(配置对象名:core.extension)中,profile的权重被设置为1000,这就使得profile提供的修改钩子在普通模块提供的修改钩子之后执行,这意味着具有更高优先级,除非人为设置其他模块的权重大于1000.
- profile扩展本身的安装和模块安装无异,她同样可以提供“hook_install()”钩子,该钩子通常用于执行和整个系统安装相关的功能,这是除安装任务之外profile可以控制系统安装的另一个方法
从配置安装:
假设经过一番努力我们使用Drupal制作了一个活动报名系统,小伙伴们体验后感觉很好很优秀,希望自己也有一个,此时怎么办呢?这就需要profile从已存站点的配置来安装系统。
如希望profile从已存站点的配置来安装系统,则在其根目录中建立配置同步目录,默认为“/config/sync”(如果不是默认值则需要在info文件中指明),并在其中存放从已存站点导出的配置yml文件,注意导出的配置中,如果核心扩展配置文件“core.extension.yml”中指定的profile和制作的profile不同名,那么需要一并修改(共有两处值),否则安装时会导致导入配置验证失败,有些配置文件中可能存在UUID或默认配置哈希,为了避免发行版实例间冲突,需要移除,可在命令行中使用如下命令:
find /path/to/PROFILE_NAME/config/install/ -type f -exec sed -i -e '/^uuid: /d' {} \;
find /path/to/PROFILE_NAME/config/install/ -type f -exec sed -i -e '/_core:/,+1d' {} \;
对于Mac OSX用户请使用以下命令:
find /path/to/PROFILE_NAME/config/install/ -type f -exec sed -i '' '/^uuid: /d' {} \;
find /path/to/PROFILE_NAME/config/install/ -type f -exec sed -i '' '/_core:/{N;d;}' {} \;
同时从源站点复制必须要的模块、主题等到新安装包;如果源站点是多语言的,建议一并提供翻译文件,避免安装时自动下载翻译遇到服务器错误相关问题而导致安装过程中断。
默认内容:
如果发行版需要默认内容,可以下载“default_content”模块,下载地址:
https://www.drupal.org/project/default_content
下载后放入profile根目录下的modules目录中,在“default_content”模块中提供默认内容(详见该扩展介绍),就绪后内容模块的安装方式依据系统安装方式的不同,有以下两种:
系统从配置安装:通过安装任务的方式来安装默认内容模块
系统不从配置安装:在profile扩展的hook_install()钩子中调用模块安装器来安装默认内容模块
可以参见默认安装包中提供的内容演示模块demo_umami
核心自带演示Profile扩展简介:
核心自带了三个profile扩展,位于目录“core/profiles”中(该目录同时也提供了一些在UI中不可见的profile,用于测试目的),标准安装和最小化安装均比较简单,你可以看一看演示profile :“demo_umami”,观察她是如何控制安装,以及如何导入站点内容,本篇内容足以让你理解她的细节,在此不多讲解。
补充:
1、官网相关文档:
创建分发版:https://www.drupal.org/docs/distributions/creating-distributions
下载共享的分发版:https://www.drupal.org/project/project_distribution
2、为了安全起见,安装后记得将站点配置文件设置成只读状态
3、在系统安装以后查询当前安装的Profile方法:$profile = \Drupal::installProfile()
4、如果安装期间网络连接中断,或不小心关闭了浏览器,导致没有完整完成安装怎么办呢?只需要刷新或无查询参数打开安装脚本继续就行了
5、判断当前系统是否正处于安装过程中,请使用以下代码:
\Drupal\Core\Installer\InstallerKernel::installationAttempted()
6、安装期间会扫描模块目录,所有模块均会被扫描,如果有模块的info文件错误,会导致抛出异常,比如在info中同时出现以下两行:
core: 8.x
core_version_requirement: '~8.8 || ^9'
就会抛出不允许有core键的异常