hyperf 二十五 数据迁移 一

教程:Hyperf

版本说明

一 生成迁移

php bin/hyperf.php gen:migration create_users_table

执行文件:Hyperf\Database\Commands\Migrations\GenMigrateCommand

功能:创建迁移文件

参数:

  1. name  文件名称

选项:

  1. create 创建。设置create未设置table,则create的值设置给table,其值重新赋值为true。

  2. table 表名。table未设置,则根据参数name文件名正则出表名。

  3. path 路径

  4. realpath 绝对路径

参数通过Hyperf\Database\Commands\Migrations\GenMigrateCommand::getArguments()设置,选项通过Hyperf\Database\Commands\Migrations\GenMigrateCommand::getOptions()设置。

文件创建通过调用Hyperf\Database\Migrations\MigrationCreator::create()实现。

若根据name转化出的类名对应的类存在会抛出错误,类似于"[ERROR] Created Migration: A CreateUserinfoTable class already exists."。

若table为空,则Stub文件为blank.stub;create为true,stub文件为create.stub,否则为update.stub。这些stub文件均为系统的模板文件,存放于vendor\hyperf\database\src\Migrations\stubs。

stub文件用于字符串替换。最后会将替换后的字符串写入path对应的文件,文件名为name。

二 迁移结构

生成的迁移文件\migrations\2024_03_02_034344_create_userinfo_table.php,中包括up()和down()两个方法。up()用于添加新的数据表,字段或者索引到数据库。down()反之用于回滚。

2.1 up

通过 Hyperf\Database\Schema\Schema::__callStatic()获取链接对象。

Schema::__callStatic()调用ConnectionResolver::connection()。connection()参数为空,则使用default数据库链接,否则从加载的配置文件信息读取对应的数据库链接信息。

源码中,数据库连接使用default。

ConnectionResolver::connection()调用Hyperf\Pool\Pool::get()中通过Hyperf\DbConnection\Pool\DbPool::createConnection()创建的Hyperf\DbConnection\Connection类实体对象。

Hyperf\DbConnection\Connection::__construct()根据配置获取链接,比如mysql驱动使用Hyperf\Database\MySqlConnection。MySqlConnection父类为Hyperf\Database\Connection。

所以Schema::__callStatic()最后调用Hyperf\Database\Connection::getSchemaBuilder()或其子类的getSchemaBuilder()方法,返回的Hyperf\Database\Schema\Builder类实体对象。

所以Schema::create(),就是调用Hyperf\Database\Schema\Builder::create()。Builder::create()调用Hyperf\Database\Schema\Blueprint::build()。

还是以mysql驱动为例,设置默认grammar为Hyperf\Database\Schema\Grammars\MySqlGrammar。Blueprint::build()中使用MySqlGrammar::compileCreate()生成对应命令的sql字符串,再通过Hyperf\Database\Connection::statement()执行。

根据源码,Hyperf\Database\Schema\Schema::__callStatic()应该是执行Hyperf\Database\Schema\Blueprint的方法。

2.2 down

根据up()的逻辑,down实际执行Schema::dropIfExists(),即为执行Hyperf\Database\Schema\Blueprint::dropIfExists()。使用mysql驱动,会执行Hyperf\Database\Schema\Grammars\MySqlGrammar::compileDropIfExists()。

三 运行迁移

运行命令

php bin/hyperf.php migrate

没有参数,只有选项。

选项:

  1. database 设置链接使用的数据库

  2. seed 指示是否应该重新运行种子任务
  3. force 在生产环境中强制运行该操作

根据database获取数据库链接,默认default,为配置D:\config\autoload\databases.php文件中数组最外层的key值。根据其获取数据库连接的配置信息。根据对应链接类比如Hyperf\Database\MySqlConnection,其中的getSchemaBuilder()方法获取的Hyperf\Database\Schema\MySqlBuilder类的实体对象,执行MySqlBuilder::hasTable()判断表是否存在。

可能是源码版本的问题,没找到用户设定的force使用代码。

使用seed参数执行db:seed命令,其命令参数--force设置为true,用于填充数据。执行Hyperf\Database\Commands\Seeders\SeedCommand::handle()。可能需要先运行gen:seeder,执行Hyperf\Database\Commands\Seeders\GenSeederCommand::handle()。根据vendor\hyperf\database\src\Seeders\stubs文件生成种子文件,用于SeedCommand::handle()中执行。

