教程:Hyperf
版本说明
一 生成迁移
php bin/hyperf.php gen:migration create_users_table
执行文件:Hyperf\Database\Commands\Migrations\GenMigrateCommand
功能:创建迁移文件
参数:
- name 文件名称
选项:
-
create 创建。设置create未设置table,则create的值设置给table,其值重新赋值为true。
-
table 表名。table未设置,则根据参数name文件名正则出表名。
-
path 路径
-
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
没有参数,只有选项。
选项:
-
database 设置链接使用的数据库
- seed 指示是否应该重新运行种子任务
-
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
选项:
- database 设置链接使用的数据库
-
pretend 转储将要运行的SQL查询
-
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
选项:
- database 设置链接使用的数据库
-
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
选项:
- database 设置链接使用的数据库
-
pretend 转储将要运行的SQL查询
-
force 在生产环境中强制运行该操作
-
path 路径
-
realpath 绝对路径
-
seed 指示是否应该重新运行种子任务
-
seeder 设置种子的类名
-
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
选项:
- database 设置链接使用的数据库
-
drop-views 删除所有视图
-
path 路径
-
realpath 绝对路径
-
force 在生产环境中强制运行该操作
-
step 执行步数
-
seed 指示是否应该重新运行种子任务
-
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),不能使用自定义的数据库链接。