在開發(fā)和維護一個數(shù)據(jù)庫驅(qū)動的應(yīng)用程序時,數(shù)據(jù)庫的結(jié)構(gòu)會隨代碼的改變而改變。例如,在開發(fā)應(yīng)用程序的過程中,會增加一張新表且必須得加進來; 在應(yīng)用程序被部署到生產(chǎn)環(huán)境后,需要建立一個索引來提高查詢的性能等等。 因為一個數(shù)據(jù)庫結(jié)構(gòu)發(fā)生改變的時候源代碼也經(jīng)常會需要做出改變,Yii 提供了一個?數(shù)據(jù)庫遷移?功能,該功能可以記錄數(shù)據(jù)庫的變化, 以便使數(shù)據(jù)庫和源代碼一起受版本控制。
如下的步驟向我們展示了數(shù)據(jù)庫遷移工具是如何為開發(fā)團隊所使用的:
如下的步驟向我們展示了如何發(fā)布一個附帶數(shù)據(jù)庫遷移的新版本到生產(chǎn)環(huán)境當中:
Yii 提供了一整套的遷移命令行工具,通過這些工具你可以:
所有的這些工具都可以通過?yii migrate
?命令來進行操作。 在這一章節(jié),我們將詳細的介紹如何使用這些工具來完成各種各樣的任務(wù)。你也可以通過?yii help migrate
?命令來獲取每一種工具的具體使用方法。
注意:遷移不僅僅只作用于數(shù)據(jù)庫表,它同樣會調(diào)整現(xiàn)有的數(shù)據(jù)來適應(yīng)新的表單、創(chuàng)建 RBAC 分層、又或者是清除緩存。
使用如下命令來創(chuàng)建一個新的遷移:
yii migrate/create <name>
必填參數(shù)?name
?的作用是對新的遷移做一個簡要的描述。例如,如果這個遷移是用來創(chuàng)建一個叫做?news?的表單的,那么你可以使用create_news_table
?這個名稱并運行如下命令:
yii migrate/create create_news_table
注意:因為?
name
?參數(shù)會被用來生成遷移的類名的一部分,所以該參數(shù)應(yīng)當只包含字母、數(shù)字和下劃線。
如上命令將會在?@app/migrations
?目錄下創(chuàng)建一個新的名為?m150101_185401_create_news_table.php
?的 PHP 類文件。該文件包含如下的代碼,它們用來聲明一個遷移類?m150101_185401_create_news_table
,并附有代碼框架:
<?php
use yii\db\Schema;
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;
}
}
每個數(shù)據(jù)庫遷移都會被定義為一個繼承自 yii\db\Migration 的 PHP 類。類的名稱按照?m<YYMMDD_HHMMSS>_<Name>
?的格式自動生成,其中
<YYMMDD_HHMMSS>
?指執(zhí)行創(chuàng)建遷移命令的 UTC 時間。<Name>
?和你執(zhí)行命令時所帶的?name
?參數(shù)值相同。在遷移類當中,你應(yīng)當在?up()
?方法中編寫改變數(shù)據(jù)庫結(jié)構(gòu)的代碼。你可能還需要在?down()
?方法中編寫代碼來恢復(fù)由?up()
?方法所做的改變。 當你通過 migration 升級數(shù)據(jù)庫時,?up()
?方法將會被調(diào)用,反之,?down()
?將會被調(diào)用。如下代碼展示了如何通過遷移類來創(chuàng)建一張?news
?表:
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends \yii\db\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');
}
}
注意:并不是所有遷移都是可恢復(fù)的。例如,如果?
up()
?方法刪除了表中的一行數(shù)據(jù),這將無法通過?down()
?方法來恢復(fù)這條數(shù)據(jù)。有時候,你也許只是懶得去執(zhí)行?down()
?方法了,因為它在恢復(fù)數(shù)據(jù)庫遷移方面并不是那么的通用。在這種情況下,你應(yīng)當在?down()
?方法中返回?false
?來表明這個 migration 是無法恢復(fù)的。
migration 的基類 yii\db\Migration 通過 yii\db\Migration::db 屬性來連接了數(shù)據(jù)庫。你可以通過?配合數(shù)據(jù)庫工作?章節(jié)中所描述的那些方法來操作數(shù)據(jù)庫表。
當你通過 migration 創(chuàng)建一張表或者字段的時候,你應(yīng)該使用?抽象類型?而不是?實體類型,這樣一來你的遷移對象就可以從特定的 DBMS 當中抽離出來。 yii\db\Schema 類定義了一整套可用的抽象類型常量。這些常量的格式為?TYPE_<Name>
。例如,TYPE_PK
?指代自增主鍵類型;TYPE_STRING
?指代字符串類型。 當遷移對象被提交到某個特定的數(shù)據(jù)庫的時候,這些抽象類型將會被轉(zhuǎn)換成相對應(yīng)的實體類型。以 MySQL 為例,TYPE_PK
?將會變成?int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
, 而?TYPE_STRING
?則變成varchar(255)
。
在使用抽象類型的時候,你可以添加額外的約束條件。在上面的例子當中,?NOT NULL
?被添加到?Schema::TYPE_STRING
?當中來指定該字段不能為空。
提示:抽象類型和實體類型之間的映射關(guān)系是由每個具體的?
QueryBuilder
?類當中的 yii\db\QueryBuilder::$typeMap 屬性所指定的。
從 2.0.5 的版本開始,schema 構(gòu)造器提供了更加方便的方法來定義字段,因此上面的 migration 可以被改寫成:
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends \yii\db\Migration
{
public function up()
{
$this->createTable('news', [
'id' => Schema::primaryKey(),
'title' => Schema::string()->notNull(),
'content' => Schema::text(),
]);
}
public function down()
{
$this->dropTable('news');
}
}
當需要實現(xiàn)復(fù)雜的數(shù)據(jù)庫遷移的時候,確定每一個遷移的執(zhí)行是否成功或失敗就變得相當重要了,因為這將影響到數(shù)據(jù)庫的完整性和一致性。為了達到這個目標,我們建議你把每個遷移里面的數(shù)據(jù)庫操作都封裝到一個?transaction?里面。
實現(xiàn)事務(wù)遷移的一個更為簡便的方法是把遷移的代碼都放到?safeUp()
?和?safeDown()
?方法里面。它們與?up()
?和?down()
?的不同點就在于它們是被隱式的封裝到事務(wù)當中的。如此一來,只要這些方法里面的任何一個操作失敗了,那么所有之前的操作都會被自動的回滾。
在如下的例子當中,除了創(chuàng)建?news
?表以外,我們還插入了一行初始化數(shù)據(jù)到表里面。
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration
{
public function safeUp()
{
$this->createTable('news', [
'id' => Schema::primaryKey(),,
'title' => Schema::string()->notNull(),
'content' => Schema::text(),
]);
$this->insert('news', [
'title' => 'test 1',
'content' => 'content 1',
]);
}
public function safeDown()
{
$this->delete('news', ['id' => 1]);
$this->dropTable('news');
}
}
需要注意的是,當你在?safeUp()
?當中執(zhí)行多個數(shù)據(jù)庫操作的時候,你應(yīng)該在?safeDown()
?方法當中反轉(zhuǎn)它們的執(zhí)行順序。在上面的例子當中,我們在?safeUp()
?方法當中首先創(chuàng)建了一張表,然后插入了一條數(shù)據(jù);而在?safeDown()
?方法當中,我們首先刪除那一行數(shù)據(jù),然后才刪除那張表。
注意:并不是所有的數(shù)據(jù)庫都支持事務(wù)。有些數(shù)據(jù)庫查詢也是不能被放倒事務(wù)里面的。在?implicit commit?章節(jié)當中有相關(guān)的例子可以參考。如果遇到這種情況的話,那么你應(yīng)該使用?
up()
?和?down()
?方法進行替代。
遷移的基類 yii\db\Migration 提供了一整套訪問和操作數(shù)據(jù)庫的方法。你可能會發(fā)現(xiàn)這些方法的命名和 yii\db\Command 類提供的?DAO 方法?很類似。 例如,yii\db\Migration::createTable() 方法可以創(chuàng)建一張新的表,這和 yii\db\Command::createTable() 的功能是一模一樣的。
使用 yii\db\Migration 所提供的方法的好處在于你不需要再顯式的創(chuàng)建 yii\db\Command 實例,而且在執(zhí)行每個方法的時候都會顯示一些有用的信息來告訴我們數(shù)據(jù)庫操作是不是都已經(jīng)完成,還有它們完成這些操作花了多長時間等等。
如下是所有這些數(shù)據(jù)庫訪問方法的列表:
提示:yii\db\Migration 并沒有提供數(shù)據(jù)庫的查詢方法。這是因為通常你是不需要去數(shù)據(jù)庫把數(shù)據(jù)一行一行查出來再顯示出來的。另外一個原因是你完全可以使用強大的?Query Builder 查詢構(gòu)建器?來構(gòu)建和查詢。
為了將數(shù)據(jù)庫升級到最新的結(jié)構(gòu),你應(yīng)該使用如下命令來提交所有新的遷移:
yii migrate
這條命令會列出迄今為止所有未提交的遷移。如果你確定你需要提交這些遷移,它將會按照類名當中的時間戳的順序,一個接著一個的運行每個新的遷移類里面的?up()
?或者是?safeUp()
?方法。如果其中任意一個遷移提交失敗了,那么這條命令將會退出并停止剩下的那些還未執(zhí)行的遷移。
對于每一個成功提交的遷移,這條命令都會在一個叫做?migration
?的數(shù)據(jù)庫表中插入一條包含應(yīng)用程序成功提交遷移的記錄,該記錄將幫助遷移工具判斷哪些遷移已經(jīng)提交, 哪些還沒有提交。
提示:遷移工具將會自動在數(shù)據(jù)庫當中創(chuàng)建?
migration
?表,該數(shù)據(jù)庫是在該命令的 yii\console\controllers\MigrateController::db 選項當中指定的。默認情況下,是由?db
?application component?指定的。
有時,你可能只需要提交一個或者少數(shù)的幾個遷移,你可以使用該命令指定需要執(zhí)行的條數(shù),而不是執(zhí)行所有的可用遷移。例如,如下命令將會嘗試提交前三個可用的遷移:
yii migrate 3
你也可以指定一個特定的遷移,按照如下格式使用?migrate/to
?命令來指定數(shù)據(jù)庫應(yīng)該提交哪一個遷移:
yii migrate/to 150101_185401 # using timestamp to specify the migration 使用時間戳來指定遷移
yii migrate/to "2015-01-01 18:54:01" # using a string that can be parsed by strtotime() 使用一個可以被 strtotime() 解析的字符串
yii migrate/to m150101_185401_create_news_table # using full name 使用全名
yii migrate/to 1392853618 # using UNIX timestamp 使用 UNIX 時間戳
如果在指定要提交的遷移前面還有未提交的遷移,那么在執(zhí)行這個被指定的遷移之前,這些還未提交的遷移會先被提交。
如果被指定提交的遷移在之前已經(jīng)被提交過,那么在其之后的那些遷移將會被還原。
你可以使用如下命令來還原其中一個或多個意見被提交過的遷移:
yii migrate/down # revert the most recently applied migration 還原最近一次提交的遷移
yii migrate/down 3 # revert the most 3 recently applied migrations 還原最近三次提交的遷移
注意:并不是所有的遷移都能被還原。嘗試還原這類遷移將可能導(dǎo)致報錯甚至是終止所有的還原進程。
重做遷移的意思是先還原指定的遷移,然后再次提交。如下所示:
yii migrate/redo # redo the last applied migration 重做最近一次提交的遷移
yii migrate/redo 3 # redo the last 3 applied migrations 重做最近三次提交的遷移
注意:如果一個遷移是不能被還原的,那么你將無法對它進行重做。
你可以使用如下命令列出那些提交了的或者是還未提交的遷移:
yii migrate/history # 顯示最近10次提交的遷移
yii migrate/history 5 # 顯示最近5次提交的遷移
yii migrate/history all # 顯示所有已經(jīng)提交過的遷移
yii migrate/new # 顯示前10個還未提交的遷移
yii migrate/new 5 # 顯示前5個還未提交的遷移
yii migrate/new all # 顯示所有還未提交的遷移
有時候你也許需要簡單的標記一下你的數(shù)據(jù)庫已經(jīng)升級到一個特定的遷移,而不是實際提交或者是還原遷移。這個經(jīng)常會發(fā)生在你手動的改變數(shù)據(jù)庫的一個特定狀態(tài),而又不想相應(yīng)的遷移被重復(fù)提交。那么你可以使用如下命令來達到目的:
yii migrate/mark 150101_185401 # 使用時間戳來指定遷移
yii migrate/mark "2015-01-01 18:54:01" # 使用一個可以被 strtotime() 解析的字符串
yii migrate/mark m150101_185401_create_news_table # 使用全名
yii migrate/mark 1392853618 # 使用 UNIX 時間戳
該命令將會添加或者刪除?migration
?表當中的某幾行數(shù)據(jù)來表明數(shù)據(jù)庫已經(jīng)提交到了指定的某個遷移上。執(zhí)行這條命令期間不會有任何的遷移會被提交或還原。
有很多方法可以自定義遷移命令。
遷移命令附帶了幾個命令行選項,可以用來自定義它的行為:
interactive
: boolean (默認值為 true),指定是否以交互模式來運行遷移。當被設(shè)置為 true 時,在命令執(zhí)行某些操作前,會提示用戶。如果你希望在后臺執(zhí)行該命令,那么你應(yīng)該把它設(shè)置成 false。
migrationPath
: string (默認值為?@app/migrations
),指定存放所有遷移類文件的目錄。該選項可以是一個目錄的路徑,也可以是?路徑別名。需要注意的是指定的目錄必選存在,否則將會觸發(fā)一個錯誤。
migrationTable
: string (默認值為?migration
),指定用于存儲遷移歷史信息的數(shù)據(jù)庫表名稱。如果這張表不存在,那么遷移命令將自動創(chuàng)建這張表。當然你也可以使用這樣的字段結(jié)構(gòu):?version varchar(255) primary key, apply_time integer
?來手動創(chuàng)建這張表。
db
: string (默認值為?db
),指定數(shù)據(jù)庫?application component?的 ID。它指的是將會被該命令遷移的數(shù)據(jù)庫。
templateFile
: string (defaults to?@yii/views/migration.php
),指定生產(chǎn)遷移框架代碼類文件的模版文件路徑。該選項即可以使用文件路徑來指定,也可以使用路徑?別名?來指定。該模版文件是一個可以使用預(yù)定義變量?$className
?來獲取遷移類名稱的 PHP 腳本。如下例子向我們展示了如何使用這些選項:
例如,如果我們需要遷移一個?forum
?模塊,而該遷移文件放在該模塊下的?migrations
?目錄當中,那么我們可以使用如下命令:
# 在 forum 模塊中以非交互模式進行遷移
yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0
在運行遷移命令的時候每次都要重復(fù)的輸入一些同樣的參數(shù)會很煩人,這時候,你可以選擇在應(yīng)用程序配置當中進行全局配置,一勞永逸:
return [
'controllerMap' => [
'migrate' => [
'class' => 'yii\console\controllers\MigrateController',
'migrationTable' => 'backend_migration',
],
],
];
如上所示配置,在每次運行遷移命令的時候,backend_migration
?表將會被用來記錄遷移歷史。你再也不需要通過migrationTable
?命令行參數(shù)來指定這張歷史紀錄表了。
默認情況下,遷移將會提交到由?db
?application component?所定義的同一個數(shù)據(jù)庫當中。如果你需要提交到不同的數(shù)據(jù)庫,你可以像下面那樣指定?db
?命令行選項,
yii migrate --db=db2
上面的命令將會把遷移提交到?db2
?數(shù)據(jù)庫當中。
偶爾有限時候你需要提交?一些?遷移到一個數(shù)據(jù)庫,而另外一些則提交到另一個數(shù)據(jù)庫。為了達到這個目的,你應(yīng)該在實現(xiàn)一個遷移類的時候指定需要用到的數(shù)據(jù)庫組件的 ID , 如下所示:
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration
{
public function init()
{
$this->db = 'db2';
parent::init();
}
}
即使你使用?db
?命令行選項指定了另外一個不同的數(shù)據(jù)庫,上面的遷移還是會被提交到?db2
?當中。需要注意的是這個時候遷移的歷史信息依然會被記錄到?db
?命令行選項所指定的數(shù)據(jù)庫當中。
如果有多個遷移都使用到了同一個數(shù)據(jù)庫,那么建議你創(chuàng)建一個遷移的基類,里面包含上述的?init()
?代碼。然后每個遷移類都繼承這個基類就可以了。
提示:除了在 yii\db\Migration::db 參數(shù)當中進行設(shè)置以外,你還可以通過在遷移類中創(chuàng)建新的數(shù)據(jù)庫連接來操作不同的數(shù)據(jù)庫。然后通過這些連接再使用?DAO 方法?來操作不同的數(shù)據(jù)庫。
另外一個可以讓你遷移多個數(shù)據(jù)庫的策略是把遷移存放到不同的目錄下,然后你可以通過如下命令分別對不同的數(shù)據(jù)庫進行遷移:
yii migrate --migrationPath=@app/migrations/db1 --db=db1
yii migrate --migrationPath=@app/migrations/db2 --db=db2
...
第一條命令將會把?@app/migrations/db1
?目錄下的遷移提交到?db1
?數(shù)據(jù)庫當中,第二條命令則會把?@app/migrations/db2
?下的遷移提交到?db2
?數(shù)據(jù)庫當中,以此類推。
更多建議: