说明
学习Yii Framework 2(易2框架)的过程是漫长的,也是充满乐趣的,以下是我学习Yii2框架时对官网英文资料(请参见原文网址)的翻译和代码实现,提供了较完整的代码,供你参考。不妥之处,请多多指正!
原文网址:
http://www.yiiframework.com/doc-2.0/guide-db-migrations.html
http://www.yiiframework.com/doc-2.0/guide-db-migrations.html#creating-migrations
1、Creating Migrations(创建迁移)
http://www.yiiframework.com/doc-2.0/guide-db-migrations.html#generating-migrations
2、Generating Migrations(生成迁移)
本文主题:为数据库创建迁移和生成迁移(migration)
在开发和维护数据库驱动的应用过程中,数据库的结构与源代码的开发同步更新。例如,在应用开发的过程中,新建了一张表,在应用部署到生产环境后,发现需要为这张表创建一个索引以提升查询性能,等等。因为数据库结构改变后需要源代码随之而改变,Yii支持此类数据库迁移特征,这样你就可以用数据库迁移的形式追踪数据库的变化,也就是与源代码同步的版本控制。
以下步骤展示了在团队开发过程中如何使用数据库迁移:
1、Tim创建了一个新的迁移(例如,创建了一张新表,更改了一个字段定义等)
2、Tim提交了新迁移到源代码控制系统中(如Git,Mercurial)
3、Doug从源码控制系统中更新了他的仓库,并接收了新的迁移
4、Doug应用新迁移到他本地的开发数据库,也就是同步了Tim所做的数据库变更到他自己的数据库中。
以下步骤展示了如何部署一个新的数据库迁移版本到生产环境:
1、Scott为项目仓库的新数据库迁移创建了一个版本标签。
2、Scott在生产服务器上更新源码到新版本标签。
3、Scott应用所累积的数据库迁移到生产数据库中。
Yii提供了一组迁移命令工具让你可以:
1、创建新迁移
2、应用迁移
3、恢复迁移
4、再次应用迁移
5、显示迁移历史和状态
所有这些工具都可以通过命令yii migrate来使用,本章节我们将描述使用这些工具完成各项任务的细节,你可以使用帮助命令yii help migrate获取每个工具的用法。
小贴士:迁移不仅影响到数据库的schema,还将调整原有数据以适应新迁移,创建RBAC分层结构或者清除缓存。
1、创建迁移(Creating Migrations)
//table//migrate
要创建一个新的迁移,运行以下命令:
yii migrate/create
必填参数name对新迁移进行了概述,例如,如果本次迁移是创建了一个新表news,你可以使用名称create_news_table,并运行以下命令:
D:\phpwork\basic>yii migrate/create create_news_table
Yii Migration Tool (based on Yii v2.0.8)
Create new migration 'D:\phpwork\basic/migrations\m170313_082453_create_news_tab
le.php'? (yes|no) [no]:y
New migration created successfully.
D:\phpwork\basic>
注意:因为name参数将被用于创建迁移类的名称,所以它只能包含字母,数字和下划线。
以上命令将会在@app/migrations目录下创建一个新的名为m150101_185401_create_news_table.php的PHP类文件。此文件主要是定义了一个迁移类m150101_185401_create_news_table框架代码,代码如下:
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration{
public function up(){
}
public function down(){
echo "m101129_185401_create_news_table cannot be reverted.\n";
return false;
}
/*
// Use safeUp/safeDown to run migration code within a transaction
public function safeUp()
{
}
public function safeDown()
{
}
*/
}
每一个数据库迁移被定义为一个继承自yii\db\Migration的PHP类,这个迁移类名称是自动生成的,其格式为:m_,这里:
,指迁移创建命令运行时的UTC时间
,是你在运行命令时提供的name参数值
在迁移类中,你需要在up()方法中编写代码,以明确在数据库结构上做出哪些改变。你可能也想要在down()方法中编写代码,以恢复那些被up()做过的修改。当你做此迁移升级数据库时,up()方法将被调用;当需要数据库降回去时,将调用down()方法。以下代码展示了,创建news表时,你需要去完成的迁移类:
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration{
public function up(){
$this->createTable('news', [
'id' => Schema::TYPE_PK,
'title' => Schema::TYPE_STRING . ' NOT NULL',
'content' => Schema::TYPE_TEXT,
]);
}
public function down(){
$this->dropTable('news');
}
}
信息:不是所有的迁移都可以恢复的。例如,如果在up()方法中删除了表中的一行记录,你可能无法在down()方法中恢复此行记录。有时,你可以不必太关注实现down()方法,因为通常并不需要恢复数据库迁移。在这种情况下,你可以在down()方法中返回false,以明确告知此迁移不支持恢复。
基础迁移类yii\db\Migration定义了一个使用db属性的数据库连接,你可以使用它操作数据库shcema,这些方法在Working with Database Schema章节中已描述:
http://www.yiiframework.com/doc-2.0/guide-db-dao.html#database-schema
创建一个表或列时,与使用物理类型相比,更好的方式是使用抽象类型,这样你的迁移不会依赖于特定的DBMS。yii\db\Schema类定义了一组常量来表示其所支持的抽象类型。这些常量格式为TYPE_。例如,TYPE_PK指自增长的主键类型;TYPE_STRING指字符串类型。当一个迁移应用到一个特定的数据库时,抽象类型会被转换为对应的物理类型。当是MySQL时,TYPE_PK将被转换为int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,而TYPE_STRING对应varchar(255)。
当使用抽象类型时,你可以追加额外的常量,NOT NULL被追加到Schema::TYPE_STRING以定义此列不能为空(null)。
信息:抽象类型与物理类型之间的对应关系在每个具体的QueryBuilder类中由$typeMap 属性来定义。
例如:mysql的$typeMap 定义:
D:\phpwork\basic\vendor\yiisoft\yii2\db\mysql\QueryBuilder.php
自2.0.6版起,你可以使用新引入的结构构建器,它提供了定义列结构更为简便的方法。上例所示的迁移就可以改写为:
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration{
public function up(){
$this->createTable('news', [
'id' => $this->primaryKey(),
'title' => $this->string()->notNull(),
'content' => $this->text(),
]);
}
public function down(){
$this->dropTable('news');
}
}
2、生成迁移(Generating Migrations)
自2.0.7版本开始,迁移控制台提供更加方便的方法去创建迁移。
如果迁移名称是特定形式,例如create_xxx_table或drop_xxx_table,那么生成的迁移文件将包含额外的代码,也就是创建或删除表。接下来将描述此特征的各种变化:
//Create Table(创建表)
yii migrate/create create_post_table
//实际使用的是“yii migrate/create create_post”,如下:
D:\phpwork\basic>yii migrate/create create_post
Yii Migration Tool (based on Yii v2.0.8)
Create new migration 'D:\phpwork\basic/migrations\m170314_032934_create_post.php
'? (yes|no) [no]:y
New migration created successfully.
D:\phpwork\basic>
//将生成代码:
/**
* Handles the creation for table `post`.
*/
class m150811_220037_create_post extends Migration{
/**
* @inheritdoc
*/
public function up(){
$this->createTable('post', [
'id' => $this->primaryKey()
]);
}
/**
* @inheritdoc
*/
public function down(){
$this->dropTable('post');
}
}
通过--fields选项可以直接定义表的字段:
D:\phpwork\basic>yii migrate/create create_post --fields="title:string,body:text"
Yii Migration Tool (based on Yii v2.0.8)
Create new migration 'D:\phpwork\basic/migrations\m170314_033234_create_post.php'? (yes|no) [no]:y
New migration created successfully.
D:\phpwork\basic>
//生成的代码:
D:\phpwork\basic\migrations\m170314_033234_create_post.php
use yii\db\Migration;
/**
* Handles the creation for table `post`.
*/
class m170314_033234_create_post extends Migration{
/**
* @inheritdoc
*/
public function up(){
$this->createTable('post', [
'id' => $this->primaryKey(),
'title' => $this->string(),
'body' => $this->text(),
]);
}
/**
* @inheritdoc
*/
public function down(){
$this->dropTable('post');
}
}
//也可以创建更多的字段属性
D:\phpwork\basic>yii migrate/create create_post --fields="title:string(12):notNUll:unique,body:text"
Yii Migration Tool (based on Yii v2.0.8)
Create new migration 'D:\phpwork\basic/migrations\m170314_033848_create_post.php'? (yes|no) [no]:y
New migration created successfully.
D:\phpwork\basic>
//将生成
D:\phpwork\basic\migrations\m170314_033848_create_post.php
use yii\db\Migration;
/**
* Handles the creation for table `post`.
*/
class m170314_033848_create_post extends Migration{
/**
* @inheritdoc
*/
public function up(){
$this->createTable('post', [
'id' => $this->primaryKey(),
'title' => $this->string(12)->notNUll()->unique(),
'body' => $this->text(),
]);
}
/**
* @inheritdoc
*/
public function down(){
$this->dropTable('post');
}
}
注意:主键是自动被加上的,并默认命名为id,如果想要使用其他名字,你可以定义为 --fields="name:primaryKey"
//Foreign keys(外键)
从2.0.8开始,生成器支持使用foreignKey生成外键:
D:\phpwork\basic>yii migrate/create create_post --fields="author_id:integer:notNull:foreignKey(user),category_id:integer:defaultValue(1):foreignKey,title:string(12):notNUll:unique,body:text"
Yii Migration Tool (based on Yii v2.0.8)
Create new migration 'D:\phpwork\basic/migrations\m170314_035136_create_post.php'? (yes|no) [no]:y
New migration created successfully.
D:\phpwork\basic>
//将生成:
D:\phpwork\basic\migrations\m170314_035136_create_post.php
use yii\db\Migration;
/**
* Handles the creation for table `post`.
* Has foreign keys to the tables:
*
* - `user`
* - `category`
*/
class m170314_035136_create_post extends Migration{
/**
* @inheritdoc
*/
public function up(){
$this->createTable('post', [
'id' => $this->primaryKey(),
'author_id' => $this->integer()->notNull(),
'category_id' => $this->integer()->defaultValue(1),
'title' => $this->string(12)->notNUll()->unique(),
'body' => $this->text(),
]);
// creates index for column `author_id`
$this->createIndex(
'idx-post-author_id',
'post',
'author_id'
);
// add foreign key for table `user`
$this->addForeignKey(
'fk-post-author_id',
'post',
'author_id',
'user',
'id',
'CASCADE'
);
// creates index for column `category_id`
$this->createIndex(
'idx-post-category_id',
'post',
'category_id'
);
// add foreign key for table `category`
$this->addForeignKey(
'fk-post-category_id',
'post',
'category_id',
'category',
'id',
'CASCADE'
);
}
/**
* @inheritdoc
*/
public function down(){
// drops foreign key for table `user`
$this->dropForeignKey(
'fk-post-author_id',
'post'
);
// drops index for column `author_id`
$this->dropIndex(
'idx-post-author_id',
'post'
);
// drops foreign key for table `category`
$this->dropForeignKey(
'fk-post-category_id',
'post'
);
// drops index for column `category_id`
$this->dropIndex(
'idx-post-category_id',
'post'
);
$this->dropTable('post');
}
}
在列描述中foreignKey关键字的位置并不会改变生成的代码,它的意思是:
author_id:integer:notNull:foreignKey(user)
author_id:integer:foreignKey(user):notNull
author_id:foreighKey(user):integer:notNull
将会产生同样的代码。
foreignKey关键字可以在括号中带一个参数,此参数是要生成的外键所关联的表名称,如果没有传递参数,将从列名中推演出表名称。
在上例中auth_id:integer:notNull:foreignKey(user)将生成名为author_id的字段,此字段有一个user表的外键;category_id:integer:defaultValue(1):foreignKey将生成名为category_id的字段,此字段有一个category的外键。
从2.0.11开始,foreignKey关键字可以接收第2个参数,使用空格分隔,它可以指定生成的外键所关联的列名称,如果第2参数被忽略,列名称将从表结构(table schema)中获取,如果表结构没有存在,主键没有设置或是复合主键,将使用默认名称:id。
//Drop Table(删除表)
D:\phpwork\basic>yii migrate/create drop_post --fields="title:string(12):noNull:unique,body:text"
Yii Migration Tool (based on Yii v2.0.8)
Create new migration 'D:\phpwork\basic/migrations\m170314_054507_drop_post.php'? (yes|no) [no]:y
New migration created successfully.
D:\phpwork\basic>
//将生成:
D:\phpwork\basic\migrations\m170314_054507_drop_post.php
use yii\db\Migration;
/**
* Handles the dropping for table `post`.
*/
class m170314_054507_drop_post extends Migration{
/**
* @inheritdoc
*/
public function up(){
$this->dropTable('post');
}
/**
* @inheritdoc
*/
public function down(){
$this->createTable('post', [
'id' => $this->primaryKey(),
'title' => $this->string(12)->noNull()->unique(),
'body' => $this->text(),
]);
}
}
//Add Column(添加列)//column
如果迁移名称样式是add_xxx_column_to_yyy,将在生成的文件中包含addColumn和dropColumn语句。
D:\phpwork\basic>yii migrate/create add_position_column_to_post --fields="position:integer"
Yii Migration Tool (based on Yii v2.0.8)
Create new migration 'D:\phpwork\basic/migrations\m170314_054954_add_position_column_to_post.php'? (yes|no) [no]:y
New migration created successfully.
D:\phpwork\basic>
//将生成:
D:\phpwork\basic\migrations\m170314_054954_add_position_column_to_post.php
use yii\db\Migration;
/**
* Handles adding position_column to table `post`.
*/
class m170314_054954_add_position_column_to_post extends Migration{
/**
* @inheritdoc
*/
public function up(){
$this->addColumn('post', 'position', $this->integer());
}
/**
* @inheritdoc
*/
public function down(){
$this->dropColumn('post', 'position');
}
}
//一次添加多个字段:
D:\phpwork\basic>yii migrate/create add_position_column_to_post --fields="position:integer,click:integer"
//Drop Column(删除列)
如果迁移名称样式是drop_xxx_column_from_yyy,生成的文件将包含dropColumn和addColumn语句。
D:\phpwork\basic>yii migrate/create drop_position_column_from_post --fields="position:integer"
Yii Migration Tool (based on Yii v2.0.8)
Create new migration 'D:\phpwork\basic/migrations\m170314_060321_drop_position_column_from_post.php'? (yes|no) [no]:y
New migration created successfully.
D:\phpwork\basic>
//将生成:
D:\phpwork\basic\migrations\m170314_060321_drop_position_column_from_post.php
use yii\db\Migration;
/**
* Handles dropping position_column from table `post`.
*/
class m170314_060321_drop_position_column_from_post extends Migration{
/**
* @inheritdoc
*/
public function up(){
$this->dropColumn('post', 'position');
}
/**
* @inheritdoc
*/
public function down(){
$this->addColumn('post', 'position', $this->integer());
}
}
//Add Junction Table(添加Junction表)
如果迁移名称样式是create_junction_table_for_xxx_and_yyy_tables或create_junction_xxx_and_yyy_tables,那么创建junction表的代码将会被生成。
D:\phpwork\basic>yii migrate/create create_junction_for_post_and_tag --fields="created_at:dateTime"
Yii Migration Tool (based on Yii v2.0.8)
Create new migration 'D:\phpwork\basic/migrations\m170314_060957_create_junction_for_post_and_tag.php'? (yes|no) [no]:y
New migration created successfully.
D:\phpwork\basic>
//将生成:
D:\phpwork\basic\migrations\m170314_060957_create_junction_for_post_and_tag.php
use yii\db\Migration;
/**
* Handles the creation for table `for_post_tag`.
* Has foreign keys to the tables:
*
* - `for_post`
* - `tag`
*/
class m170314_060957_create_junction_for_post_and_tag extends Migration{
/**
* @inheritdoc
*/
public function up(){
$this->createTable('for_post_tag', [
'for_post_id' => $this->integer(),
'tag_id' => $this->integer(),
'created_at' => $this->dateTime(),
'PRIMARY KEY(for_post_id, tag_id)',
]);
// creates index for column `for_post_id`
$this->createIndex(
'idx-for_post_tag-for_post_id',
'for_post_tag',
'for_post_id'
);
// add foreign key for table `for_post`
$this->addForeignKey(
'fk-for_post_tag-for_post_id',
'for_post_tag',
'for_post_id',
'for_post',
'id',
'CASCADE'
);
// creates index for column `tag_id`
$this->createIndex(
'idx-for_post_tag-tag_id',
'for_post_tag',
'tag_id'
);
// add foreign key for table `tag`
$this->addForeignKey(
'fk-for_post_tag-tag_id',
'for_post_tag',
'tag_id',
'tag',
'id',
'CASCADE'
);
}
/**
* @inheritdoc
*/
public function down(){
// drops foreign key for table `for_post`
$this->dropForeignKey(
'fk-for_post_tag-for_post_id',
'for_post_tag'
);
// drops index for column `for_post_id`
$this->dropIndex(
'idx-for_post_tag-for_post_id',
'for_post_tag'
);
// drops foreign key for table `tag`
$this->dropForeignKey(
'fk-for_post_tag-tag_id',
'for_post_tag'
);
// drops index for column `tag_id`
$this->dropIndex(
'idx-for_post_tag-tag_id',
'for_post_tag'
);
$this->dropTable('for_post_tag');
}
}
自2.0.11版本开始,Junction表的外键列名称从表结构中获取,如果结构中没有定义表,或主键没有设置或是复合主键,将使用默认值:id。
//Transactional Migrations(事务化迁移)
当执行复杂的DB迁移时,确保每次迁移是整体成功或失败是非常重要的,这样使得数据库能够保持完整性和一致性。要实现这个目标,推荐你将每次迁移相关的数据库操作封装到一个事务中去。
一个实现事务化迁移更简单的方法是将迁移代码主在safeUp()和safeDown()方法中,这两个方法不同于up()和down()之处在于,它们是完全封装于事务中的。因此,如果在这些方法中有任何失败,之前的所有操作都将被自动回滚。
在下例中,除了创建一个news表外,我们还插入了一个初始化行记录到这个表中:
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration{
public function safeUp(){
$this->createTable('news', [
'id' => $this->primaryKey(),
'title' => $this->string()->notNull(),
'content' => $this->text(),
]);
$this->insert('news', [
'title' => 'test 1',
'content' => 'content 1',
]);
}
public function safeDown(){
$this->delete('news', ['id' => 1]);
$this->dropTable('news');
}
}
注意通常当你在safeUp()方法中执行多个DB操作时,你应该在safeDown()中掉转它们的执行顺序。在上例中我们首先在safeUp()中创建了表然后插入了一行记录,在safeDown()中我们先删除了行记录然后再删除表。
注意:不是所有的DBMS都支持事务,一些数据查询也不能被放到事务中来,更多实例,请参考隐式提交(implicit commit):
https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html
在此种情况下,你应该使用up()或down()。
//数据库存取方法(Database Accessing Methods)
基础迁移类yii\db\Migration提供了一组从数据库获取和操作的方法。你会发现这些方法的命名与yii\db\Command类提供的DAO方法相似。例如:yii\db\Migration::createTable()方法允许你创建一个新表,与yii\db\Command::createTable()类似。
使用yii\db\Migration提供的方法的好处是,你无需明确创建yii\db\Command实例,每个方法执行时都会自动显示有用的信息,告诉你哪个数据库操作已完成,还需要多长时间。
以下列出了所有的数据库存取操作方法:
execute(),执行一个SQL语句
insert(),插入一行
batchInsert(),插入多行
update(),更新多行记录
delete(),删除多行记录
createTable(),创建一个表
renameTable(),重命名一个表
dropTable(),删除一个表
truncateTable(),删除表中的所有记录
addColumn(),添加一列
renameColumn(),重命名列
dropColumn(),删除一列
alterColumn(),修改列关系(外键之类的)
addPrimaryKey(),添加一个主键
dropPrimaryKey(),删除一个主键
addForeignKey(),添加一个外键
dropForeignKey(),删除一个外键
createIndex(),创建一个索引
dropIndex(),删除一个索引
addCommentOnColumn(),为列添加注释
dropCommentFromColumn(),删除列注释
addCommentOnTable(),为表添加注释
dropCommentFromTable(),删除列注释
信息:yii\db\Migration没有提供数据库查询方法,这是因为你从数据库取回数据时无需显示额外的信息,也因为你可以使用Query Builder构建和运行更为复杂的查询语句。
注意:当使用迁移处理数据时,你可能发现使用你的Active Record类做此项操作更好使,因为在AR中已经有了实现逻辑。记住,迁移中编写的代码是永远不变的,与此不同的是,应用逻辑将随着业务的改变而改变。在迁移代码中使用AR类,如果AR层逻辑被修改后,将导致现存迁移的意外终止,正因如此,迁移应保持独立,以与其他应用逻辑如AR类相分离。
(全文完)