lucinda/migrations

轻量级PHP7.1库,用于执行跨环境迁移

v2.0.4 2022-12-25 17:51 UTC

This package is auto-updated.

Last update: 2024-09-25 21:22:30 UTC


README

目录

关于

此API作为一个平台,能够自动化在开发环境之间(例如,数据库结构的变化)进行数据迁移,这对于由持续集成/交付系统支持的系统非常有用。基于Symfony DoctrineMigrationsBundle 操作,它以截然不同的方式进行操作

  • 像其他Lucinda库一样,它以简单性、灵活性和效率为指导思想(因此轻量)。它是完全独立的,没有捆绑到任何库或框架中,因此可以在使用或未使用Lucinda框架的任何PHP环境中使用
  • 它就像一个骨架,需要在上面构建各种迁移模式(例如,SQL表的迁移)。这意味着它默认不会假设数据库需要迁移:任何需要在开发环境之间同步的内容都属于迁移范畴

使用的方法

在开发此API时,我自问:所有迁移,无论它们迁移什么,有什么共同之处?以下方面浮现在我的脑海中

  • 迁移应该是一个能够在多个环境中执行一个或多个相关操作的类
  • 类操作应该是 事务性的,这意味着它必须有COMMIT/ROLLBACK操作
  • API需要能够在开发者请求时 生成 一个迁移类
  • 开发者必须 编程 生成的迁移(例如,使用查询)
  • API需要能够在 查找执行 迁移
  • API需要能够在环境之间 跟踪 迁移进度
  • 跟踪需要将每个迁移类分配给一个状态:PENDING、FAILED、PASSED
  • 一旦开发者在自己的环境中执行了迁移并确保一切正常,他必须 提交 它(例如,到GIT)
  • 其他开发者拉取新更改,看到添加了新迁移,然后执行迁移以保持其环境更新
  • 持续集成/交付系统(例如,TeamCity)将在每次构建时自动执行迁移,从而保持所有环境更新

此API仅执行上述API级别操作的物流,而不对以下内容做任何假设

  • 迁移的主题是什么(例如,是否是SQL表相关?)
  • 跟踪迁移进度的存储介质是什么(例如,是否是SQL表?)

支持的迁移操作将包括

  • 生成迁移
  • 运行所有迁移(其状态为PENDING或FAILED),相当于对每个迁移进行全局提交
  • 提交单个迁移(其状态为PENDING或FAILED)
  • 回滚单个迁移(其状态为PASSED)

请注意,与DoctrineMigrationsBundle不同,默认情况下不会支持 diffdump-schema 操作,因为它们对 主题和实现假设(即SQL数据库是主题,DAO实现使用ORM模型)

实现

为了实现创建一个 迁移骨架 以进行特殊化的目标,此API仅定义了共同物流

  • 缓存:存储和跟踪迁移进度的结构蓝图
  • 脚本:支持提交/回滚操作的迁移类蓝图
  • 结果:实现迁移(类操作)执行结果的类
    • 状态:收集可能的迁移状态(待处理、通过、失败)的枚举
  • 包装器:将上述四个组件绑定在一起,以便找到并执行迁移操作的类
  • 控制台执行器:封装包装器以在控制台显示迁移操作结果的类

API完全遵循PSR-4规范,只需要PHP 8.1+解释器和控制台表API(用于在控制台显示迁移结果)。所有类都属于命名空间 Lucinda\Migration!要快速了解其工作原理,请查看

  • 安装:使用composer下载API,创建存储迁移的文件夹
  • 设置缓存:设置存储迁移进度的缓存
  • 执行:使用包装器运行迁移或使用控制台执行器在控制台显示结果以及一个或多个迁移脚本

为确保可靠性,API已完全使用单元测试API进行单元测试,如测试文件夹中所示。要自行运行单元测试,请运行以下命令

cd vendor/lucinda/migrations
php test.php

安装

要安装此API,请转到项目根目录并运行

composer require lucinda/migrations

然后创建一个执行迁移的migrations.php脚本