3.1 强制执行

php bin/hyperf.php migrate --force

3.2 回滚迁移

3.2.1 回滚指定迁移

php bin/hyperf.php migrate:rollback

选项:

  1. database 设置链接使用的数据库
  2. pretend  转储将要运行的SQL查询

  3. step 执行步数

执行文件Hyperf\Database\Commands\Migrations\RollbackCommand,RollbackCommand::handle()运行Hyperf\Database\Migrations\Migrator::rollback()。

Migrations\Migrator::rollback()使用参数step。step参数意味着回滚的步数,需要配合数据库记录回滚的操作,通过使用step作为limit参数创建sql语句,返回批次号列表。未设置step参数返回最有一次迁移的批次号。

Migrator::rollback()实际运行迁移结构中的down(),最后删除管理迁移记录表中的对应数据。

3.2.2 回滚所有迁移

php bin/hyperf.php migrate:reset

选项:

  1. database 设置链接使用的数据库
  2. pretend  转储将要运行的SQL查询

执行文件Hyperf\Database\Commands\Migrations\ResetCommand,运行Hyperf\Database\Migrations\Migrator::reset()。

Migrator::reset()和Migrator::rollback()不同,获取批次号从大到小排列的全部数据。便利数据时运行down()。但是没有管理迁移记录表数据的删除操作。

3.3 回滚并迁移

php bin/hyperf.php migrate:refresh

选项:

  1. database 设置链接使用的数据库
  2. pretend  转储将要运行的SQL查询

  3. force 在生产环境中强制运行该操作

  4. path 路径

  5. realpath 绝对路径

  6. seed 指示是否应该重新运行种子任务

  7. seeder 设置种子的类名

  8. step 执行步数

执行文件Hyperf\Database\Commands\Migrations\RefreshCommand。step大于0执行migrate:rollback,否则执行migrate:reset,最后执行migrate。设置seed或seeder后执行db:seed。

选项的使用参数其他命令。其设置的选项也是在其他命令中运行。其本身没有其他逻辑。

3.4 重建数据库

php bin/hyperf.php migrate:fresh

选项:

  1. database 设置链接使用的数据库
  2. drop-views 删除所有视图

  3. path  路径

  4. realpath 绝对路径

  5. force 在生产环境中强制运行该操作

  6. step 执行步数

  7. seed 指示是否应该重新运行种子任务

  8. seeder 设置种子的类名

执行文件Hyperf\Database\Commands\Migrations\FreshCommand。

drop-views为true则删除视图,以myql驱动为例,执行Hyperf\Database\Schema\MySqlBuilder::dropAllViews(),MySqlBuilder::dropAllViews()调用Hyperf\Database\Schema\Grammars\MySqlGrammar::compileDropAllViews()返回的sql字符串,在Hyperf\Database\MySqlConnection的父类Hyperf\Database\Connection::statement()中执行。

四 测试

show databases;
use test1;
show tables;

 运行结果

#show databases;
test
test1
#show tables;
null

 证明test1数据库中无表。

数据库链接配置文件

#config\autoload\databases.php

