Swoft2 框架精华教程:数据库 Migration

概述

swoft 框架中,devtool开发组件集成了数据库的初始化、迁移所需的相关功能。

可以根据数据库的 ER 图,将对应的实体,分别构建相应的 Migration 类,类可能涉及到对应数据表的创建,表结构的修改,索引的修改,甚至初始化数据的插入等等(分别对应数据库的 ddl,dml)。

  • DML(Data Manipulation Language)

    数据库的基本操作语言,对应 insert/delete/update/select。可以通过数据库事务,对操作进行提交或者回滚。

  • DDL(Data Definition Language)

    数据库定义、表结构涉及相关语句:create、alter、drop、truncate、comment、grant、revoke。隐性提交,不能回滚。

shell 复制
# 查看脚手架的相关命令
php bin/swoft -h
Usage:
  swoft COMMAND [arg0 arg1 arg2 ...] [--opt -v -h ...]

Options:
      --debug        Setting the application runtime debug level(0 - 4)
      --no-color     Disable color/ANSI for message output
  -h, --help         Display help message for application or command
  -V, --version      Display application version information
      --expand-cmd   Expand sub-commands for all command groups

Available Commands:
  agent      This is an agent for Apllo config center
  app        There are some help command for application[by devtool]
  dConf      There are some help command for application[by devtool]
  dclient    Provide some simple tcp, ws client for develop or testing[by devtool]
  demo       Class DemoCommand
  dinfo      There are some commands for application dev[by devtool]
  dtool      There are some commands for application dev[by devtool]
  entity     Generate entity class by database table names[by devtool]
  http       Provide some commands to manage the swoft HTTP server(alias: httpsrv)
  migrate    Manage swoft project database migration commands[by devtool](alias: mig)
  process    Provides some command for manage process pool
  rpc        Provide some commands to manage swoft RPC server
  tcp        Provide some commands to manage swoft TCP server(alias: tcpsrv)
  test       Class TestCommand
  ws         Provide some commands to manage swoft websocket server(alias: wsserver,websocket)

Migration 注解(标签)

  • time

    指定本次迁移的时间,据此对 migration 表进行查询,判断此次升级是否成功,还是回滚。默认为 0。

  • pool

    指定当前迁移对象本次迁移要使用的数据库连接池,字符串型,可以指定多个,默认使用默认数据库连接池db.pool

注解示例

php 复制
// 只指定时间
@Migration(time=20190913232743)
// 指定时间和可用的连接池
@Migration(time=20190630164222, pool="db.pool,db1.pool")

Migration 命令

  • Migration 创建

    会在 Migration 的指定路径下,根据给定的名称生成一个新的 Migration 基本类框架。

    php bin/sowft mig:create test

    创建一个名字为 test 的 Migration(类名会自动首字母大写)。创建时候不需要连接数据库。

  • Migration 启用

    将 Migration 中记录的所有 dml、ddl 语句进行执行,进行一个数据结构的初始化。

    php bin/swoft mig:up test

  • Migration 关闭

    执行相关数据表的(一般是删除)语句,将已经存在的表进行删除。

    php bin/swoft mig:down test

    注:这些命令尽量在开发环境执行,线上环境,随意使用后果自负!!!

Bean 配置

可以通过 beanFile(默认:app/bean.php)配置,来调整一些配置:

比如通过以下配置,可以调整 migration 的查找和生成的路径,调整完毕后变成了 @base/database,程序会从这个路径下 Migration 文件夹中查找和生成最终 Migration 的名字。

php 复制
return [
    // 配置 migrationManager 的查找路径,此路径默认为 app/Migration
    'migrationManager'   => [
        'migrationPath' => '@base/database',
    ],
];

PS:beanFile 中配置的 bean 定义如果不存在 class 关键字,表示要配置一个已有的 bean 的相关属性。

Migration 实现

Migration 脚手架

最简单的创建方式,就是通过 swoft 的 console 命令进行创建:

php 复制
// 执行如下命令可以创建对应的 Migration 的基本框架.
$ php bin/swoft mig:create cms
Generate migrate /var/www/swoft-app/database/Migration/Cms.php, ensure continue ?
Please confirm (yes|no)[default:yes]: 
yes
Migration Create Success
  className Cms
  file      /var/www/swoft-app/database/Migration/Cms.php

