[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。

原文地址:https://www.cnblogs.com/tbphp/p/9164424.html

时间: 2024-08-27 06:15:54

[Doctrine Migrations] 数据库迁移组件的深入解析四:集成diff方式迁移组件的相关文章

[Doctrine Migrations] 数据库迁移组件的深入解析三:自定义数据字段类型

自定义type 根据官方文档,新建TinyIntType类,集成Type,并重写getName,getSqlDeclaration,convertToPHPValue,getBindingType等方法. TinyIntType.php完整代码: <?php namespace db\types; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Typ

[Doctrine Migrations] 数据库迁移组件的深入解析一:安装与使用

场景分析 团队开发中,每个开发人员对于数据库都修改都必须手动记录,上线时需要人工整理,运维成本极高.而且在多个开发者之间数据结构同步也是很大的问题.Doctrine Migrations组件把数据库变更加入到代码中和代码一起进行版本管理,很好的解决了上述问题. Doctrine Migrations是基于Doctrine DBAL组件的数据迁移组件.集成于Laravel,Symfony等主流框架.大概可以分为两种方式进行迁移,即版本管理方式和diff方式. 版本管理:把数据库变更写入到代码中,来

EFCodeFirst Migrations数据库迁移

EFCodeFirst Migrations数据库迁移 数据库迁移 1.生成数据库 修改类文件PortalContext.cs的静态构造函数,取消当数据库模型发生改变时删除当前数据库重建新数据库的设置. static PortalContext() {  Database.SetInitializer<PortalContext>(null);  }  添加Migrations文件夹,并生成类文件Configuration.cs. namespace Portal.Migrations { 

Oracle数据库中的blob类型解析

Oracle的Blob字段比较特殊,他比long字段的性能要好很多,可以用来保存例如图片之类的二进制数据. 写入Blob字段和写入其它类型字段的方式非常不同,因为Blob自身有一个cursor,你必须使用cursor对blob进行操作,因而你在写入Blob之前,必须获得cursor才能进行写入,那么如何获得Blob的cursor呢? 这需要你先插入一个empty的blob,这将创建一个blob的cursor,然后你再把这个empty的blob的cursor用select查询出来,这样通过两步操作

OpenCV 2.4.8组件结构全解析

转自: http://blog.csdn.net/huang9012/article/details/21811271 之前啃了不少OpenCV的官方文档,发现如果了解了一些OpenCV整体的模块架构后,再重点学习自己感兴趣的部分的话,就会有一览众山小的感觉,于是,就决定写出这篇文章,作为启程OpenCV系列博文的第二篇. 至于OpenCV组件结构的研究方法,我们不妨管中窥豹,通过opencv安装路径下include目录里面头文件的分类存放,来一窥OpenCV这些年迅猛发展起来的庞杂组件架构.

DRF-解析器组件源码解析

解析器组件源码解析 解析器组件源码解析 1 执行request.data 开始找重装的request中的data方法 2 在dispatch找到重装的request def dispatch(self, request, *args, **kwargs): request = self.initialize_request(request, *args, **kwargs) ***这里开始找data 3 在initialize_request中找到实例request的类Request() def

(在数据库中调用webservices。)SQL Server 阻止了对组件 &#39;Ole Automation Procedures&#39; 的 过程&#39;sys.sp_OACreate&#39; 的访问

--开启 Ole Automation Procedures sp_configure 'show advanced options', 1; GO RECONFIGURE; GO sp_configure 'Ole Automation Procedures', 1; GO RECONFIGURE; GO EXEC sp_configure 'Ole Automation Procedures'; GO --关闭 Ole Automation Procedures sp_configure '

深入解析React中的元素、组件、实例和节点

React 深入系列,深入讲解了React中的重点概念.特性和模式等,旨在帮助大家加深对React的理解,以及在项目中更加灵活地使用React. React 中的元素.组件.实例和节点,是React中关系密切的4个概念,也是很容易让React 初学者迷惑的4个概念.现在,我就来详细地介绍这4个概念,以及它们之间的联系和区别,满足喜欢咬文嚼字.刨根问底的同学的好奇心. 元素 (Element) React 元素其实就是一个简单JavaScript对象,一个React 元素和界面上的一部分DOM对应

Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? ??如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一样,本篇文章最最核心的要点就是 SqlSession实现数据库操作的源码解析.但按照惯例,我这边依然列出如下的问题: 1. SqlSession 是如何被创建的? 每次的数据库操作都会创建一个新的SqlSession么?(也许有很多同学会说SqlSession是通过 SqlSessionFactor