declare (strict_types = 1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
return [
    'default' => [
        'driver' => env('DB_DRIVER', 'mysql'),
        'host' => env('DB_HOST', '127.0.0.1'),
        'database' => env('DB_DATABASE', 'test'),
        'port' => env('DB_PORT', 3306),
        'username' => env('DB_USERNAME', 'root'),
        'password' => env('DB_PASSWORD', 'root'),
        'charset' => env('DB_CHARSET', 'utf8'),
        'collation' => env('DB_COLLATION', 'utf8_unicode_ci'),
        'prefix' => env('DB_PREFIX', ''),
    ],
    'default2' => [
        'driver' => env('DB_DRIVER', 'mysql'),
        'host' => '127.0.0.1',
        'database' => 'test1',
        'port' => env('DB_PORT', 3306),
        'username' => env('DB_USERNAME', 'root'),
        'password' => env('DB_PASSWORD', 'root'),
        'charset' => env('DB_CHARSET', 'utf8'),
        'collation' => env('DB_COLLATION', 'utf8_unicode_ci'),
        'prefix' => env('DB_PREFIX', ''),
    ],
];

使用env文件会先取env文件的值,所以在env有值的情况下设置的自定值无效。即env("DB_DATABASE",'test')和env("DB_DATABASE",'test1')值相同,都是env文件DB_DATABASE的值。

执行命令

php bin/hyperf.php gen:migration create_userinfo_table

 生成文件migrations\2024_03_02_034344_create_userinfo_table.php

 php bin/hyperf.php migrate --database=default2 --pretend
#show tables;
migrations
userinfo
#show CREATE table migrations;
CREATE TABLE `migrations` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `batch` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

此时俩表都没有数据。添加字段之后,应该也是仅适合表结构迁移。

表数据迁移可以使用工具或者sql文件执行。

为测试回滚等操作,生成新的迁移文件。

php bin/hyperf.php gen:migration create_userinfo2_table --table=userinfo
php bin/hyperf.php gen:migration create_userinfo3_table --create=userinfo

 在对应表迁移文件已有的基础上,检查类名已存在会报错。判断类名已存之前未判断--create和--table参数,所以设置这俩不会解决报错,除非换路径或把想同的类名改掉。

使用table生成的文件如下

class CreateUserinfo2Table extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('userinfo', function (Blueprint $table) {
            //
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('userinfo', function (Blueprint $table) {
            //
        });
    }
}

使用create生成文件如下

class CreateUserinfo3Table extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('userinfo', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('userinfo');
    }
}

 migrations表没数据,回滚会报错"Nothing to rollback."。所以需要看下migrations表何时写入数据。经过查询发现,应该是pretend选项导致。源码执行up()之后,判断pretend参数,为true则执行Hyperf\Database\Migrations\Migrator::pretendToRun(),然后return。pretend默认为false,最后执行Hyperf\Database\Migrations\DatabaseMigrationRepository::log(),写入migrations表。

删表重来一次。

DROP TABLE migrations;
DROP TABLE userinfo;
php bin/hyperf.php migrate --database=default2

 会在test1数据库没有userinfo表存在的情况下,报数据表userinfo重复,因为test数据库有userinfo。这个报错还是执行create后报的错,应为create执行在test数据库中。

但是加选项pretend执行Hyperf\Database\Migrations\Migrator::pretendToRun(),就可以正常执行。

由于测试环境为,一个项目使用两个数据库连接配置链接本地两个库,而不使用pretend的情况下迁移文件默认使用default。即使用同一个项目环境迁移数据库必须使用prepend,但是迁移后的库就记录不了迁移记录。

可以稍微改下

#Hyperf\Database\Migrations\Migrator

protected function runMigration(object $migration, string $method): void {
    ……
    $this->resolver->setDefaultConnection($migration->getConnection() ?: $this->connection);
    ……
}

#改为
protected function runMigration(object $migration, string $method): void {
    ……
    $this->resolver->setDefaultConnection($this->connection ?: $migration->getConnection());
    ……
}

归结原因是按照之前逻辑获取迁移文件对象的getConnection()方法,因为是新对象获取的就是default。所以应该改为先判断当前对象的connect属性是否有值,没有则采用新对象的默认值。

执行

 php bin/hyperf.php migrate --database=default2

成功

Migrating: 2024_03_06_082623_create_userinfo_table
Migrated:  2024_03_06_082623_create_userinfo_table

此时migrations表中数据正常。

select * from migrations

 

执行回滚

php bin/hyperf.php migrate:rollback --database=default2

成功

Rolling back: 2024_03_06_082623_create_userinfo_table
Rolled back:  2024_03_06_082623_create_userinfo_table

此时 migragtions没有数据,test1库中没有userinfo表。

五 源码修改内容

#Hyperf\Database\Migrations\Migrator

protected function runMigration(object $migration, string $method): void {
    ……
    $this->resolver->setDefaultConnection($migration->getConnection() ?: $this->connection);
    ……
}

#改为
protected function runMigration(object $migration, string $method): void {
    ……
    $this->resolver->setDefaultConnection($this->connection ?: $migration->getConnection());
    ……
}

 修改原因:执行时使用新对象默认的数据库链接(default),不能使用自定义的数据库链接。

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lsswear

感谢大佬打赏 q(≧▽≦q)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值