require(__DIR__."/vendor/autoload.php");

// defines folder to store migrations and creates it if not exists
$folder = "migrations";
if (!file_exists($folder)) {
  mkdir($folder);
}

// TODO: instance a Lucinda\Migration\Cache into $cache variable

// run migrations based on console input
$executor = new Lucinda\Migration\ConsoleExecutor($folder, $cache);
$executor->execute((isset($argv[1])?$argv[1]:"migrate"), (isset($argv[2])?$argv[2]:""));

设置缓存

实现迁移缓存超出了骨架API的范围,该API不对迁移的主题或缓存的存储方式(例如:可以是MySQL,可以是Amazon DynamoDB)做出假设。

缓存实现的示例,绑定到SQL数据访问API,使用MySQL表存储信息

class TableCache implements \Lucinda\Migration\Cache
{    
    private $tableName;

    public function __construct(string $tableName)
    {
        $this->tableName = $tableName;
    }

    public function exists(): bool
    {
        return !empty(SQL("SHOW TABLES LIKE '".$this->tableName."'")->toRow());
    }

    public function create(): void
    {
        SQL("
        CREATE TABLE ".$this->tableName."
        (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT,
        class_name VARCHAR(255) NOT NULL,
        is_successful BOOLEAN NOT NULL DEFAULT TRUE,
        date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY(id),
        UNIQUE(class_name)
        ) Engine=INNODB");
    }

    public function read(): array
    {
        return SQL("SELECT class_name, IF(is_successful=0,2,3) AS status FROM ".$this->tableName."")->toMap("class_name", "status");
    }

    public function add(string $className, int $statusCode): void
    {
        $isSuccessful = ($statusCode==\Lucinda\Migration\Status::PASSED);
        $results = SQL("UPDATE ".$this->tableName." SET is_successful=:status, date=NOW() WHERE class_name=:name", [
            ":status"=>$isSuccessful,
            ":name"=>$className            
        ])->getAffectedRows();
        if ($results == 0) {
            SQL("INSERT INTO ".$this->tableName." SET is_successful=:status, class_name=:name", [
                ":status"=>$isSuccessful,
                ":name"=>$className
            ]);
        }
    }

    public function remove(string $className): void
    {
        SQL("DELETE FROM ".$this->tableName." WHERE class_name=:name", [":name"=>$className]);
    }
}

上述SQL函数的代码

function SQL(string $query, array $boundParameters = array()): \Lucinda\SQL\StatementResults
{
    $preparedStatement = \Lucinda\SQL\ConnectionSingleton::getInstance()->createPreparedStatement();
    $preparedStatement->prepare($query);
    return $preparedStatement->execute($boundParameters);
}

执行

实现缓存后,您可以完成迁移脚本并执行迁移。使用上一节中的两个类的示例

require(__DIR__."/vendor/autoload.php");

// loads dependencies that bind Cache and Script to SQL Data Access API
require(__DIR__."/TableCache.php");
require(__DIR__."/SQL.php");

// defines folder to store migrations and creates it if not exists
$folder = "migrations";
if (!file_exists($folder)) {
  mkdir($folder);
}

// sets up Lucinda SQL Data Access API for current development environment based on XML
new Lucinda\SQL\Wrapper(simplexml_load_file("xml/servers.xml"), getenv("ENVIRONMENT"));

// run migrations based on console input, saving cache to SQL table "migrations"
$executor = new Lucinda\Migration\ConsoleExecutor($folder, new TableCache("migrations"));
$executor->execute((isset($argv[1])?$argv[1]:"migrate"), (isset($argv[2])?$argv[2]:""));

上述示例将在以下情况下工作

