[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 ...
随机推荐
- java笔记--String类格式化当天日期转换符文档
String类格式化当天日期 --如果朋友您想转载本文章请注明转载地址"http://www.cnblogs.com/XHJT/p/3877389.html "谢谢-- 转换符:% ...
- 解决WPF图片模糊最佳方法
对于图片显示模糊相信很多人都遇到过,其实,有些图片会因为垂直分辨率/水平分辨率不同而造成在WPF界面上显示出现模糊. WPF默认是96Dpi,但有些图片可能是72DPI甚至更低或更高,这样就会出现图片 ...
- SQL查询语句执行的逻辑顺序
一.简介 大家都知道SELECT语句是用来查询数据表中的数据的,构成SELECT语句的还有各种元素(where.from.group by等),不同元素又代表了不同的处理逻辑,那么这些元素在执行查询时 ...
- 小J学python--Exception-异常
现在我们要打开一个名为fuck的文件,fuck是不存在的,看看异常是怎么工作的 不捕获异常的情况 #打开文件 open('fuck') 执行结果 捕获所有异常 Exception是所有异常类的父类,所 ...
- 附加进程找不到w3wp.exe进程解决方案
在进程列表的下面,有个show processes in all sessions(显示所有用户的进程(U)),把它勾上就能看到了 ,就是这么简单.
- 当有多个相同的DIV时,我怎么判断我点击的是哪个嘞
链接:https://segmentfault.com/a/1190000003480973?utm_source=tuicool&utm_medium=referral 序言 这是我曾经面试 ...
- 021.10 IO流 打印流
内容:PrintStream:字节流 PrintWriter:字符流 PrintStream public static void main(String[] args) throws IOEx ...
- iOS NSRunloop的简单理解
最近学习了下NSRunloop. 作一下简单的理解: 1.runloop与线程的关系,每一个线程创建是都会有伴有一个runloop诞生,runloop用来接收事件源,让线程执行事件.当没有事件处理时, ...
- xgcom linux下的串口助手
好用到爆@@! 2.Install: Source code: http://code.google.com/p/xgcom/ svn checkout http://xgcom.googlecode ...
- js字符串和数组
sustr substring slice的联系与区别 str.substr(2,5) //从索引2开始截取5个字符,原有字符串str不变 str.substring(2,5) //从索引2开始截 ...