5.8. 插件升级

5.8.1. 数据升级

所有的程序发布之后都会面临着一个升级的问题,bzfshop 的插件也一样会需要升级。程序的升级分成 2 部分:

代码升级
你的代码更新了,一般用新的代码覆盖老代码就可以了,代码升级是最容易、最简单的部分
数据升级
你的程序总有使用一些数据吧,现在新程序需要添加新数据了、删除一些不再使用的旧数据,或者对一些旧的数据结构做变化(以前是 txt 格式,现在要改成 json 格式了)。数据升级比代码升级来的复杂,并且更加重要。

所以,以后我们说到 bzfshop 升级了,说的是 代码升级 + 数据升级。你如果只是简单的用新代码把旧代码覆盖了,那很可能你的新代码会运行出错,因为数据还是旧的,你必须同时完成数据升级才能让新代码正确执行。我们把数据升级的讨论暂停一下,但是你心里要记住 "数据升级 比 代码升级 更重要"。

5.8.2. 跨版本升级

最早你的程序会发布 1.0.0 版本,之后是 1.0.1 → 1.1.0 → … → 1.10.1 ,版本不断升级,对应的数据结构也在不断的变化。每次升级你都有类似的数据升级,比如增加了一个字段,修改了2个数据,删除了一个文件 … 等等。恭喜你,现在程序到了 1.10.1 一个很大的进步了。突然你面临了一个问题,你的几个客户问你他们怎么升级到最新的 1.10.1,然后你发现棘手的问题来了,因为他们有人现在还用这最原始的 1.0.0,另外有个用着中间的 1.5.3,另外有人用着 1.9.6,每个人现在的版本都不一样,但是他们都要升级到最新的 1.10.1,你该如何保证每个人都能成功升级呢?

图 5.9. 用户插件升级问题

dev_plugin_user_update.png

从上面的图可以看到,你的程序从 1.0.0 → … → 1.10.1 中间经历了很多版本的变化,而用户并不一定会跟着你升级,于是就会出现不同的用户使用着不同的版本,而他们都希望从自己的版本升级到你最新的版本。代码升级好办,直接用最新的代码覆盖旧代码就可以了,但是 数据升级 如何操作呢? 从 1.0.1 → 1.10.1 的升级 很明显 和 1.5.6 → 1.10.1 的升级是不一样的,中间的数据结构甚至发生过很多次的变化,你的数据升级该如何操作呢?

bzfshop 设计了一整套的升级机制,可以确保用户从之前的任何版本正确升级到你现在的最新版本。其基本原理就是保持一个完整的 "升级序列",用户总是从 "自己当前版本顺序往后升级",一次一次升级直到最新版本结束。

  1. 每次升级你都会写一个升级程序用于做数据升级,比如 Update_1_1_0.php 用于从 1.0.1 → 1.1.0 做数据升级, Update_1_1_1.php 用于从 1.1.0 → 1.1.1 的升级
  2. 版本多了之后,你会有一系列的 Update_x_x_x.php 文件用于不同版本之间的升级,你需要在代码里始终保留这一系列的程序
  3. 当一个用户是 1.5.1 需要升级到 1.10.1 的时候,bzfshop 会找到 Update_1_5_2.php 首先把程序升级到 1.5.2 ,然后继续这个查找过程,一步一步的做升级,直到升级到 1.10.1 为止。只要你这一系列的升级程序都保留着,用户就可以从其中任何一个版本开始 一步一步 升级到最新版

5.8.3. 代码结构

这章节我们给一个 Example3 的升级版例子,在这个例子里面你可以看到我们是怎么把 Example3 升级到最新的 1.5.0 版本的,代码这里 下载

[警告]

由于这个程序是 Example3 的升级版,所以安装的时候你需要把 Example3 插件的代码首先删除,然后用这个代码替换,也就是先做 "代码升级"

图 5.10. 插件Example3_2目录结构

插件Example3_2目录结构

从上面的目录结构可以看到我们多了一个 update 目录,里面是一系列的升级程序。每一个程序都是从一个版本到下一个版本的数据升级,比如 Update_1_0_3.php 就是从 1.0.2 → 1.0.3 的数据升级。

现在到 manage 后台 插件主题 → 插件管理 你能看到 Example3 的升级提示了,如下图:

图 5.11. 插件Example3_2升级

插件Example3_2升级

点击上面的 "升级" 按钮, bzfshop 就会调用插件自身的 update 目录下一系列 Update_x_x_x.php 程序完成升级工作。其中的版本判断,该从哪个版本开始升级之类的工作 bzfshop 会替你完成,你只需要在每次升级的时候提供一个 Update 程序,剩下的事情就交给 bzfshop 去处理好了。

5.8.4. 升级代码

我们拿一个升级代码来做具体的讲解,如下:

Update_1_0_3.php. 

<?php

/**
 * 升级程序例子,演示从一个版本到另一个版本的数据升级
 */

// 定义自己的 namespace ,防止和别的插件冲突
namespace Plugin\Example\Example3 {

    use Core\Plugin\AbstractUpdate;

    class Update_1_0_3 extends AbstractUpdate
    {

        /**
         * 接受从 1.0.2 版本做升级
         */
        protected $sourceVersionAllowed = array('1.0.2');

        /**
         * 把版本升级到 1.0.3
         */
        protected $targetVersion = '1.0.3';

        public function doUpdate($currentVersion)
        {

            // 增加了一个新的参数
            Example3Plugin::saveOptionValue('config_3', '新增加的配置3');

            // 把版本升级到目标版本
            Example3Plugin::saveOptionValue('version', $this->targetVersion);

            return true;
        }
    }
}

// 全局命名空间代码,我们在这里生成一个插件的实例返回给加载程序
namespace {
    // 返回 update instance
    return Plugin\Example\Example3\Update_1_0_3::instance();
}

首先从 AbstractUpdate 派生一个你自己的升级类,protected $sourceVersionAllowed = array('1.0.2'); 这里定义了当前升级程序接受从哪个版本开始的升级。如果当前程序不是 1.0.2 版本,bzfshop 就不会调用这个升级程序做升级。通过这个定义 bzfshop 可以知道当前程序是否能够升级。

public function doUpdate($currentVersion) 是真实的升级工作,bzfshop 会把当前的插件版本传递给你,你在这里做自己的数据升级。注意,升级最后一定要把当前的版本号设置为新的版本(Example3Plugin::saveOptionValue('version', $this→targetVersion);),否则 bzfshop 会不断循环尝试。

升级既然有成功,就一定会有失败的情况,失败的情况下一般就要做数据的回滚操作。AbstractUpdate 一样也定义了一组 回滚 操作,考虑到不要把例子弄得太复杂,我们没有把 回滚 放在这里,你可以自己研究 AbstractUpdate 的代码,里面有所有完整的流程。