“中国要复兴、富强,必须在开源软件领域起到主导作用,为了国家安全和人类发展,责无旁贷,我们须为此而奋斗”——By:云客
“我的孩子,你是我生命的延续,我终会归于泥土,你将永世长存,让我来助你成长”——开发者。
术语解释:
在开始本篇前,我们需要对以下词汇做一个清晰的解释:
更新Update:
Drupal遵循语义化版本控制(见https://semver.org/lang/zh-CN/),更新是指同一个主版本(major version)下的小版本(minor version)升级,比如从D8.3.2升级到8.9.0,这里的小版本从3升到9,这就是本篇的主要内容
升级Upgrade:
指大版本的升级,比如Drupal 7升级到Drupal 8,或D8升级到D9
迁移Migrating:
指系统在不同主机上的迁移,如从本地计算机迁移到线上服务器、从一个服务器迁移到另一个。
注意:
Drupal核心在Migration组中提供提供了三个模块:migrate、migrate_drupal、migrate_drupal_ui,她们既不用于更新,也不用于系统迁移或系统升级,而是专用于“系统内容”的迁移,系统内容迁移和系统迁移是两回事,比如将帝国cms的数据迁移到Drupal。
系统更新概述:
有过很多年经验的软件开发者会强烈的感觉到软件是有生命的,她的生命和开发者融为一体,离开了开发者即便软件能正常使用,那也只能算是工具,是生命就需要成长,更新即软件成长,生命的成长须一步一步来,没有哪个生命是跳跃式成长的,缔造出一款生命力强劲的软件是每个开发者都渴望的。
这里“成长须一步一步来”是关键,表现在软件上便是每一次更新都是建立在上一次更新基础之上的,换句话说是从上一次更新后的状态开始进行新的更新,如果开发者在设计一次更新时,不以上一次更新后的结果为基础,那么事情将变的非常复杂,甚至是不可能完成的任务,因此在更新这条路上,我们需要为每一次更新建立序号,以表明各次更新的先后关系,在设计每一次更新时,仅需以上一次更新后的状态为基础即可,软件需要携带从初始版本到新版之间设计的每一次更新,这样当用户得到新版软件时,就可以从当前版本按次序依次执行那些更新,这样不管用户的当前版本是什么,都能保证正确的更新到最新版。
具体到Drupal,更新任务分两大部分:
代码更新:
目前需用户自行下载核心代码或模块代码上传到服务器,将来可能采用自动下载
数据库更新:
又可分为模式更新和数据更新,目前数据库更新暂时只有模式更新和数据更新两种类型。
模式schema是指数据库中的数据结构,模式更新即数据结构更新,由模式更新钩子“hook_update_N”进行,钩子函数名中的N部分是一个从小到大变化的数字,每一次更新均实现一次该钩子,将N指定为一个更大的唯一的值,其代表更新的先后关系,每次更新完成后,N值将作为模块当前的模式版本号,所有模块的模式版本号保存在以下位置:
\Drupal::keyValue('system.schema')->getAll()
通过记录模式版本号就能知道已经执行过哪些更新钩子,当前处于更新链路的哪个位置,模式更新钩子详见下文。
数据更新即可能出现的数据本身的更新,由数据更新钩子“hook_post_update_NAME”进行,其中NAME部分是一个变化的字符串标识,同模式更新钩子中的N有类似作用,其字母排序顺序代表更新的先后顺序,已经执行过的数据更新钩子保存在以下位置:
\Drupal::keyValue('post_update')->getAll();
其中的“existing_updates”键保存着已被执行过的数据更新钩子函数名。
数据更新钩子详见下文
某个Drupal大版本的(核心和模块)代码中需要携带该大版本实现过的全部更新钩子,这样才能保证用户系统不论处于该大版本的哪一个小版本都能进行正常更新,这些更新钩子通常在大版本升级时才从代码中移除,移除也不是直接删除那么简单,详见下文。
执行更新:
更新可以有几种方式:Composer、drush(已不赞成)、手动更新,本节以手动更新为例。
不论是核心还是模块更新,系统更新的流程均如下:
一、开启维护模式(不是必须的,但建议开启),下载并替换旧的代码。
二、如果更新发布页有特别说明,则按说明进行额外处理,如修改“.htaccess”、配置文件等。
三、访问站点根目录的系统更新脚本(update.php)进行数据库更新。
通常通过以上三步就完成了一次系统更新,如果出现异常就需要查看日志进行人工排查处理(强烈推荐更新前备份,以便发生异常时安全的恢复数据)。
访问更新脚本必须满足以下两个条件之一:
1、在站点配置文件中,$settings['update_free_access']项的值为true,这代表任何人可以执行更新,默认为false,当更新结束后应该改回false。
2、当前登录账户具备管理软件更新的权限(权限名:Administer software updates),该权限由系统模块提供;如果是公司团队那么可以建立一个更新账号并分配该权限,如果是个人站长,由于维护账号具备一切权限,通常直接登录维护账号执行更新
更新脚本update.php和index.php大体类似,所不同的是继承微调了DrupalKernel核心,最终将执行以下数据库更新控制器:
\Drupal\system\Controller\DbUpdateController::handle
该控制器会和用户交互执行以下步骤:
检查安装需求 (完成)
概览 (活跃)
审查更新
运行更新
审查日志
这些步骤详见下文,所有的更新钩子均在批处理流程中执行。
注:系统提供了一个更新路由,如下:
路由名:system.db_update
路径:/update.php/info
该路由仅用来产生URL,当被访问时,系统入口并不是“index.php”,而是更新脚本“update.php”
在详解更新逻辑前我们先了解下几个需要用到的服务。
数据更新注册服务:
服务id:update.post_update_registry
类:Drupal\Core\Update\UpdateRegistry
该服务用于数据更新钩子的相关操作,如记录已执行的数据更新钩子,得到尚未执行的数据更新钩子等,在类设计上还可以用于其他类型的更新钩子操作。
由服务工厂“update.post_update_registry_factory”实例化,各方法解释如下:
public function getRemovedPostUpdates($module)
获取模块被移除的数据更新钩子函数名,即调用其“removed_post_updates”钩子(详见数据更新钩子一节),传递一个模块机器名,如果该模块实现了钩子:“{$module}_removed_post_updates”,那么调用钩子并返回钩子的返回值,如无实现则返回空数组,该钩子用于指明不再被使用的“post_update”钩子(即数据更新钩子),其返回一个数组:键名是该模块已不再使用的“post_update”更新钩子函数名,键值为不再使用此更新钩子的第一个模块稳定版本的版本号,没有时返回空数组;一旦被声明移除,就需要从代码中移除。
protected function getAvailableUpdateFunctions()
从用户定义的函数空间中,取得某类型(默认为数据更新post_update)有效的更新钩子(函数),有效是指提供更新函数的模块已开启,且函数名满足以下格式:
“moduleName_updateType_funName”
其中moduleName为模块机器名,updateType为更新类型,funName为更新标识;
如果是“post_update”类型的更新(数据更新),返回值会包含已执行的数据更新钩子,另外会检查该钩子对应的移除声明钩子“removed_post_updates”,如果已被声明为移除状态但还出现在代码中,那么将抛出错误。
取得的更新函数以字母序排序(sort函数),这意味着函数名代表了更新函数的执行顺序
public function getPendingUpdateFunctions()
找出所有模块尚未执行的(挂起的)数据更新钩子,返回一个由更新钩子函数名构成的数组,已经排除了被执行过的数据更新钩子,以及hook_removed_post_updates()钩子指定的已失效的数据更新钩子,结果经过sort函数排序
public function getPendingUpdateInformation()
得到所有模块挂起的数据更新钩子信息,该方法返回一个多维数组,第一级键名为模块名,第二级键名有两个:
pending:
代表该模块挂起的更新,其值为数组,键名为数据更新钩子函数名的尾名(post_update_之后的部分,即NAME部分),键值为描述,来自钩子函数的注释文档块,已经去掉了换行和注释符号,注意:并未将其包装成翻译对象
start:
指示将要执行的第一个更新钩子函数,值为数据更新钩子中的NAME部分,如无该项表示没有更新会执行
public function registerInvokedUpdates(array $function_names)
保存已经执行的数据更新钩子函数,以指示不被再次执行;传递已执行的数据更新钩子函数名构成的数组,她们将和以前保存的信息一起合并储存在以下键值储存中:
\Drupal::keyValue('post_update')->get('existing_updates', [])
一旦保存(注册)将不会再被执行;该方法返回$this以便链式调用。
public function getModuleUpdateFunctions($module_name)
得到某模块的全部数据更新钩子函数,包含已执行过的,传递模块机器名,返回函数名构成的数组
protected function scanExtensionsAndLoadUpdateFiles()
扫描并加载所有已启用模块和安装配置扩展的数据更新钩子,即include_once存放她们的php文件;
该类的设计不仅用于数据更新钩子,也可用于其他类型的更新,数据更新仅是更新类型中的一种,更新类型在类属性“updateType”中指定,默认为“post_update”(数据更新钩子)
public function filterOutInvokedUpdatesByModule($module)
从已执行数据更新钩子注册记录中移除某个模块所有已执行的数据更新钩子,这用于模块卸载时,传递模块机器名,无返回值。
注意:
模块在安装阶段,会将其所有数据更新钩子注册成为已执行,这里的所有是指不管是已执行或未执行,同时包含移除钩子的移除声明。在模块卸载时,将从注册数据中删除该模块的所有已注册“数据更新钩子”。
裸页面渲染器:
服务id:bare_html_page_renderer
类:Drupal\Core\Render\BareHtmlPageRenderer
用于裸页面(bare HTML page)的渲染,裸页面指仅包含核心内容(主内容)的页面,不包含页头、页尾、菜单等周边块,常见的裸页面如下:
安装页面: install.php
更新页面:update.php
授权页面:authorize.php
维护模式:maintenance mode
异常提示页: exception handlers
裸页面渲染器仅一个方法:
public function renderBarePage(array $content, $title, $page_theme_property, array $page_additions = []);
参数含义如下:
$content:主内容的渲染数组
$title:页面标题
$page_theme_property:裸页面采用的主题钩子,即“#theme”属性的值
$page_additions:需要显示的额外内容(需要主题支持才能正常显示),默认会附加状态消息,但我们可以设置该参数“$page_additions['#show_messages']”的值为false而取消状态消息
在使用裸页面渲染器时,我们通常需要先提供一个主题钩子,这里以维护页主题钩子为例提供一个裸页面渲染器的使用示例(在控制器中执行):
$main = [
'#markup' => '裸页面示例',
];
$title = '裸页面标题示例';
$response = \Drupal::service('bare_html_page_renderer')->renderBarePage($main, $title, 'maintenance_page');
return $response;
注:系统更新页默认使用的是seven主题,主题钩子来自seven主题覆写
更新过程如下:
- 加载所有已安装模块的“module.install”文件,但更新代码后与系统不兼容的模块除外,更新序号(schema version)不大于-1的模块也不会加载(更新序号在模块被安装时记录,卸载时被删除,执行更新后被更新,如其等于-1,即常量SCHEMA_UNINSTALLED,则表示模块已被卸载),这意味着这样的模块不会得到更新
- 执行更新需求检查(执行需求检查钩子),如果出现需求错误将显示报告页面,如果仅为警告信息那么当网址中有查询参数“continue”且值等效为true时,允许跳过需求检查页面继续执行更新,此阶段称为需求检查阶段,页面内容由元素类型为“status_report”的渲染元素负责呈现
- 需求检查通过后,将显示一个概览提示页(称为info阶段),给用户提示一些重要信息,比如备份建议、更新手册查看链接等
- 显示等待执行的(挂起的)更新函数(称为selection阶段),更新描述来自更新函数的注释文档块(去掉了换行和注释符号,没有采用翻译机制),已经解析过依赖和兼容性,如有异常消息将通过状态消息显示给用户,如果没有挂起的更新,则更新过程结束。
- 开始批处理设置和运行阶段,在执行更新前,不管系统是否处于维护模式,都总是将系统置为维护模式,之后所有更新均在批处理流程中进行
- 执行模式更新钩子,全部模式更新钩子均在批处理回调函数update_do_one($module, $number, $dependency_map, &$context)中执行
- 所有模式更新钩子执行完毕后,继续执行数据更新钩子(如果有的话),在数据更新钩子执行前系统会失效所有缓存,数据更新钩子在批回调update_invoke_post_update($function, &$context)中执行
- 批处理结束后执行\Drupal\system\Controller\DbUpdateController::batchFinished,这将清失效全部缓存,恢复之前的维护模式状态
- 最后一步显示更新处理结果(称为results阶段),如果在该页面出现了异常提示,那么需要通过日志排查错误原因,并手动处理,处理后如果有尚未执行的更新,则需要继续执行
需求检查钩子:
钩子名:requirements
用于模块在安装、更新时进行需求检查,也用于模块正常使用期间进行状态报告,该钩子必须放置在“module_name.install”文件中。
返回值是一个数组,键名是被检查项目的标识名称,该名称随意,但需要保证唯一,建议采用模块名做前缀,键值为一个数组,各键含义如下:
severity:
状态信息或需求不被满足的严重级别,值为四个常量之一:REQUIREMENT_INFO(表示检查仅传递状态信息)、REQUIREMENT_OK(需求成功满足)、REQUIREMENT_WARNING(需求勉强满足,安装会进行但发出警告)、REQUIREMENT_ERROR(错误,需求完全不能满足,终止安装);整个返回值中,只要有一个检查项目为错误级别,那么整体就被认为是错误级别。
title:
需求项的显示名称,一个翻译对象
description:
需求或状态的描述信息,一个翻译对象
value:
当前值(version, time, level等),在参数为安装时,仅应表示版本号,如果不适用则不要去设置该项。
该钩子仅接收一个表示场景的参数,值为install、update、runtime之一,含义如下:
参数为“install”时:
表示调用该钩子时,所属模块即将被安装,用于检查所属模块的安装需求是否能够得到满足,不满足将不被安装,注意:仅“将被安装的模块”的该钩子被调用,调用时模块尚未安装,钩子调用未必要求模块一定被安装,该钩子就是一个列子;钩子内部:不需要检查模块依赖、环境信息,这些应该通过模块的info文件指定,系统会自动检查;模块的该钩子调用可能发生在系统安装时,此时配置文件还不存在,尚没有模块被安装,数据库无法使用,因此需要检查安装时机是系统建立后还是系统正在安装时(安装核心类有静态方法来判断当前是否处于系统安装时,详见本系列系统安装篇);如果需要一个类文件,那么需要直接去加载;
参数为“update” 时:
详见:update_check_requirements(),调用该钩子时,所属模块已被安装,update.php正在运行,用于执行更新前检查更新需求是否能被满足
参数为“runtime” 时:
调用该钩子时,所属模块已被安装,属于运行时检查,用于进行状态报告,不局限于请求检查,比如维护任务、安全更新、站点迁移后的检查等,在(/admin/reports/status)状态报告页可见其返回的信息。
模式schema更新:
钩子函数签名为:hook_update_N(&$sandbox)
用于在模块小版本间执行模式schema更新,如数据库结构调整、类型变化等(数据更新有单独的钩子,见下文),其实更适合的钩子名应为“hook_schema_update_N”,但由于历史原因并没有这样做,钩子函数应放置在模块根目录的module.install文件中,其中“N”是一个数字,称为更新序号或模式号,同一个模块的更新钩子是按更新序号从低到高依次执行的,模式号“N”由以下三部分组成:
第一部分:占1位或2位,代表核心兼容性,比如8为Drupal 8,10为Drupal10,该约定必须遵守
第二部分:占1位,代表模块的主版本号,该约定是可选的,可以采用0-9的任一数字,但必须要有,建议采用模块主版本号,通常核心模块采用drupal的次版本号,假设为Drupal8.3,那么该位就是3
第三部分:占2位,代表从01开始的从小到大的更新序号,最多容纳99次更新
示例如:
node_update_8001():表示Drupal 8.0.x 中节点模块的第一个更新函数
module_update_8101():模块版本 8.x-1.x 的第一个更新函数
module_update_8208():模块版本 8.x-2.x的第八个更新函数
模式号N被视为数字,以数字方式进行比较,每次更新设置的模式号必须由小到大(但不要求连续),系统在执行更新时也从小到大执行,在逻辑上每一次更新均以上一个更新函数的处理结果为基础。
在模块新安装时系统会在“\Drupal::keyValue('system.schema')”中记录模块的最大模式号,之后每次更新完成时会设置新的模式号,记录值便是模块当前的模式号(schema version),其指明了模块最后一个被执行的模式更新钩子,系统以此方式追踪更新,已执行过的模式更新钩子不再被执行;在卸载模块时会删除记录的模式号。
最小模式号应大于常量:\Drupal::CORE_MINIMUM_SCHEMA_VERSION(默认为8000,不能等于),在模块生命周期中一旦丢失被记录的模式号,则按新安装对待,这可能引起严重问题,且修复工作困难。
为避免PHP超时,更新均在批处理流程中进行,但模式更新钩子并不直接作为批处理回调,真正作为批处理回调的是以下函数(模式更新钩子在该函数中执行):
update_do_one($module, $number, $dependency_map, &$context)
参数含义如下:
$module:模块机器名
$number:模式号,即模式更新钩子中的N部分
$dependency_map:模式更新钩子之间可能有先后执行的需求,即有依赖关系(详见下文的模式更新依赖钩子),该参数指明本模式更新钩子依赖的其他模式更新钩子,是一个由模式更新钩子函数名构成的数组,已经过依赖排序,第一个元素是依赖链的根节点,即所有钩子都会依赖的那个模式更新钩子
$context:为批处理上下文参数
在模式更新钩子函数中,参数$sandbox并不被传递完整的批处理上下文参数$context,而是$context['sandbox'],钩子内部必须以引用接收,如果钩子在一个请求中无法完成任务,需要多请求多次执行,那么可以设置参数项$sandbox ['#finished'],这将被系统传递给批处理上下文参数的$context['finished'],值为0-1之间的浮点数,表示进度,详见批处理API。
当模式更新钩子执行成功时,应返回一个字符串(或翻译对象),这将在更新脚本运行完成后显示给用户,如果没有需要显示的,也可返回NULL或不返回;返回后系统会记录新的模式号。
当模式更新钩子执行遇到异常时,应抛出以下异常:
Drupal\Core\Utility\UpdateException
如:
use Drupal\Core\Utility\UpdateException;
throw new UpdateException('Description of what went wrong');
系统收到异常后会记录日志,并将本模式更新钩子视为执行不成功,不会记录新的模式号,一旦被视作不成功,那么后续依赖本模式更新钩子的所有其他模式更新钩子都不会被执行,连不相干的数据更新钩子也不会被执行。
在运行更新脚本(update.php)时,全部模块所有挂起的模式更新钩子均会被列在审查更新页面,其注释文档块会被系统提取出来当做更新的描述(已去掉换行和注释符号),因此为了更好的用户体验,我们在实现该钩子时建议通过注释文档块注明其工作内容,但须注意这里并不采用翻译机制。
有些资料将模式更新钩子称为模块的更新函数,这是因为其无法通过模块处理器派发,所以从狭义上讲她并不是钩子,但从广义上讲本系列也不否认其是钩子
模式更新钩子的删除(更新断点):
前文已经讲到了新发布的(核心和模块)代码需要携带实现过的全部更新钩子,这样才能保证用户系统不论处于哪一个版本都能依次进行更新,但这产生了一个新的问题:随着更新次数越来越多,新版携带的更新钩子函数将越来越多,文件将越来越大,这可能达到夸张的地步,它们对新版的发布形成了拖累,怎么解决呢?
Drupal的思路是设置“更新断点”(注:官方尚未使用更新断点这个词汇,这里为了描述方便由云客提出),即当更新钩子达到一定量后删除一次,删除后的第一个版本称为断点版,其形成一个更新断点,被删除的模式更新钩子中最大的那一个的N值即为断点号,断点版及其之后发布的更新版不携带断点号及之前的模式更新钩子,如果用户系统的模式号小于断点号(非小于等于),那么无法直接更新到最新版,因为缺失更新钩子,需要先更新到包含断点号对应的更新钩子的任一版本,然后再更新到最新版;在整个更新路径中,断点可以有多个,在开发者角度来看,每一次设置断点都意味着删除了之前的更新钩子,从用户角度看,如果在当前版本后续出现了多个断点,那么在更新时不能够一次性更新到最新版,而需要下载后续发布的多个版本(每个断点号需要一个版本),从低到高逐个进行更新。
那么如何设置断点呢?Drupal使用以下断点设置钩子:
hook_update_last_removed()
该钩子应放在mymodule.install中,没有参数,返回值即为断点号,是被删除的最大那个模式更新钩子的N值,比如该钩子返回“8500”,即表示在本版软件中,模式号小于或等于8500的钩子已经被删除了,仅包含大于8500的更新钩子,如果用户想升级到本版软件,那么需要先升级到携带8500号钩子的某个版本才行(由于新版本不一定会实现模式更新钩子,因此这样的版本可能不止一个,更新到任意一个均可),通常Drupal会在主版本更新时设置断点。
断点的检查在system_requirements($phase)钩子中进行($phase为update),如果用户系统模式号小于断点号,则无法执行更新,必须大于或等于。
钩子函数签名:hook_update_dependencies()
派发位置:update_retrieve_dependencies(),放置在mymodule.install文件中
同一个模块声明的模式更新钩子(hook_update_N(&$sandbox) )是按N(更新序号或模式号)从小到大执行的,那不同模块声明的模式更新钩子谁先执行呢?模块间对此可能有先后需求,比如一个模块声明的模式更新钩子必须在另一个模块声明的某序号的模式更新钩子之后运行,换句话说模块间声明的模式更新钩子可能有依赖关系,因此系统派发该钩子来收集这种依赖关系,其决定着模块间模式更新钩子的执行顺序,返回值是一个多维数组,类似如下:
$dependencies['mymodule'][8001] = [
'another_module' => 8003,
];
该示例指示mymodule模块提供的:
mymodule_update_8001()
需要在another_module模块提供的:
another_module_update_8003()
之后运行,如果mymodule_update_8001()依赖其他多个模块提供的更新钩子,只需要在值数组中添加多个声明即可,如果依赖于同一个模块提供的多个模式更新钩子,只需声明N值最高的那个即可,因为相同模块的模式更新钩子总是按N值从小到大的顺序执行。
更新声明也可以反过来声明,如下:
$dependencies[' another_module'][8003] = [
' mymodule' => 8001,
];
这指示mymodule_update_8001()需要在another_module_update_8003()之前执行,但这样的声明很罕见,因为如果后者已经被执行,那么该声明将被忽略。
数据更新钩子:
钩子函数签名为:hook_post_update_NAME(&$sandbox)
和模式更新钩子(hook_update_N())不同的是该钩子专门用于更新数据,放置在模块根目录的以下文件中:
“MODULE.post_update.php”
其中NAME可以是任意有效的机器名,称为数据更新标识符,模块内该钩子按NAME的字母序执行(通过PHP函数sort排序),因此如果有强制顺序要求,那么应该采用合适的NAME,建议采用数字前缀或完全采用数字;模块间数据更新钩子的执行顺序由模块机器名决定,这一点和模式更新钩子不同,不存在依赖机制,没有对应的依赖收集钩子。
每个数据更新钩子只会被系统执行一次,执行过的数据更新钩子函数名被保存在以下位置:
\Drupal::keyValue('post_update')->get('existing_updates', [])
数据更新钩子也是在批处理流程中执行,但不直接作为批处理回调,而是被以下批处理回调执行:
update_invoke_post_update($function, &$context)
参数含义如下:
$function:要被调用执行的数据更新钩子函数名
$context:批处理上下文参数
和模式更新钩子一样,在数据更新钩子函数中,参数$sandbox并不被传递完整的批处理上下文参数$context,而是$context['sandbox'],钩子内部必须以引用接收,如果钩子在一个请求中无法完成任务,需要多请求多次执行,那么可以设置参数项$sandbox ['#finished'],这将被系统传递给批处理上下文参数的$context['finished'],值为0-1之间的浮点数,表示进度,详见批处理API。
系统在全部模式更新钩子函数成功执行完后才开始执行数据更新钩子,如任一模式更新钩子发生错误(抛出异常)则不会被执行,开始执行前系统会失效全部缓存。
当数据更新钩子执行成功时,应返回一个字符串(或翻译对象),这将在更新脚本运行完成后显示给用户,如果不需要显示,也可返回NULL或不返回;执行成功时系统会将其函数名记录起来以避免再次执行,记录位置见前文。
当数据更新钩子执行遇到异常时,应抛出以下异常:
Drupal\Core\Utility\UpdateException
如:
use Drupal\Core\Utility\UpdateException;
throw new UpdateException('Description of what went wrong');
系统收到异常后会记录日志,并将本数据更新钩子视为执行不成功,一旦被视作不成功,那么后续的数据更新钩子将不会被执行,且不会将其记录为已执行,管理员应查看日志并手动排查,然后再次执行更新。
数据更新钩子的移除:
前文讲到系统可以通过“更新断点”来移除早期的模式更新钩子,与之对应的,数据更新钩子也可以移除,但不是通过“更新断点”,数据更新钩子的移除没有断点概念,取而代之的是模块可实现以下钩子:
hook_removed_post_updates()
该钩子放在模块根目录的MODULE.post_update.php文件中,用于指明从第一发布版本开始历次被移除的数据更新钩子
返回一个数组,键名是该模块已从代码中删除的数据更新钩子函数名(包含从第一个版本开始的全部),键值为第一个删除了该数据更新钩子的模块版本号(用于提示没有执行过这些钩子的用户需要先下载哪个版本进行更新)。
如果没有删除过或没有过数据更新钩子,那么该钩子可以返回空数组或不实现;一旦有删除过,那么就必须在该钩子中声明(包含从首版开始的所有删除),系统以此追踪是否会有数据更新钩子被漏执行,逻辑上讲被删除的数据更新钩子理应是曾经被执行过的,其会存在于以下记录中:
\Drupal::keyValue('post_update')->get('existing_updates', [])
如果记录中没有,那么说明没有被执行过,但又被移除的话将导致漏执行,当系统以此推断将可能出现漏执行时,则不允许继续执行更新,该判断工作在以下钩子中进行:
system_requirements($phase)
参数$phase为update,即更新脚本的需求检查阶段
注:从代码中删除的数据更新钩子必须被申明在hook_removed_post_updates()钩子中,反过来,一旦申明就必须删除,否则系统将抛出异常。
更新update模块:
核心提供了“update”模块,用来为维护人员提供更新辅助,如自动检查新版本、通知用户有更新、下载等;该模块属于辅助性质,让维护更加方便,本篇仅面向开发者介绍更新原理,因此关于该模块不做过多介绍。
补充:
1、更新相关官网文档:
更新用户向导:
https://www.drupal.org/docs/updating-drupal
更新概述:
https://www.drupal.org/docs/updating-drupal/overview-of-options
更新api文档:
https://www.drupal.org/docs/drupal-apis/update-api
模式更新钩子文档:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Extension%21module.api.php/function/hook_update_N/9.0.x
2、只要有更新被执行(准确的说是更新流程运行到批处理阶段),那么系统将失效全部缓存,这在访问量巨大的站点需要注意缓存雪崩问题
3、如果更新能运行到批处理阶段,须注意:由于批处理前会自动将系统设置成维护模式,如果批处理过程由于异常中断,那么管理员需要检查是否上线站点
反馈互动