[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 ...
随机推荐
- Windows Server 2008 R2 /2012 修改密码策略(摘抄 原文地址 https://www.cnblogs.com/mili3/p/7799347.html)
今天建了域环境,在添加新用户的时候,发现用简单的密码时域安全策略提示密码复杂度不够,于是我就想在域安全策略里面把密码复杂度降低一点. 问题: 在“管理工具 >> 本地安全策略 > ...
- Windows 7 控制面板Update选项灰色解决办法
具体解决方法是开始-运行-regedit,打开注册表编辑器,在注册表里找: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows,展开Windo ...
- C# 方法与参数 常见命名空间汇总 using的使用 main方法参数
本文主要讲 C# 常见命名空间 using static 指令 && 调用静态方法 嵌套命名空间&&作用域 别名 Main() 方法 C# 常见命名空间 命名空间 作用 ...
- 乘风破浪:LeetCode真题_001_TwoSum
乘风破浪:LeetCode真题_001_TwoSum 一.前言 沉寂了很长时间,也悟出了很多的道理,写作是一种业余的爱好,是一种自己以后学习的工具,是对自己过往的经验积累的佐证,是检验自己理解深入度的 ...
- 简单转java-web项目
- 在 ServiceModel 客户端配置部分中,找不到引用协定“myservice.Service1Soap”的默认终结点元素。这可能是因为未找到应用程序的配置文件,或者是因为客户端元素中找不到与此协定匹配的终结点元素。
在做项目的时候遇到这个问题,当我在web网站中引用webservice时,很正常,但是当我在一个类库里引用并调用webservice方法后,然后网站调用这个类库里的方法,就会报标题这样的错误.最后纠结 ...
- BZOJ 1001 狼抓兔子 平面图的最小割
题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=1001 题目大意: 见链接 思路: 求最小割,平面图的最小割等价于对偶图的最短路 直接建 ...
- BZOJ3573:[HNOI2014]米特运输(树形DP)
Description 米特是D星球上一种非常神秘的物质,蕴含着巨大的能量.在以米特为主要能源的D星上,这种米特能源的运输和储 存一直是一个大问题.D星上有N个城市,我们将其顺序编号为1到N,1号城市 ...
- leetcode 20 括号匹配
class Solution { public: bool isValid(string s) { stack<char> result; for(char c:s){ if(c == ' ...
- urlparse 用法
ifrom urllib2 import urlparse ‘’ captcha_id = urlparse.parse_qs(urlparse.urlparse(link).query, True) ...