// 执行创建指定 migration 的表结构
php bin/swoft mig:up cms
// 执行删除指定 migration 中的表结构
php bin/swoft mig:down cms
// 执行根据数据表创建实体类,最后参数为表名称
php bin/swoft entity:c user
// 更详细的语句,后台执行创建
php bin/swoft entity:create -d swoft --table tb_content --table_prefix tb_ --remove_prefix tb_

通过交互,可以方便的创建一个新的 Migration 框架。

代码编写

主要注意以下几个方面:

  1. 类名

    完整的 migration 名称为 sprintf('%s%s', $className, $time),即类名和时间拼接到一起的名字,用户在执行 migration 命令时候,会通过 stripos($migrationName, $name)看时候匹配到了相应的迁移过程,匹配到的所有迁移过程会按照时间从低到高,依次执行。此种方式可以用来配合数据库的逐步升级过程。

  2. 类标签 @Migration(time=20250501120000)

    这个是用来记录将要执行的 Migration 的时间,每次执行后,如果时间不变动,就不会再次执行(幂等性)

    时间字符串为 14位(YmdHis)年月日时分秒

  3. up 方法,执行此次迁移涉及的相关操作

  4. down 方法,回滚此次迁移操作

    当执行命令

php 复制
<?php declare(strict_types=1);

namespace Database\Migration;

use Swoft\Devtool\Migration\Migration as BaseMigration;
use Swoft\Devtool\Annotation\Mapping\Migration;
use Swoft\Db\Schema\Blueprint;

/**
 * Class User
 * @Migration(time=20250310101700)
 */
class User extends BaseMigration
{

    /**
     * @inheritDoc
     */
    public function up(): void
    {
        // 用户主表
        $this->schema->createIfNotExists('user', function (Blueprint $blueprint) {
            $blueprint->comment('用户主账号表');

            $blueprint->string('id', 32)->comment('用户唯一ID');
            $blueprint->string('name', 100)->comment('用户名');
            $blueprint->string('password', 64)->comment('密码');
            $blueprint->string('salt', 6)->comment('密码盐');
            $blueprint->bigInteger('create_time', false, true)->comment('创建时间');
            $blueprint->bigInteger('update_time', false, true)->comment('更新时间');

            // 普通btree 索引,前缀索引20位字符,注意这里必须用表达式对象,保证sql按用户自定义格式拼接
            $blueprint->index([Expression::new('`name`(20)')], 'idx_name');
            // 唯一索引
            $blueprint->unique('md5', 'uq_md5');
            // 普通索引,字段名可以用字符串,或者数组。
            // 前缀索引不用表达式对象,会造成整个部分作为字段名引用,进而导致字段无法找的语法问题
            $blueprint->index('create_time', 'idx_create_time');
            $blueprint->engine  = 'Innodb';
            $blueprint->charset = 'utf8mb4';
        });
    }

    public function down(): void
    {
        $this->schema->dropIfExists('user');
    }
}

保证生成sql 的关键字的正确分隔

必要时候要使用 Expression 对象,来封装拼接 sql 的值,这样不会被 sql 将某一部分视为一个整体进行引用。

执行结果

Migration 执行后,首先会通过指定连接池,去相应的数据库中找到 migration 数据表,没有就会新建一个。

然后会将此次执行的 Migration 记录插入到表中,up 命令和 down 命令互为相反,一个建表结构,一个删除表结构。同一命令可以反复执行(幂等性)。可以快速批量创建数据表,方便进行程序测试。

mysql 复制
-- migration 表格式
CREATE TABLE `migration` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `time` bigint NOT NULL,
  `is_rollback` tinyint NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- 数据
-- id	name					time			is_rollback
-- 1	Database\Migration\User	20250310101700	1
-- 其中 name 是执行的 Migration 名字记录,time 是时间
-- is_rollback=1 表示回滚过(down执行后),
-- is_rollback=2 表示已经完成(up命令执行过后)

注意:

此组件,一般用在开发阶段,方便开发可以对数据结构进行统一管理。

数据库的 DDL 处理一般由专门 DBA 处理,不一定可以通过此工具直接执行。

另外,进行 DDL 操作的连接池,最好要跟 DML 普通数据操作的连接池分开来(使用不同权限账号),DDL 连接池不对应用上层暴露,以保证数据库安全。