教程:Hyperf
根据之前写的数据迁移的文章,已经说明Hyperf\Database\Schema\Schema
::create()实际运行Hyperf\Database\Schema\Grammars\MySqlGrammar::compileCreate()生成的sql字符串。
文档所谓"在迁移文件中主要通过 Hyperf\Database\Schema\Schema
类来定义数据表和管理迁移流程。",就是使用Schema
来执行具体的迁移sql。生成迁移sql的内容由回调设置。
一 数据表
1.1 创建数据表
使用Hyperf\Database\Schema\Blueprint类对象设置表结构。
属性 | 解释 | 类型 |
engine | 指定表存储引擎 | string |
charset | 指定表默认字符集 | string |
collation | 指定表默认排序规则 | string |
temporary | 指定是否为临时表 | boolen |
after | 在该列之后添加新列 | string |
Blueprint::temporary()设置temporary为true,设置为临时表。
1.2 重命名数据表
可以使用 $table->rename($to),或者Schema::rename($from,$to)。
1.2.1 $table->rename($to)
Blueprint::rename($to)重命名表名,$to为重新定义的表名。
执行的是Blueprint::rename(),其将执行内容设置为Hyperf\Utils\Fluent类的name属性,并将其放到数组commands中,之后获取sql时调用Blueprint::toSql()。
Blueprint::toSql()遍历数组commands,执行以compile开头的Hyperf\Database\Schema\Grammars\Grammar或其子类的对应方法。
1.2.2 Schema::rename($from, $to)
Hyperf\Database\Schema\Schema::__callStatic()获取对应驱动的Connection子类,再获取Builder或其子类执行对应方法。
以musql驱动为例,$table->rename($to)执行Hyperf\Database\Schema\Blueprint::rename($to),Schema::rename($from,$to)通过子类Hyperf\Database\Schema\MySqlBuilder执行Hyperf\Database\Schema\Builder::rename($from,$to)。
Builder::rename($from,$to)中调用Builder::createBlueprint(),返回Hyperf\Database\Schema\Blueprint类对象。即Schema::rename($from,$to)其实最后也是执行Blueprint类方法。
使用Blueprint需要在回调当中使用。根据源码Schema::table()中可以设置回调。
注:在重命名表之前,应判断该表是否为其他表外键。
1.3 删除数据表
在删除表之前应判断表是否存在。
1.3.1 删除表
使用Schema::drop($table),或者$table::drop()。
根据以上逻辑,$table::drop()执行Hyperf\Database\Schema\Blueprint::drop(),Schema::drop($table)执行Hyperf\Database\Schema\Builder::drop($table)。
Builder::drop($table)中也调用Builder::createBlueprint()。
1.3.2 判断表是否存在
使用Schema::dropIfExists($table),或者$table::dropIfExists()。
根据以上逻辑,$table::drop()执行Hyperf\Database\Schema\Blueprint::dropIfExists(),Schema::drop($table)执行Hyperf\Database\Schema\Builder::dropIfExists($table)。
Builder::dropIfExists($table)中也调用Builder::createBlueprint()。
1.3.3 检查表是否存在
使用Schema::hasTable($table),执行Hyperf\Database\Schema\Builder::hasTable($table)。
Schema\Builder::hasTable($table)直接执行Hyperf\Database\Schema\Grammars\Grammar或其子类compileTableExists()返回的sql字符串。
1.3.4 检查字段是否存在
使用Schema::hasColumn($table, $column),执行Hyperf\Database\Schema\Builder::hasColumn($table, $column)。也是直接执行查询出列名,再用in_array()做判断。
1.4 数据库链接选项
设置connection属性,其值为数据库配置文件中数组最外层key值。
为多个数据库连接设置对应配置……这样根据之前文章的修改内容就不用改了……
这里的内容,若迁移使用不同数据库配置,而且不修改源码的情况下,需要在前文件中设置连接。
这样确实安全点……但是执行迁移时,参数中--database会无效,而且还不能回滚,除非设置成和命令一样的表。
/(ㄒoㄒ)/~~ 还是一次性看完比较好,当时就想到会有这样的设计,但是感觉不太合理。
二 字段
2.1 创建字段
可用字段定义方法
$table->bigIncrements('id'); | 递增 ID(主键),相当于「UNSIGNED BIG INTEGER」 |
$table->bigInteger('votes'); | 相当于 BIGINT |
$table->binary('data'); | 相当于 BLOB |
$table->boolean('confirmed'); | 相当于 BOOLEAN |
$table->char('name', 100); | 相当于带有长度的 CHAR |
$table->date('created_at'); | 相当于 DATE |
$table->dateTime('created_at'); | 相当于 DATETIME |
$table->dateTimeTz('created_at'); | 相当于带时区 DATETIME |
$table->decimal('amount', 8, 2); | 相当于带有精度与基数 DECIMAL |
$table->double('amount', 8, 2); | 相当于带有精度与基数 DOUBLE |
$table->enum('level', ['easy', 'hard']); | 相当于 ENUM |
$table->float('amount', 8, 2); | 相当于带有精度与基数 FLOAT |
$table->geometry('positions'); | 相当于 GEOMETRY |
$table->geometryCollection('positions'); | 相当于 GEOMETRYCOLLECTION |
$table->increments('id'); | 递增的 ID (主键),相当于「UNSIGNED INTEGER」 |
$table->integer('votes'); | 相当于 INTEGER |
$table->ipAddress('visitor'); | 相当于 IP 地址 |
$table->json('options'); | 相当于 JSON |
$table->jsonb('options'); | 相当于 JSONB |
$table->lineString('positions'); | 相当于 LINESTRING |
$table->longText('description'); | 相当于 LONGTEXT |
$table->macAddress('device'); | 相当于 MAC 地址 |
$table->mediumIncrements('id'); | 递增 ID (主键) ,相当于「UNSIGNED MEDIUM INTEGER」 |
$table->mediumInteger('votes'); | 相当于 MEDIUMINT |
$table->mediumText('description'); | 相当于 MEDIUMTEXT |
$table->morphs('taggable'); | 相当于加入递增的 taggable_id 与字符串 taggable_type |
$table->multiLineString('positions'); | 相当于 MULTILINESTRING |
$table->multiPoint('positions'); | 相当于 MULTIPOINT |
$table->multiPolygon('positions'); | 相当于 MULTIPOLYGON |
$table->nullableMorphs('taggable'); | 相当于可空版本的 morphs() 字段 |
$table->nullableTimestamps(); | 相当于可空版本的 timestamps() 字段 |
$table->point('position'); | 相当于 POINT |
$table->polygon('positions'); | 相当于 POLYGON |
$table->rememberToken(); | 相当于可空版本的 VARCHAR (100) 的 remember_token 字段 |
$table->smallIncrements('id'); | 递增 ID (主键) ,相当于「UNSIGNED SMALL INTEGER」 |
$table->smallInteger('votes'); | 相当于 SMALLINT |
$table->softDeletes(); | 相当于为软删除添加一个可空的 deleted_at 字段 |
$table->softDeletesTz(); | 相当于为软删除添加一个可空的 带时区的 deleted_at 字段 |
$table->string('name', 100); | 相当于带长度的 VARCHAR |
$table->text('description'); | 相当于 TEXT |
$table->time('sunrise'); | 相当于 TIME |
$table->timeTz('sunrise'); | 相当于带时区的 TIME |
$table->timestamp('added_on'); | 相当于 TIMESTAMP |
$table->timestampTz('added_on'); | 相当于带时区的 TIMESTAMP |
$table->timestamps(); | 相当于可空的 created_at 和 updated_at TIMESTAMP |
$table->timestampsTz(); | 相当于可空且带时区的 created_at 和 updated_at TIMESTAMP |
$table->tinyIncrements('id'); | 相当于自动递增 UNSIGNED TINYINT |
$table->tinyInteger('votes'); | 相当于 TINYINT |
$table->unsignedBigInteger('votes'); | 相当于 Unsigned BIGINT |
$table->unsignedDecimal('amount', 8, 2); | 相当于带有精度和基数的 UNSIGNED DECIMAL |
$table->unsignedInteger('votes'); | 相当于 Unsigned INT |
$table->unsignedMediumInteger('votes'); | 相当于 Unsigned MEDIUMINT |
$table->unsignedSmallInteger('votes'); | 相当于 Unsigned SMALLINT |
$table->unsignedTinyInteger('votes'); | 相当于 Unsigned TINYINT |
$table->uuid('id'); | 相当于 UUID |
$table->year('birth_year'); | 相当于 YEAR |
$table->comment('Table Comment'); | 设置表注释,相当于 COMMENT |
大概运行都是Grammar或其子类,简称为Grammar类。运行Grammar类方法compileAdd(),Grammar::compileAdd()中运行Grammar::getType(),调用以type开头的Grammar类对应方法,用于返回sql字符串。
2.2 修改字段
composer require "doctrine/dbal:^3.0"
根据文档
Schema::table('users', function (Blueprint $table) {
// 将字段的长度修改为 50 并允许为空
$table->string('name', 50)->nullable()->change();
});
$table->string()返回\Hyperf\Database\Schema\ColumnDefinition类对象。ColumnDefinition父类Hyperf\Utils\Fluent::__call()实际执行nullable()和change()。
Fluent::__call()返回$this,所以用“->”继续调用。其仅设置以方法名设置的属性值。
Hyperf\Database\Schema\Blueprint::toSql()时使用对应属性,具体执行Hyperf\Database\Schema\Grammars\Grammar::compileChange(),最终执行Doctrine\DBAL\Platforms\AbstractMySQLPlatform::getAlterTableSQL()。
所以必须安装doctrine/dbal。
只有下面的字段类型能被 "修改": bigInteger、 binary、 boolean、date、dateTime、dateTimeTz、decimal、integer、json、 longText、mediumText、smallInteger、string、text、time、 unsignedBigInteger、unsignedInteger and unsignedSmallInteger。
2.3 重命名字段
$table->renameColumn('from', 'to')->change();
执行Hyperf\Database\Schema\Grammars\Grammar::compileRenameColumn(),然后执行Hyperf\Database\Schema\Grammars\RenameColumn::compile(),最后也是执行Doctrine\DBAL\Platforms\AbstractMySQLPlatform::getAlterTableSQL()。
2.4 删除字段
$table->dropColumn('name');
$table->dropColumn(['name', 'age']);
根据以上逻辑执行Hyperf\Database\Schema\Grammars\Grammar:compileDropColumn(),直接生成sql字符串。
三 测试
3.1 表
测试流程
- 创建表
- 重命名表
- 删除表
3.1.1 创建表
use test1;
show tables;
php bin/hyperf gen:migration create_test_table
#migrations\2024_03_06_082623_create_userinfo_table.php
class CreateUserinfoTable extends Migration {
protected $connection = 'default2';
/**
* 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\2024_03_11_070103_create_test_table.php
class CreateTestTable extends Migration {
protected $connection = 'default2';
/**
* Run the migrations.
*/
public function up(): void {
Schema::create('test', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->engine = 'MyISAM';
// 指定数据表的默认字符集
$table->charset = 'utf8mb4';
// 指定数据表默认的排序规则
$table->collation = 'utf8mb4_unicode_ci';
// 创建临时表
$table->temporary();
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::dropIfExists('test');
}
}
php bin/hyperf.php migrate --database=default2
Migrating: 2024_03_06_082623_create_userinfo_table
Migrated: 2024_03_06_082623_create_userinfo_table
Migrating: 2024_03_11_070103_create_test_table
Migrated: 2024_03_11_070103_create_test_table
migration中有test表的记录,但是实际库中没有test表。可能和设置为临时表有关。
经查询mysql临时表仅在当前会话(session)中有效,会话结束时自动消失。
所以把临时表去掉再试下,但是记录未删除的话不会执行。
再次执行,从数据库表看,执行正常。
3.1.2 重命名表
php bin/hyperf.php gen:migration update_test_table --table=test
[INFO] Created Migration: 2024_03_11_074311_update_test_table
#migrations\2024_03_11_074311_update_test_table.php
class UpdateTestTable extends Migration {
protected $connection = 'default2';
/**
* Run the migrations.
*/
public function up(): void {
Schema::table('test', function (Blueprint $table) {
$table->rename("test1");
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::table('test', function (Blueprint $table) {
//
});
}
}
php bin/hyperf.php migrate --database=default2
Migrating: 2024_03_11_074311_update_test_table
Migrated: 2024_03_11_074311_update_test_table
3.1.3 删除表
php bin/hyperf.php gen:migration update_test2_table --table=test
[INFO] Created Migration:2024_03_11_075322_update_test2_table.php
因为down函数仅在回滚时执行,所以在up()中设置表删除。
#migrations\2024_03_11_075322_update_test2_table.php
class UpdateTest2Table extends Migration {
protected $connection = 'default2';
/**
* Run the migrations.
*/
public function up(): void {
Schema::table('test1', function (Blueprint $table) {
$table->dropIfExists();
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::table('test1', function (Blueprint $table) {
//
});
}
}
php bin/hyperf.php migrate --database=default2
Migrating: 2024_03_11_075322_update_test2_table
Migrated: 2024_03_11_075322_update_test2_table
3.2 字段
测试流程
- 设置字段
- 修改字段
- 删除字段
3.2.1 设置字段
php bin/hyperf.php gen:migration update_userinfo_table --table=userinfo
[INFO] Created Migration: 2024_03_11_080602_update_userinfo_table
#migrations\2024_03_11_080602_update_userinfo_table.php
class UpdateUserinfoTable extends Migration {
protected $connection = 'default2';
/**
* Run the migrations.
*/
public function up(): void {
Schema::table('userinfo', function (Blueprint $table) {
$table->string('name', 255);
$table->tinyInteger('age');
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::table('userinfo', function (Blueprint $table) {
//
});
}
}
php bin/hyperf.php migrate --database=default2
Migrating: 2024_03_11_080602_update_userinfo_table
Migrated: 2024_03_11_080602_update_userinfo_table
DESCRIBE userinfo;
3.2.2 修改字段
php bin/hyperf.php gen:migration update_userinfo2_table --table=userinfo
[INFO] Created Migration: 2024_03_11_081819_update_userinfo2_table
#migrations\2024_03_11_081819_update_userinfo2_table.php
class UpdateUserinfo2Table extends Migration {
protected $connection = 'default2';
/**
* Run the migrations.
*/
public function up(): void {
Schema::table('userinfo', function (Blueprint $table) {
$table->string("name")->nullable()->change();
$table->renameColumn('age', 'age1')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::table('userinfo', function (Blueprint $table) {
//
});
}
}
php bin/hyperf.php migrate --database=default2
Migrating: 2024_03_11_081819_update_userinfo2_table
Migrated: 2024_03_11_081819_update_userinfo2_table
DESCRIBE userinfo;
执行回滚,然会修改文件再次执行。
php bin/hyperf.php migrate:rollback --database=default2
Rolling back: 2024_03_11_081819_update_userinfo2_table
Rolled back: 2024_03_11_081819_update_userinfo2_table
但是数据库字段没变……因为down里没设置语句……
#migrations\2024_03_11_081819_update_userinfo2_table.php
class UpdateUserinfo2Table extends Migration {
protected $connection = 'default2';
/**
* Run the migrations.
*/
public function up(): void {
Schema::table('userinfo', function (Blueprint $table) {
$table->string("name")->nullable()->change();
//$table->tinyInteger('age')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::table('userinfo', function (Blueprint $table) {
$table->string("name")->change();
$table->tinyInteger('age')->change();
});
}
}
经过手动改动后,此时数据表结构:
测试时用$table->tinyInteger('age')测试修改,确实报错PHP Fatal error: Uncaught Doctrine\DBAL\Exception: Unknown column type "tinyinteger" requested. Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType().
因为这种字段类型不能修改。所以将代码改为上面图中内容,再次执行。
php bin/hyperf.php migrate --database=default2
Migrating: 2024_03_11_081819_update_userinfo2_table
Migrated: 2024_03_11_081819_update_userinfo2_table
测试发现写notnull无效,因为改前和改后表差异对比时返回false,但是Hyperf\Utils\Fluent属性notnull确实设置为true。
对应源码
#Hyperf\Database\Schema\Grammars\ChangeColumn
public static function compile($grammar, Blueprint $blueprint, Fluent $command, Connection $connection) {
if (!$connection->isDoctrineAvailable()) {
throw new RuntimeException(sprintf(
'Changing columns for table "%s" requires Doctrine DBAL; install "doctrine/dbal".',
$blueprint->getTable()
));
}
$tableDiff = static::getChangedDiff(
$grammar,
$blueprint,
$schema = $connection->getDoctrineSchemaManager()
);
if ($tableDiff !== false) {
return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff);
}
return [];
}
#$tableDiff 为false
#Hyperf\Database\Schema\Grammars\ChangeColumn
protected static function getTableWithColumnChanges(Blueprint $blueprint, Table $table) {
$table = clone $table;
foreach ($blueprint->getChangedColumns() as $fluent) {
$column = static::getDoctrineColumn($table, $fluent);
// Here we will spin through each fluent column definition and map it to the proper
// Doctrine column definitions - which is necessary because Laravel and Doctrine
// use some different terminology for various column attributes on the tables.
foreach ($fluent->getAttributes() as $key => $value) {
if (!is_null($option = static::mapFluentOptionToDoctrine($key))) {
if (method_exists($column, $method = 'set' . ucfirst($option))) {
$column->{$method}(static::mapFluentValueToDoctrine($option, $value));
}
}
}
}
return $table;
}
protected static function getTableWithColumnChanges(Blueprint $blueprint, Table $table) {
$table = clone $table;
foreach ($blueprint->getChangedColumns() as $fluent) {
$column = static::getDoctrineColumn($table, $fluent);
// Here we will spin through each fluent column definition and map it to the proper
// Doctrine column definitions - which is necessary because Laravel and Doctrine
// use some different terminology for various column attributes on the tables.
foreach ($fluent->getAttributes() as $key => $value) {
if (!is_null($option = static::mapFluentOptionToDoctrine($key))) {
if (method_exists($column, $method = 'set' . ucfirst($option))) {
$column->{$method}(static::mapFluentValueToDoctrine($option, $value));
}
}
}
}
return $table;
}
#column
/***
object(Doctrine\DBAL\Schema\Column)#901 (16) {
["_type":protected]=>
object(Doctrine\DBAL\Types\StringType)#884 (0) {
}
["_length":protected]=>
int(255)
["_precision":protected]=>
int(10)
["_scale":protected]=>
int(0)
["_unsigned":protected]=>
bool(false)
["_fixed":protected]=>
bool(false)
["_notnull":protected]=>
bool(false)
["_default":protected]=>
NULL
["_autoincrement":protected]=>
bool(false)
["_platformOptions":protected]=>
array(2) {
["charset"]=>
string(7) "utf8mb4"
["collation"]=>
string(18) "utf8mb4_unicode_ci"
}
["_columnDefinition":protected]=>
NULL
["_comment":protected]=>
NULL
["_customSchemaOptions":protected]=>
array(0) {
}
["_name":protected]=>
string(4) "name"
["_namespace":protected]=>
NULL
["_quoted":protected]=>
bool(false)
}
***/
#$fluent
/***
array(5) {
["type"]=>
string(6) "string"
["name"]=>
string(4) "name"
["length"]=>
int(255)
["notnull"]=>
bool(true)
["change"]=>
bool(true)
}
***/
#Hyperf\Database\Schema\Grammars\ChangeColumn
protected static function mapFluentOptionToDoctrine($attribute) {
switch ($attribute) {
case 'type':
case 'name':
return;
case 'nullable':
return 'notnull';
case 'total':
return 'precision';
case 'places':
return 'scale';
default:
return $attribute;
}
}
#Hyperf\Utils\Fluent
public function __call($method, $parameters)
{
$this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true;
return $this;
}
#Doctrine\DBAL\Schema\Column
/** @return mixed[] */
public function toArray()
{
return array_merge([
'name' => $this->_name,
'type' => $this->_type,
'default' => $this->_default,
'notnull' => $this->_notnull,
'length' => $this->_length,
'precision' => $this->_precision,
'scale' => $this->_scale,
'fixed' => $this->_fixed,
'unsigned' => $this->_unsigned,
'autoincrement' => $this->_autoincrement,
'columnDefinition' => $this->_columnDefinition,
'comment' => $this->_comment,
], $this->_platformOptions, $this->_customSchemaOptions);
}
#Doctrine\DBAL\Platforms\DB2Platform
private function getAlterColumnClausesSQL(ColumnDiff $columnDiff): array
{
$newColumn = $columnDiff->getNewColumn()->toArray();
$alterClause = 'ALTER COLUMN ' . $columnDiff->getNewColumn()->getQuotedName($this);
if ($newColumn['columnDefinition'] !== null) {
return [$alterClause . ' ' . $newColumn['columnDefinition']];
}
……
}
根据源码意思,应该是看Doctrine\DBAL\Schema\Column类中是否有对应的set方法。果然Column类中没有setNotnull(),仅有setNullable()。所以能用的就是Column类中以set开头的方法。
方法 | 调用 | 解释 |
Column::setType() | type() | 字段类型 |
Column::setName | name() | 字段名 |
Column::setLength() | length() | 字段长度 |
Column::setPrecision() | total()或者precision() | 字段浮点精度 |
setScale() | places()或者scale() | 小数点后位数 |
setUnsigned() | unsigned() | 无符号 |
setFixed() | fixed() | 双精度浮点 |
setNotnull() | notnull() | 设置是否不为空 |
setDefault() | default() | 设置默认值 |
setPlatformOptions() | platformOptions() | 批量设置其他属性 |
setPlatformOption() | platformOption() | 单个设置其他属性 |
setColumnDefinition() | columnDefinition() | 大概是直接定义的语句 |
应该notnull这样的属性没被处理。 结合属性的定义,可以尝试传参数。比如nullable(false)。
执行代码改为
#migrations\2024_03_11_081819_update_userinfo2_table.php
class UpdateUserinfo2Table extends Migration {
protected $connection = 'default2';
/**
* Run the migrations.
*/
public function up(): void {
Schema::table('userinfo', function (Blueprint $table) {
$table->string("name")->nullable(false)->change();
//$table->tinyInteger('age')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::table('userinfo', function (Blueprint $table) {
$table->string("name")->change();
$table->tinyInteger('age')->change();
});
}
}
php bin/hyperf.php migrate --database=default2
Migrating: 2024_03_11_081819_update_userinfo2_table
Migrated: 2024_03_11_081819_update_userinfo2_table
修改成功
3.2.3 删除字段
php bin/hyperf.php gen:migration update_userinfo3_table --table=userinfo
[INFO] Created Migration: 2024_03_11_094726_update_userinfo3_table
#migrations\2024_03_11_094726_update_userinfo3_table.php
class UpdateUserinfo3Table extends Migration {
protected $connection = 'default2';
/**
* Run the migrations.
*/
public function up(): void {
Schema::table('userinfo', function (Blueprint $table) {
$table->dropColumn(['name', 'age']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void {
Schema::table('userinfo', function (Blueprint $table) {
//
});
}
}
php bin/hyperf.php migrate --database=default2
Migrating: 2024_03_11_094726_update_userinfo3_table
Migrated: 2024_03_11_094726_update_userinfo3_table
DESCRIBE userinfo