  • 已设置一个名为“ENVIRONMENT”的环境变量,其值是您的开发环境名称
  • mysql连接凭据已为当前开发环境设置,如SQL数据访问API配置部分中所述
  • 当前数据库用户对目标模式(包括CREATE)拥有完全权限。如果不是这样,请手动运行create方法中的代码!

现在,您可以转到安装API的文件夹,执行generate命令,转到migrations文件夹并相应地填充up/down方法。一旦至少填充了一个迁移脚本,您将能够从控制台执行迁移。

为了最大化灵活性,不想使用ConsoleExecutor提供的控制台输出的开发者可以直接与Wrapper一起工作,并自行处理输出。

控制台命令

API允许以下控制台命令

生成命令是如何工作的

这将生成一个位于migrations文件夹中的迁移脚本类。打开该类,用查询填充updown方法,如以下示例所示

class Version20210205105634 implements \Lucinda\Migration\Script
{
  public function up(): void
  {
    SQL("
      CREATE TABLE test(
      id INT UNSIGNED NOT NULL AUTO_INCREMENT,
      primary key(id)
      ) ENGINE=INNODB
      ")
  }
  public function up(): void
  {
    SQL("DROP TABLE test");
  }
}

示例

> php migrations.php generate

这将输出位于migrations文件夹中,您必须填充其up/down方法的脚本名称。

迁移命令是如何工作的

当运行migrate时,API将定位所有位于migrations文件夹中的脚本类,并将每个与缓存匹配

  • 如果在缓存中找到,并且迁移状态为PASSED:则跳过up(因为它已经运行过了)
  • 否则:运行up

如果up没有抛出Throwable,则迁移将带有PASSED状态的保存到缓存中。否则,它将带有FAILED状态保存,并将Throwable消息显示在结果摘要中。

示例

> php migrations.php migrate

最后将是一个具有以下列的终端表格

up命令是如何工作的

当运行up(提交)命令时,API将首先检查作为第二个参数接收的脚本名称是否位于磁盘上的migrations文件夹中

  • 如果在磁盘上找到
    • 如果在缓存中找到,并且迁移状态为PASSED:则跳过up(因为它已经运行过了)
    • 否则:运行up
  • 否则:程序以错误退出

示例

> php migrations.php up Version20210205105634

最终结果将是一个具有以下列的终端表格

down命令是如何工作的

当运行down(回滚)命令时,API将首先检查作为第二个参数接收的脚本名称是否位于磁盘上的migrations文件夹中

  • 如果在磁盘上找到
    • 如果在缓存中找到,并且迁移状态为PASSED:则运行down
    • 否则:跳过down
  • 否则:程序以错误退出

示例

> php migrations.php down Version20210205105634

最终结果将是一个具有以下列的终端表格

参考指南

本指南包括API使用的所有类、枚举和接口。

缓存

接口Lucinda\Migration\Cache定义了缓存必须实现的操作,该缓存用于保存迁移Lucinda\Migration\Script的执行进度。它定义了以下方法

脚本

接口Lucinda\Migration\Script定义了迁移脚本必须实现的操作,对应以下方法

如果发生错误,方法应该抛出Throwable,这将通知API它们以错误结束。以下建议适用

  • 如果您需要在上下文内执行多个操作,它们必须进行事务处理(以确保数据完整性)
  • 理想情况下,迁移应该只执行单个操作(以防止冲突)

结果

Lucinda\Migration\Result 封装了 Lucinda\Migration\Script 执行的结果。以下公共方法对开发者相关

通常情况下,您不需要使用这个类,除非您正在基于 Wrapper 构建 Wrapper 的结果显示器!

状态

枚举 Lucinda\Migration\Status 包含了迁移执行结果的 Result 状态列表

包装器

Lucinda\Migration\Wrapper 执行创建、定位和执行迁移 Script 的 API 任务,并更新 Cache。以下定义了以下公共方法

为了提高可重用性,API 不对结果的显示方式做任何假设,因此这个类严格上是一个模型,各种显示器都可以在其基础上构建!

ConsoleExecutor

Lucinda\Migration\ConsoleExecutor 假定您想封装 Wrapper 方法,以便使用映射命令在控制台/终端上显示其结果(更多信息)。它包含以下公共方法

当使用 execute 方法时

  • $operation 值必须对应于 Wrapper 方法名(减去构造函数)
  • $className 值必须只在 $operation 是 up / down 时存在