[Doctrine Migrations] 数据库迁移组件的深入解析四:集成diff方式迁移组件
场景及优势
熟悉Symfony框架之后,深刻感受到框架集成的ORM组件Doctrine2的强大之处,其中附带的数据迁移也十分方便。Doctrine2是使用Doctrine DBAL组件把代码里面的表结构与实际数据库中的表结构进行对比的方式进行数据迁移。这种方式比之前版本管理的方式更加精准也更方便。
Symfony框架是自身ORM组件支持,但是很多项目并没有使用其中的ORM功能,或者有自己的ORM组件,又该如何集成diff方式的迁移呢?下面我们就来完成这个任务。
源码解析
在研究组件源码时,发现一个类:Doctrine\DBAL\Migrations\Provider\SchemaDiffProvider,其中getSqlDiffToMigrate方法就是我们编写diff脚本的关键。
/**
* @param Schema $fromSchema
* @param Schema $toSchema
* @return string[]
*/
public function getSqlDiffToMigrate(Schema $fromSchema, Schema $toSchema)
{
return $fromSchema->getMigrateToSql($toSchema, $this->platform);
}
该方法通过传入的两个入参(fromSchema和toSchema),来对比旧数据结构和新数据结构的差异,最后返回相关的sql语句。那么我们只要拿到现有数据库中的表结构,以及代码里面的表结构,就能实现diff方式的数据迁移了。
获取现有数据库的表结构可以通过上面的createFromSchema方法直接获取。
然后我们需要把数据结构写入代码,再从代码中获取实时的表结构,最后diff。
编写diff脚本
首先在项目创建diff文件,并赋予执行权限。之后是根据上面的思路来编写脚本,以下是完整代码示例:
#!/usr/bin/env php
<?php
require_once 'vendor/autoload.php';
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Migrations\Provider\SchemaDiffProvider;
use Doctrine\DBAL\Schema\MySqlSchemaManager;
use Doctrine\DBAL\Schema\Schema;
// 读取数据库配置信息
$db_config = include 'config/db.php';
$db_params = [
'driver' => 'pdo_mysql',
'host' => $db_config['host'],
'port' => $db_config['port'],
'dbname' => $db_config['dbname'],
'user' => $db_config['user'],
'password' => $db_config['password'],
];
try {
$connection = DriverManager::getConnection($db_params);
} catch (DBALException $e) {
echo $e->getMessage() . PHP_EOL;
exit;
}
// 获取数据库表结构
$schema_manager = new MySqlSchemaManager($connection);
$sdp = new SchemaDiffProvider($schema_manager, $schema_manager->getDatabasePlatform());
$from_schema = $sdp->createFromSchema();
// 获取写在脚本里面的表结构
$to_schema = new Schema();
$schema_class_list = glob(__DIR__ . '/db/tables/*.php');
foreach ($schema_class_list as $file) {
require_once $file;
$class = '\\db\\tables\\' . basename($file, '.php');
if (class_exists($class)) {
$cls = new $class;
if (method_exists($cls, 'up')) {
$cls->up($to_schema);
}
}
}
// 进行对比,输出结果
$diff = $sdp->getSqlDiffToMigrate($from_schema, $to_schema);
if ($diff) {
foreach ($diff as $sql) {
echo $sql . ';' . PHP_EOL;
}
}
diff方式的迁移使用
之后,所有的数据表都需要在根目录下db/tables目录里面建立对应的表结构脚本(任意文件名本),下面,我们以test_user表举例:
创建db/tables/testUser.php文件,编写代码:
<?php
namespace db\tables;
use Doctrine\DBAL\Schema\Schema;
class testUser
{
public function up(Schema $schema): void
{
$table = $schema->createTable('test_user');
$table->addColumn('id', 'integer')->setUnsigned(true)->setAutoincrement(true);
$table->addColumn('name', 'string')->setLength(20)->setComment('用户名');
$table->addColumn('age', 'integer')->setUnsigned(true)->setDefault(0)->setComment('年龄');
$table->addColumn('sex', 'string')->setLength(2)->setDefault('')->setComment('性别');
$table->addColumn('is_del', 'boolean')->setDefault(false)->setComment('是否删除');
$table->setPrimaryKey(['id'])->addIndex(['is_del']);
}
}
然后在根目录执行./diff命令,就会看到输出的sql信息。
添加黑名单过滤
到这一步diff方式的迁移已经能正常使用了,但是我们发现,如果是在已有的项目中途加入迁移功能时,使用diff命令会把已经存在的表删除掉,另外,也有时候会有其他表不需要管理的,我们就需要过滤掉这些表。现在我们来添加黑名单功能。
在进行对比之前,加入以下代码:
// 过滤黑名单
$black_list = ['migration_versions', 'test1', 'test2', 'test3'];
foreach ($black_list as $black_table) {
$from_schema->hasTable($black_table) && $from_schema->dropTable($black_table);
$to_schema->hasTable($black_table) && $to_schema->dropTable($black_table);
}
再执行diff命令时,就会过滤掉$black_list变量数组里面的表,当然这个黑名单变量完全可以用其他配置文件来代替,可以任意方式的自定义。
脚本增强
最好再对diff脚本进一步的增强,让它可以直接执行sql。
修改最后的对比代码为以下内容:
// 进行对比,输出结果
$diff = $sdp->getSqlDiffToMigrate($from_schema, $to_schema);
if (empty($diff)) {
echo 'No need to update the schema.' . PHP_EOL;
} elseif ($diff) {
$statement = '';
foreach ($diff as $sql) {
$statement .= $sql;
echo $sql . ';' . PHP_EOL;
}
echo 'Do you want to execute these SQLs? (Y/n)';
$flag = trim(fgets(STDIN));
if ($flag === 'Y') {
$connection->beginTransaction();
try {
$connection->executeUpdate($statement);
$connection->commit();
} catch (\Exception $e) {
try {
$connection->rollBack();
} catch (ConnectionException $e) {
echo $e->getMessage();
exit;
}
echo $e->getMessage() . PHP_EOL;
exit;
}
echo 'SQL executed successfully' . PHP_EOL;
}
}
现在,执行diff命令时就会提示是否需要执行sql(默认不执行)。
结语
现在,diff方式的数据迁移就已经比较完美的集成与项目中了,但是diff方式也有缺陷,就是不能对数据进行管理,比如有时候菜单或者或者权限就需要数据也能同步。所以两种方式要根据自己的项目情况去选择。
此系列的所有代码都可以在文章最后的代码库链接中找到对应的代码,并且每次commit对应每篇文章,方便读者对应文章和代码。
现在此系列就已经完结,希望这个系列的文章能对你使用数据迁移组件有所帮助,感谢您的阅读,也希望提出您宝贵的意见。
在我的代码库可以查看这篇文章的详细代码,欢迎star。
[Doctrine Migrations] 数据库迁移组件的深入解析四:集成diff方式迁移组件的更多相关文章
- [Doctrine Migrations] 数据库迁移组件的深入解析三:自定义数据字段类型
自定义type 根据官方文档,新建TinyIntType类,集成Type,并重写getName,getSqlDeclaration,convertToPHPValue,getBindingType等方 ...
- [Doctrine Migrations] 数据库迁移组件的深入解析二:自定义集成
自定义命令脚本 目录结构 目前的项目结构是这样的(参照代码库): 其中,db/migrations文件夹是迁移类文件夹,config/db.php是我们项目原有的db配置,migrations.php ...
- [Doctrine Migrations] 数据库迁移组件的深入解析一:安装与使用
场景分析 团队开发中,每个开发人员对于数据库都修改都必须手动记录,上线时需要人工整理,运维成本极高.而且在多个开发者之间数据结构同步也是很大的问题.Doctrine Migrations组件把数据库变 ...
- 使用vue开发输入型组件更好的一种解决方式(子组件向父组件传值,基于2.2.0)
(本人想封装一个带有input输入框的组件) 之前使用vue开发组件的时候,在遇到子组件向父组件传递值时我采用的方法是这样的: 比如子组件是一个输入框,父组件调用时需要获取到子组件输入的值,子组件通过 ...
- MVC5中Model层开发数据注解 EF Code First Migrations数据库迁移 C# 常用对象的的修饰符 C# 静态构造函数 MSSQL2005数据库自动备份问题(到同一个局域网上的另一台电脑上) MVC 的HTTP请求
MVC5中Model层开发数据注解 ASP.NET MVC5中Model层开发,使用的数据注解有三个作用: 数据映射(把Model层的类用EntityFramework映射成对应的表) 数据验证( ...
- EFCodeFirst Migrations数据库迁移
EFCodeFirst Migrations数据库迁移 数据库迁移 1.生成数据库 修改类文件PortalContext.cs的静态构造函数,取消当数据库模型发生改变时删除当前数据库重建新数据库的设置 ...
- 鸿蒙开源第三方组件 ——B站开源弹幕库引擎的迁移(上)
鸿蒙入门指南,小白速来!0基础学习路线分享,高效学习方法,重点答疑解惑--->[课程入口] 目录: 一.弹幕库的基础知识 二.弹幕库的使用方法 三.sample解析 四.作者系列文章合集 前言 ...
- 如何使用doctrine:migrations:migrate
doctrine:migrations:migrate: 可以生成数据库表 当新建完实体之后需要执行 doctrine:migrations:diff 更新差异到db 然后就ok了,这时候你的app/ ...
- 可展开的列表组件——ExpandableListView深入解析
可展开的列表组件--ExpandableListView深入解析 一.知识点 1.ExpandableListView常用XML属性 2.ExpandableListView继承BaseExpanda ...
随机推荐
- 古老的CSS同高列问题
今日在网页设计时,遇到了希望页面的几列同高需求的实现问题,搜罗了一下google,找到以下文章,感觉不错,翻译过来,同时作为学习加深印象. https://css-tricks.com/fluid-w ...
- .net core系列之《在.net core中使用MemoryCache实现本地缓存》
说到内存缓存MemoryCache不由的让我想起.Net Framework中的MemoryCache,它位于 System.Runtime.Caching 程序集中. 接下来我们来看看.net co ...
- 装饰器( decorate )
装饰器分步解释-形成过程: #-*- coding: UTF-8 -*- #示例1: def deco(p_args): def pack(): print('haha,i am deco fun') ...
- Google论文(1) GFS:Google文件系统 - 思维导图
Google文件系统是一个面向大规模分布式数据密集型应用的可扩展分布式文件系统. 这里的思维导图作为个人的读书笔记. 参考资料: <google系列论文>- GFS
- Python学习---Django重点之静态资源配置
[官网静态文件介绍] https://docs.djangoproject.com/en/1.10/howto/static-files/ # settings.py 配置静态资源文件 # STATI ...
- Python实例---基于页面的后台管理[简单版]
后台管理菜单 + 母板[css/content/js] 向后台提交数据[2种]: 1. 模态对话框(数据少操作,且Js复杂): form表单 :优点:简单,前端提交后后台处 ...
- Eclipse导出可运行的jar包并运行
https://blog.csdn.net/kpchen_0508/article/details/49275407 程序运行的第二种方式:
- 深入浅出WinDbg——利用快速定位错误
场景描述: Sharepoint代码的某方法LoadLines()中使用了SPSecurity.RunWithElevatedPrivileges(delegate() 此方法两次调用了Common. ...
- iframe加载方案及性能
普通方法加载iframe 在onload之后加载iframe setTimeout来加载iframe 友好型iframe加载 转载地址:翻译文章-iframe异步加载技术及性能 英文原文:Iframe ...
- echarts柱状图,改变柱状颜色
在使用echarts产生的柱状图中,有时候自动产生的颜色大不如人意,可以通过以下参数进行修改. series : [ { name:'天数', type:'bar', stack: '天', data ...