dbmover / core
基于PHP的数据库版本控制工具,核心组件
Requires
- php: >=7.3
- monomelodies/kingconf: ^1.1
Requires (Dev)
- gentry/gentry: ^0.16.11
- gentry/toast: ^0.1.0
- toast/unit: ^2.2.1
- dev-master
- 0.10.3
- 0.10.2
- 0.10.1
- 0.10.0
- 0.9.5
- 0.9.4
- 0.9.3
- 0.9.2
- 0.9.1
- 0.9.0
- 0.8.0
- 0.7.1
- 0.7.0
- 0.6.4
- 0.6.3
- 0.6.2
- 0.6.1
- 0.6.0
- 0.5.2
- 0.5.1
- 0.5.0
- 0.4.13
- 0.4.12
- 0.4.11
- 0.4.10
- 0.4.9
- 0.4.8
- 0.4.7
- 0.4.6
- 0.4.5
- 0.4.4
- 0.4.3
- 0.4.2
- 0.4.1
- 0.4.0
- 0.3.14
- 0.3.13
- 0.3.12
- 0.3.11
- 0.3.10
- 0.3.9
- 0.3.8
- 0.3.7
- 0.3.6
- 0.3.5
- 0.3.4
- 0.3.3
- 0.3.2
- 0.3.1
- 0.3.0
- 0.2.2
- 0.2.1
- 0.2.0
- dev-dependabot/composer/twig/twig-2.15.3
- dev-dependabot/composer/composer/composer-1.10.26
This package is auto-updated.
Last update: 2024-09-22 15:05:57 UTC
README
基于PHP的数据库版本控制工具,核心包。
安装
推荐使用Composer安装DbMover。目前DbMover支持通过dbmover/pgsql
和dbmover/mysql
分别使用PostgreSQL和MySQL。例如:
composer require dbmover/pgsql
设计目标
Web应用程序通常与SQL数据库一起工作。程序员将这样的数据库布局在一个“模式文件”中,这本质上是SQL语句。当一名新的程序员开始在一个项目上工作时,这是可行的,因为她可以简单地创建数据库,并运行模式以启动。问题是当在开发过程中或应用程序的生命周期中,需要对此模式进行更改时。
手动执行此操作既繁琐又容易出错。记得为每个更改编写迁移也很繁琐,跟踪哪些迁移已经被应用(或未应用)也很容易出错(真实生活案例:导入特定数据库的较旧版本以解决特定问题,并且迁移“注册表”本身也过时)。
DbMover通过查看中央的、领先的、版本控制的模式文件并应用所需的任何更改来自动化此任务。这允许您盲目运行vendor/bin/dbmover
,例如在post-receive钩子中。
dbmover.json
文件
DbMover使用dbmover.json
文件进行配置。这应该在项目的根目录中(即从vendor/bin
向上两个文件夹)。格式如下:
{ "your dsn": { "user": "yourUserName", "pass": "something secret", "schema": ["path/to/file1.sql", "path/to/file2.sql"[, ...]], "plugins": [] } }
每次您运行DbMover时,它将遍历所有条目并应用您请求的内容。许多项目将使用单个数据库,但如您所见,DbMover在单个配置文件中完全支持多个数据库。
当然,您不希望将实际的用户名/密码放在受版本控制的配置文件中(您确实希望这样做)。最佳实践(除非您独自工作)是版本控制一个带有用户名/密码空白的dbmover.json.sample
文件,但包含其他重要信息(模式和插件)。
DSN
这是数据库的“DSN”连接字符串。确切格式将因供应商而异,但通常为“vendor:dname=NAME;host=HOST;port=PORT”类型,其中port
通常可以省略以使用默认值。如有疑问,请咨询系统管理员。目前支持的供应商是pgsql
和mysql
;如果您想贡献另一个供应商,请参阅下面的“贡献”部分。
模式
这是一个模式文件的数组,相对于您仓库的根目录。DbMover将按顺序处理它们。请注意,提供将模式拆分为多个文件的选项是为了方便/可维护性 - 在DbMover开始工作之前,DbMover会将它们全部连接在一起。
插件
DbMover将加载的插件数组以执行迁移。通过使用插件,我们使DbMover非常适用于您的确切需求。要仅使用合理的默认值,只需指定特定数据库供应商的插件(例如,Dbmover\Pgsql\Plugin
)。有关插件的更多信息见下文。
运行DbMover
只需简单地执行vendor/bin/dbmover
。对于每个指定的数据库,它将对您定义的模式执行请求的操作。如果您已经根据上面的教程填写了dbmover.json
并在现在运行它...什么也没有发生。这是因为所有实际功能都在插件中。您需要在dbmover.json
配置中指定它们。
插件
截至版本0.6,DbMover使用插件来指定操作。需要注意的是,一个插件本身不应该改变数据库中的任何内容;它们用于收集执行迁移时执行的命令。因此,由于您的plugins
数组此时为空,DbMover尚不知道要做什么。请参阅上面的语法。
插件按指定顺序处理,并且可以指定多次。在这种情况下,它们将简单地多次运行(这实际上是很有用的)。
每个插件实际上运行两次;一次修改SQL,一次在__destruct
中进行清理。销毁调用按与调用调用相同的顺序进行(请参阅下文“编写自定义插件”)。
元包
插件还可以加载其他插件;事实上,有一些官方提供的元插件。通常,它们会为您数据库的类型和设计执行所需操作。但是,您也可以混合搭配,编写自己的或组合这些。
例如,假设您有一个MySQL数据库,并且只想让DbMover迁移所有内容。在这种情况下,您应该安装以下插件
composer require dbmover/mysql
...并注册此单个插件
{ ... plugins: ["Dbmover\\Mysql\\Plugin"] }
编写自定义插件
每个插件都必须实现Dbmover\Core\PluginInterface
。通常您会想要扩展抽象的Dbmover\Core\Plugin
,但在某些情况下这可能是不可取的(因此有接口)。
插件使用单个参数构造:正在运行迁移的Dbmover\Core\Loader
实例。通过此对象,您可以使用getPdo()
方法访问底层PDO
实例。它还通过getDatabase()
公开当前数据库的名称。
插件的主要任务是接收当前可用的SQL,将其相关部分转换为迁移加载器的操作,并返回(通常是修改后的)SQL字符串。理想情况下,在所有插件都运行后,没有SQL可以检查。
上述主要任务是通过魔法__invoke
方法完成的。它接受当前SQL作为字符串参数,并必须返回(可选修改后)的新当前SQL。
插件还可以选择实现一个__destroy
方法。插件在运行顺序与运行顺序相同,在所有插件运行后。
元插件将覆盖__construct
方法并手动加载它们自己的“子插件”。不要在__invoke
或__destruct
实现中添加新插件 - 在这些运行时,DbMover已经组装了插件和行为的指定是不明确的(并且可能是不可预测的)。
数据库供应商通常会添加非常特定的行为。我们已经实现了我们所能想到的最多常见用例(即,它们适用于我们自己的相当复杂的数据库),但总可以总是进行改进。如果您编写了有用的插件并希望分享,请参阅下文的“贡献”。
编写您的模式
您应该将模式编写成好像要在完全空的数据库上运行一样 - 之后您应该有一些可以工作的东西,可能包括默认数据。
添加表
只需将新的表定义添加到模式中,然后重新运行。
添加列
忘记在数据库中添加列?没问题,只需在您的模式中添加它,然后重新运行DbMover。
请注意,新列始终追加到表的末尾。一些数据库驱动程序(如MySQL)支持BEFORE
关键字,但例如PostgreSQL不支持,DbMover尽可能地数据库无关。
修改列
只需在模式文件中更改列定义,DbMover 会为您进行修改。这假设列保留相同的名称,并且其中包含的数据与新类型兼容(或可以丢弃);对于更复杂的修改,请参阅以下内容。
删除列
只需从模式中删除它们并重新运行。注意:之后它们将真正、真正地被删除,数据库不支持撤销。
删除表、视图等。
只需从模式中删除它们并重新运行。再次提醒:它们将被真正删除。
索引和外键约束
根据您的数据库供应商,在创建表时可能允许指定这些约束。对此的支持仍然是非常实验性的,并且肯定不完整。所以如果可能的话,不要这样做。相反,使用 CREATE INDEX
或 ALTER TABLE
语句在表创建后创建这些约束。
主键可能在 CREATE TABLE
块中已经指定。其他约束仍在开发中。
松散的 ALTER
语句
有时您在创建后需要特别对表进行 ALTER
,例如当它有一个外键引用您稍后需要创建的表时。例如,一个 blog_posts
表可能引用一个 lastcomment
,而 blog_post_comments
又反过来引用 blog_posts
上的 blog_id
。在这种情况下,您首先创建帖子表,然后创建评论表(带其外键约束),最后将约束添加到帖子表中。
DbMover 将按模式文件中指定的顺序单独运行每个 ALTER TABLE
语句,因此只需在逻辑上添加外键的位置添加外键即可。该语句要么会静默失败(如果列不存在或类型错误,等待迁移),要么最终会成功。
更复杂的模式更改
某些事情自动确定可能比较困难,比如表或列重命名。您应该使用 IF
块包装这些更改,并在迁移需要时通过条件通过,否则失败。
根据您的数据库供应商,可能需要将这些内容包装在一个“废弃”过程中。例如,MySQL 只支持过程内的 IF
。DbMover 的供应商特定类会为您处理这些。废弃过程以 tmp_
为前缀。
请注意,条件运算符(ELSE IF
、ELSIF
)的确切语法也取决于供应商。确定是否需要重命名表的方法也取决于供应商(尽管在当前版本中 DbMover 仅支持 ANSI 兼容的数据库,因此您可以使用 INFORMATION_SCHEMA
来此目的)。
条件运算符
DbMover 通过 dbmover/conditionals
插件支持在模式中包含 IF
块。这是对 SQL 的扩展,因为这些块通常仅在过程中允许。DbMover 会为您包装它们。
插入默认数据
为防止重复插入,应将其包装在 IF NOT EXISTS ()
条件中,如下所示
IF NOT EXISTS (SELECT 1 FROM mytable WHERE id = 1) THEN INSERT INTO mytable (id, value1, value2, valueN) VALUES (1, 2, 3, 4); END IF;
这通常需要 dbmover/VENDOR-conditionals
插件(它未包含在元包中)。有关更多信息,请参阅 dbmover/mysql-conditionals
和 dbmover/pgsql-conditionals
。
从一个表转移到另一个表的数据
这有时是必要的。在这种情况下,您应使用 IF
块并查询例如 INFORMATION_SCHEMA
(根据您的供应商)以确定迁移是否已运行。
重要:如果迁移已运行,则
IF
应评估为false
,以避免运行两次。请小心处理。
简化和抽象的伪示例
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE ...) THEN INSERT INTO target SELECT * FROM original; END IF;
有关条件运算符的说明,请参阅上一节中的注释。
注意事项
整洁
DbMover假设SQL格式良好,关键字全部大写。它并不具体验证您的SQL,尽管任何错误都会导致语句失败并停止脚本(因此理论上它们不会造成太大损害...)。DbMover会告诉您它遇到了什么错误。
“整齐”的意思是写CREATE TABLE
而不是create Table
等。
DbMover也不识别例如MySQL使用反引号转义保留字的情况。不要这么做,这是邪恶的。
对于
ignore
正则表达式,如果您需要,可以使用“奇怪”的对象名称,因为这些是逐字正则化的。
对于提升,假设要提升的语句在行的开头(即,例如,正则表达式中的/^IF
)。
数据库可能区分大小写,也可能不区分大小写;请注意,DbMover是区分大小写的,因此请保持拼写一致。
存储引擎和校对规则
目前DbMover忽略了这些。计划支持MySQL;对于PostgreSQL,更改校对规则是数据库级别的操作,DbMover无法处理(需要重新创建整个数据库)。
首先测试您的模式
始终针对更新的模式在测试数据库上运行DbMover。每个人都可能犯错,您不希望这些错误损坏生产数据库。最好是对实际生产数据库的副本进行测试。
在迁移期间关闭您的应用程序
根据您的要求和您的数据集大小,迁移可能需要几分钟。您不希望用户在模式尚未稳定时编辑任何数据!
DbMover无法决定您的应用程序如何处理其down状态。一个简单的方法是为此编写自己的插件
{ "dsn": { "plugins": ['Myplugins\\Down', ..., 'Myplugins\\Up'] } }
处理down/up状态的简单方法是在应用程序的根目录中写入一个空文件(例如,简单地称为down
),在前端控制器中检查它,并在再次启动应用程序时删除它。一个非常基本的示例
<?php namespace Myplugins; use Dbmover\Core\PluginInterface; class Down implements PluginInterface { public $description = 'Bringing application down...'; public function __invoke(string $sql) : string { $cwd = getcwd(); `touch $cwd/down`; return $sql; } } class Up implements PluginInterface { public $description = 'Briging application back up...'; public function __destruct() { $cwd = getcwd(); `rm $cwd/down`; parent::__destruct(); } }
...在你的前端控制器中(在这个例子中,简单地是index.php
)
<?php if (file_exists('/path/to/down')) { die("Application is down for maintainance."); } // ...other code...
在迁移之前备份您的数据库
如果您对实际副本进行了测试并且它工作正常,这通常不是必需的,但毕竟安全比后悔好。您可能会在迁移过程中遇到停电!
此外,脚本运行正确并不一定意味着它做了您想做的事情。迁移后始终验证您的数据。
使用上一节中的Up
和Down
自定义插件,您可以自动处理此操作。使用Loader的getErrors()
方法查看是否需要回滚,或者您可以直接删除备份(或只是以防万一手动检查时出现任何可疑情况)。
贡献
SQLite支持计划在不久的将来实现,但它不是我的优先事项(客户偶尔会使用它,但它真的不是非常适合Web开发的数据库)。
MSSQL和Oracle是有效的选择,但我们无法访问它们。如果您可以并愿意移植DbMover的数据库特定部分,请随意创建仓库分支并发送给我们拉取请求!
没有正式的风格指南,但请查看现有代码,并尽量使您的编码风格与之保持一致。如果您正在处理我无法/不会支持的分销商,请确保您也为这些添加单元测试。
贡献也可以是错误报告的形式(请针对受影响的包提交!)和功能请求(分销商支持绝非详尽无遗,只是最常用的选项)。
插件包有时也包含在它们的README.md
中的TODO列表。如果您的请求已经列在那里,就没有必要报告它,因为它已经在路线图中了。
运行单元测试,请执行vendor/bin/toast
。测试需要一个名为dbmover_test
的空MySQL数据库,用户名为dbmover_test
,密码为moveit
。
调试和开发
使用--dry-run
标志运行dbmover
,仅组装要执行的操作列表,实际上不进行任何更改。