資料庫:Migration
簡介
Migration 就像是資料庫的版本控制,讓你的團隊能定義並共享應用程式的資料庫結構定義(Schema definition)。如果你曾要求團隊成員從版本控制拉取變更後,手動在他們的本機資料庫結構新增欄位,那你已經遇到了資料庫 Migration 所能解決的問題。
Laravel 的 Schema Facade 提供不受限於特定資料庫的支援,可用於在所有 Laravel 支援的資料庫系統中建立與操作資料表。通常,Migration 會使用這個 Facade 來建立與修改資料庫資料表與欄位。
產生 Migration
你可以使用 make:migration Artisan 指令來產生資料庫 Migration。新的 Migration 會被放置在你的 database/migrations 目錄中。每個 Migration 檔名都包含時間戳記,這讓 Laravel 能夠判斷 Migration 的執行順序:
php artisan make:migration create_flights_tableLaravel 會利用 Migration 的名稱嘗試猜測資料表的名稱,以及該 Migration 是否要建立新的資料表。如果 Laravel 能從 Migration 名稱中推導出資料表名稱,Laravel 就會自動在產生的 Migration 檔案中預先填入指定的資料表。否則,你只需在 Migration 檔案中手動指定資料表即可。
如果你想為產生的 Migration 指定自訂路徑,可以在執行 make:migration 指令時使用 --path 選項。給定的路徑應該相對於應用程式的根目錄路徑。
📌 備註
Migration Stub 可以透過 Stub 發布進行自訂。
壓縮 Migration
隨著應用程式的開發,Migration 檔案可能會隨著時間越積越多。這可能會導致你的 database/migrations 目錄變得十分龐大,甚至可能包含數百個 Migration。如果你願意,可以將這些 Migration「壓縮(Squash)」成單一 SQL 檔案。首先,請執行 schema:dump 指令:
php artisan schema:dump
# Dump the current database schema and prune all existing migrations...
php artisan schema:dump --prune當你執行此指令時,Laravel 會在應用程式的 database/schema 目錄中寫入一個「Schema」檔案。該 Schema 檔案的檔名將與資料庫連線相對應。現在,當你嘗試執行 Migration 且尚未執行過其他 Migration 時,Laravel 會優先執行你目前所使用的資料庫連線之 Schema 檔案中的 SQL 敘述句。在執行完 Schema 檔案的 SQL 敘述句後,Laravel 才會執行其餘不包含在 Schema Dump 中的 Migration。
如果你應用程式的測試所使用的資料庫連線,與你在本機開發時通常使用的連線不同,你應確保已使用該測試資料庫連線匯出了 Schema 檔案,以便你的測試能夠建立資料庫。你可以在匯出本機開發用的資料庫連線後進行此操作:
php artisan schema:dump
php artisan schema:dump --database=testing --prune你應該將資料庫 Schema 檔案提交(Commit)到版本控制中,這樣你團隊中的其他新開發人員就能快速建立應用程式的初始資料庫結構。
⚠️ 警告
Migration 壓縮功能僅適用於 MariaDB、MySQL、PostgreSQL 和 SQLite 資料庫,且需要利用該資料庫的命令列用戶端程式。
Migration 結構
一個 Migration 類別包含兩個方法:up 與 down。up 方法用於向資料庫新增資料表、欄位或索引,而 down 方法則應還原 up 方法所執行的操作。
在這兩個方法中,你都可以使用 Laravel 的 Schema 建構器流暢地建立與修改資料表。若要了解 Schema 建構器上所有可用的方法,請查看其相關文件。例如,以下 Migration 會建立一個 flights 資料表:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('airline');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::drop('flights');
}
};設定 Migration 連線
如果你的 Migration 將與應用程式預設資料庫連線以外的連線進行互動,你應該設定 Migration 的 $connection 屬性:
/**
* The database connection that should be used by the migration.
*
* @var string
*/
protected $connection = 'pgsql';
/**
* Run the migrations.
*/
public function up(): void
{
// ...
}跳過 Migration
有時 Migration 可能是為了支援尚未啟用的功能,而你暫時不想執行它。在這種情況下,你可以在 Migration 中定義一個 shouldRun 方法。如果 shouldRun 方法回傳 false,該 Migration 就會被跳過:
use App\Models\Flight;
use Laravel\Pennant\Feature;
/**
* Determine if this migration should run.
*/
public function shouldRun(): bool
{
return Feature::active(Flight::class);
}執行 Migration
要執行所有尚未執行的 migration,請執行 migrate Artisan 指令:
php artisan migrate若想查看哪些 migration 已經執行、哪些仍在等待執行,您可以使用 migrate:status Artisan 指令:
php artisan migrate:status如果您在 migrate 指令中提供 --step 選項,該指令會將每個 migration 作為獨立的批次(batch)執行,讓您之後可以使用 migrate:rollback 指令還原個別的 migration:
php artisan migrate --step若您想預覽 migration 將會執行的 SQL 語句,但不想實際執行它們,可以在 migrate 指令中加上 --pretend 標記:
php artisan migrate --pretend隔離 Migration 執行
若您正在將應用程式部署至多台伺服器,且在部署流程中會執行 migration,您可能不希望兩台伺服器同時嘗試對資料庫進行 migration。為了避免這種情況,您可以在呼叫 migrate 指令時使用 isolated 選項。
當提供 isolated 選項時,Laravel 會在嘗試執行 migration 之前,使用應用程式的快取驅動取得一個原子鎖(atomic lock)。在該鎖定被持有的期間,所有其他嘗試執行 migrate 指令的操作都不會被執行;不過,該指令仍然會以成功的退出狀態碼結束:
php artisan migrate --isolated⚠️ 警告
要使用此功能,您的應用程式必須使用 memcached、redis、dynamodb、database、file 或 array 快取驅動作為應用程式的預設快取驅動。此外,所有伺服器都必須與同一個中央快取伺服器進行通訊。
強制在正式環境中執行 Migration
某些 migration 操作具有破壞性,這意味著它們可能會導致資料遺失。為了防止您針對正式環境資料庫執行這些指令,系統會在執行指令前提示您進行確認。若要強制執行指令而不顯示提示,請使用 --force 標記:
php artisan migrate --force還原 Migration
要還原最新的 migration 操作,您可以使用 rollback Artisan 指令。此指令會還原最後一個「批次(batch)」的 migration,其中可能包含多個 migration 檔案:
php artisan migrate:rollback您可以透過在 rollback 指令中提供 step 選項來還原限定數量的 migration。例如,以下指令將還原最後 5 個 migration:
php artisan migrate:rollback --step=5您可以透過在 rollback 指令中提供 batch 選項來還原特定的 migration「批次」,其中 batch 選項對應到應用程式 migrations 資料表中的批次數值。例如,以下指令將還原第三批次中的所有 migration:
php artisan migrate:rollback --batch=3若您想預覽 migration 還原時將會執行的 SQL 語句,但不想實際執行它們,可以在 migrate:rollback 指令中加上 --pretend 標記:
php artisan migrate:rollback --pretendmigrate:reset 指令會還原您應用程式所有的 migration:
php artisan migrate:reset使用單一指令還原並執行 Migration
migrate:refresh 指令會先還原您所有的 migration,接著再執行 migrate 指令。這個指令實際上會重新建立您的整個資料庫:
php artisan migrate:refresh
# Refresh the database and run all database seeds...
php artisan migrate:refresh --seed您可以透過在 refresh 指令中提供 step 選項來還原並重新執行限定數量的 migration。例如,以下指令將還原並重新執行最後 5 個 migration:
php artisan migrate:refresh --step=5刪除所有資料表並執行 Migration
migrate:fresh 指令會從資料庫中刪除所有資料表,接著再執行 migrate 指令:
php artisan migrate:fresh
php artisan migrate:fresh --seed預設情況下,migrate:fresh 指令只會刪除預設資料庫連線中的資料表。但是,您可以使用 --database 選項來指定應該進行 migration 的資料庫連線。資料庫連線名稱應與應用程式 database 設定檔 中所定義的連線相符:
php artisan migrate:fresh --database=admin⚠️ 警告
migrate:fresh 指令無論資料表是否有前綴(prefix),都會刪除所有資料庫資料表。當在與其他應用程式共用的資料庫上進行開發時,應謹慎使用此指令。
資料表
建立資料表
若要建立新的資料表,請使用 Schema Facade 上的 create 方法。create 方法接收兩個引數:第一個是資料表的名稱,第二個則是一個閉包,該閉包接收一個可以用來定義新資料表的 Blueprint 物件:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email');
$table->timestamps();
});建立資料表時,您可以使用結構建構器的任何 欄位方法 來定義資料表的欄位。
檢查資料表 / 欄位是否存在
您可以使用 hasTable、hasColumn 以及 hasIndex 方法來檢查資料表、欄位或索引是否存在:
if (Schema::hasTable('users')) {
// The "users" table exists...
}
if (Schema::hasColumn('users', 'email')) {
// The "users" table exists and has an "email" column...
}
if (Schema::hasIndex('users', ['email'], 'unique')) {
// The "users" table exists and has a unique index on the "email" column...
}資料庫連線與資料表選項
如果您想在非應用程式預設的資料庫連線執行結構操作,可以使用 connection 方法:
Schema::connection('sqlite')->create('users', function (Blueprint $table) {
$table->id();
});此外,還可以使用一些其他的屬性和方法來定義建立資料表時的其他層面。當使用 MariaDB 或 MySQL 時,可以使用 engine 屬性來指定資料表的儲存引擎:
Schema::create('users', function (Blueprint $table) {
$table->engine('InnoDB');
// ...
});使用 MariaDB 或 MySQL 時,可以使用 charset 和 collation 屬性來指定所建立資料表的字元集與定序:
Schema::create('users', function (Blueprint $table) {
$table->charset('utf8mb4');
$table->collation('utf8mb4_unicode_ci');
// ...
});temporary 方法可用於指示資料表應該是「暫時」的。暫時資料表僅對當前連線的資料庫會話可見,並在連線關閉時自動刪除:
Schema::create('calculations', function (Blueprint $table) {
$table->temporary();
// ...
});若想為資料表新增「註解」,可以在資料表實例上呼叫 comment 方法。資料表註解目前僅由 MariaDB、MySQL 和 PostgreSQL 支援:
Schema::create('calculations', function (Blueprint $table) {
$table->comment('Business calculations');
// ...
});更新資料表
Schema Facade 上的 table 方法可以用來更新現有的資料表。就像 create 方法一樣,table 方法接受兩個引數:資料表的名稱以及一個接收 Blueprint 實例的閉包,您可以使用該實例為資料表新增欄位或索引:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('users', function (Blueprint $table) {
$table->integer('votes');
});重新命名 / 刪除資料表
若要重新命名現有的資料庫資料表,請使用 rename 方法:
use Illuminate\Support\Facades\Schema;
Schema::rename($from, $to);若要刪除現有的資料表,您可以使用 drop 或 dropIfExists 方法:
Schema::drop('users');
Schema::dropIfExists('users');重新命名帶有外鍵的資料表
在重新命名資料表之前,您應該先確認該資料表上的所有外鍵約束在您的 Migration 檔案中都有明確的名稱,而不是讓 Laravel 自動指派基於慣例的名稱。否則,外鍵約束名稱仍會指向舊的資料表名稱。
欄位
建立欄位
可以使用 Schema Facade 上的 table 方法來更新現有的資料表。如同 create 方法,table 方法接收兩個引數:資料表名稱,以及一個接收 Illuminate\Database\Schema\Blueprint 實例的 Closure,您可以透過它來向資料表新增欄位:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('users', function (Blueprint $table) {
$table->integer('votes');
});可用的欄位型別
Schema 建構器藍圖提供各種方法,對應到您可以新增至資料庫表單的各種不同欄位型別。下表列出了所有可用的方法:
布林型別
字串與文字型別
數字型別
bigIncrementsbigIntegerdecimaldoublefloatidincrementsintegermediumIncrementsmediumIntegersmallIncrementssmallIntegertinyIncrementstinyIntegerunsignedBigIntegerunsignedIntegerunsignedMediumIntegerunsignedSmallIntegerunsignedTinyInteger
日期與時間型別
二進位型別
物件與 JSON 型別
UUID 與 ULID 型別
空間型別
關聯型別
特殊型別
bigIncrements()
bigIncrements 方法會建立一個自動遞增且相當於 UNSIGNED BIGINT(主鍵)的欄位:
$table->bigIncrements('id');bigInteger()
bigInteger 方法會建立一個相當於 BIGINT 的欄位:
$table->bigInteger('votes');binary()
binary 方法會建立一個相當於 BLOB 的欄位:
$table->binary('photo');在使用 MySQL、MariaDB 或 SQL Server 時,您可以傳入 length 與 fixed 引數來建立相當於 VARBINARY 或 BINARY 的欄位:
$table->binary('data', length: 16); // VARBINARY(16)
$table->binary('data', length: 16, fixed: true); // BINARY(16)boolean()
boolean 方法會建立一個相當於 BOOLEAN 的欄位:
$table->boolean('confirmed');char()
char 方法會建立一個指定長度且相當於 CHAR 的欄位:
$table->char('name', length: 100);dateTimeTz()
dateTimeTz 方法會建立一個相當於 DATETIME(包含時區)的欄位,並可選擇性指定秒數小數點精確度:
$table->dateTimeTz('created_at', precision: 0);dateTime()
dateTime 方法會建立一個相當於 DATETIME 的欄位,並可選擇性指定秒數小數點精確度:
$table->dateTime('created_at', precision: 0);date()
date 方法會建立一個相當於 DATE 的欄位:
$table->date('created_at');decimal()
decimal 方法會建立一個相當於 DECIMAL 的欄位,並具有指定的精度(總位數)與小數位數(小數點後的位數):
$table->decimal('amount', total: 8, places: 2);double()
double 方法會建立一個相當於 DOUBLE 的欄位:
$table->double('amount');enum()
enum 方法會建立一個相當於 ENUM 的欄位,並具有指定的有效值清單:
$table->enum('difficulty', ['easy', 'hard']);當然,您也可以使用 Enum::cases() 方法,而不是手動定義允許值的陣列:
use App\Enums\Difficulty;
$table->enum('difficulty', Difficulty::cases());float()
float 方法會建立一個指定精度的相當於 FLOAT 的欄位:
$table->float('amount', precision: 53);foreignId()
foreignId 方法會建立一個相當於 UNSIGNED BIGINT 的欄位:
$table->foreignId('user_id');foreignIdFor()
foreignIdFor 方法會為給定的 Model 類別新增相當於 {column}_id 的欄位。根據該 Model 主鍵的型別,欄位型別會是 UNSIGNED BIGINT、CHAR(36) 或 CHAR(26):
$table->foreignIdFor(User::class);foreignUlid()
foreignUlid 方法會建立一個相當於 ULID 的欄位:
$table->foreignUlid('user_id');foreignUuid()
foreignUuid 方法會建立一個相當於 UUID 的欄位:
$table->foreignUuid('user_id');foreignUuidFor()
foreignUuidFor 方法會為給定的 Model 類別新增相當於 {column}_id 的 UUID 欄位:
$table->foreignUuidFor(User::class);geography()
geography 方法會建立一個相當於 GEOGRAPHY 的欄位,並具有指定的空間型別與 SRID(空間參考系統識別碼):
$table->geography('coordinates', subtype: 'point', srid: 4326);📌 備註
對空間型別的支援取決於您的資料庫驅動程式。請參閱您的資料庫文件。如果您的應用程式使用的是 PostgreSQL 資料庫,在使用 geography 方法之前,必須先安裝 PostGIS 擴充功能。
geometry()
geometry 方法會建立一個相當於 GEOMETRY 的欄位,並具有指定的空間型別與 SRID(空間參考系統識別碼):
$table->geometry('positions', subtype: 'point', srid: 0);📌 備註
對空間型別的支援取決於您的資料庫驅動程式。請參閱您的資料庫文件。如果您的應用程式使用的是 PostgreSQL 資料庫,在使用 geometry 方法之前,必須先安裝 PostGIS 擴充功能。
id()
id 方法是 bigIncrements 方法的別名。預設情況下,該方法會建立一個 id 欄位;但如果您想為欄位指定不同的名稱,可以傳入欄位名稱:
$table->id();increments()
increments 方法會建立一個自動遞增且相當於 UNSIGNED INTEGER 的主鍵欄位:
$table->increments('id');integer()
integer 方法會建立一個相當於 INTEGER 的欄位:
$table->integer('votes');ipAddress()
ipAddress 方法會建立一個相當於 VARCHAR 的欄位:
$table->ipAddress('visitor');當使用 PostgreSQL 時,將會建立 INET 欄位。
json()
json 方法會建立一個相當於 JSON 的欄位:
$table->json('options');當使用 SQLite 時,將會建立 TEXT 欄位。
jsonb()
jsonb 方法會建立一個相當於 JSONB 的欄位:
$table->jsonb('options');當使用 SQLite 時,將會建立 TEXT 欄位。
longText()
longText 方法會建立一個相當於 LONGTEXT 的欄位:
$table->longText('description');在使用 MySQL 或 MariaDB 時,您可以對欄位套用 binary 字元集,以建立相當於 LONGBLOB 的欄位:
$table->longText('data')->charset('binary'); // LONGBLOBmacAddress()
macAddress 方法會建立一個用於儲存 MAC 位址的欄位。某些資料庫系統(例如 PostgreSQL)對此類資料具有專用的欄位型別。其他資料庫系統則會使用相當於字串的欄位:
$table->macAddress('device');mediumIncrements()
mediumIncrements 方法會建立一個自動遞增且相當於 UNSIGNED MEDIUMINT 的主鍵欄位:
$table->mediumIncrements('id');mediumInteger()
mediumInteger 方法會建立一個相當於 MEDIUMINT 的欄位:
$table->mediumInteger('votes');mediumText()
mediumText 方法會建立一個相當於 MEDIUMTEXT 的欄位:
$table->mediumText('description');在使用 MySQL 或 MariaDB 時,您可以對欄位套用 binary 字元集,以建立相當於 MEDIUMBLOB 的欄位:
$table->mediumText('data')->charset('binary'); // MEDIUMBLOBmorphs()
morphs 方法是一個便捷方法,會新增一個相當於 {column}_type 的 VARCHAR 欄位以及相當於 {column}_id 的欄位。根據 Model 主鍵的型別,{column}_id 的欄位型別會是 UNSIGNED BIGINT、CHAR(36) 或 CHAR(26)。
此方法旨在用於定義多型 Eloquent 關聯 所需的欄位。在以下範例中,將會建立 taggable_type 與 taggable_id 欄位:
$table->morphs('taggable');nullableMorphs()
此方法與 morphs 方法類似;不過,所建立的欄位會是「可為空值 (nullable)」:
$table->nullableMorphs('taggable');nullableUlidMorphs()
此方法與 ulidMorphs 方法類似;不過,所建立的欄位會是「可為空值 (nullable)」:
$table->nullableUlidMorphs('taggable');nullableUuidMorphs()
此方法與 uuidMorphs 方法類似;不過,所建立的欄位會是「可為空值 (nullable)」:
$table->nullableUuidMorphs('taggable');rememberToken()
rememberToken 方法會建立一個可為空值、相當於 VARCHAR(100) 的欄位,用於儲存目前的「記住我」認證令牌:
$table->rememberToken();set()
set 方法會建立一個相當於 SET 的欄位,並具有指定的有效值清單:
$table->set('flavors', ['strawberry', 'vanilla']);smallIncrements()
smallIncrements 方法會建立一個自動遞增且相當於 UNSIGNED SMALLINT 的主鍵欄位:
$table->smallIncrements('id');smallInteger()
smallInteger 方法會建立一個相當於 SMALLINT 的欄位:
$table->smallInteger('votes');softDeletesTz()
softDeletesTz 方法會新增一個可為空值、相當於 TIMESTAMP(包含時區)的 deleted_at 欄位,並可選擇性指定秒數小數點精確度。此欄位用於儲存 Eloquent「軟刪除 (soft delete)」功能所需的 deleted_at 時間戳記:
$table->softDeletesTz('deleted_at', precision: 0);softDeletes()
softDeletes 方法會新增一個可為空值、相當於 TIMESTAMP 的 deleted_at 欄位,並可選擇性指定秒數小數點精確度。此欄位用於儲存 Eloquent「軟刪除 (soft delete)」功能所需的 deleted_at 時間戳記:
$table->softDeletes('deleted_at', precision: 0);string()
string 方法會建立一個指定長度且相當於 VARCHAR 的欄位:
$table->string('name', length: 100);text()
text 方法會建立一個相當於 TEXT 的欄位:
$table->text('description');在使用 MySQL 或 MariaDB 時,您可以對欄位套用 binary 字元集,以建立相當於 BLOB 的欄位:
$table->text('data')->charset('binary'); // BLOBtimeTz()
timeTz 方法會建立一個相當於 TIME(包含時區)的欄位,並可選擇性指定秒數小數點精確度:
$table->timeTz('sunrise', precision: 0);time()
time 方法會建立一個相當於 TIME 的欄位,並可選擇性指定秒數小數點精確度:
$table->time('sunrise', precision: 0);timestampTz()
timestampTz 方法會建立一個相當於 TIMESTAMP(包含時區)的欄位,並可選擇性指定秒數小數點精確度:
$table->timestampTz('added_at', precision: 0);timestamp()
timestamp 方法會建立一個相當於 TIMESTAMP 的欄位,並可選擇性指定秒數小數點精確度:
$table->timestamp('added_at', precision: 0);timestampsTz()
timestampsTz 方法會建立相當於 created_at 與 updated_at 的 TIMESTAMP(包含時區)欄位,並可選擇性指定秒數小數點精確度:
$table->timestampsTz(precision: 0);timestamps()
timestamps 方法會建立相當於 created_at 與 updated_at 的 TIMESTAMP 欄位,並可選擇性指定秒數小數點精確度:
$table->timestamps(precision: 0);tinyIncrements()
tinyIncrements 方法會建立一個自動遞增且相當於 UNSIGNED TINYINT 的主鍵欄位:
$table->tinyIncrements('id');tinyInteger()
tinyInteger 方法會建立一個相當於 TINYINT 的欄位:
$table->tinyInteger('votes');tinyText()
tinyText 方法會建立一個相當於 TINYTEXT 的欄位:
$table->tinyText('notes');在使用 MySQL 或 MariaDB 時,您可以對欄位套用 binary 字元集,以建立相當於 TINYBLOB 的欄位:
$table->tinyText('data')->charset('binary'); // TINYBLOBunsignedBigInteger()
unsignedBigInteger 方法會建立一個相當於 UNSIGNED BIGINT 的欄位:
$table->unsignedBigInteger('votes');unsignedInteger()
unsignedInteger 方法會建立一個相當於 UNSIGNED INTEGER 的欄位:
$table->unsignedInteger('votes');unsignedMediumInteger()
unsignedMediumInteger 方法會建立一個相當於 UNSIGNED MEDIUMINT 的欄位:
$table->unsignedMediumInteger('votes');unsignedSmallInteger()
unsignedSmallInteger 方法會建立一個相當於 UNSIGNED SMALLINT 的欄位:
$table->unsignedSmallInteger('votes');unsignedTinyInteger()
unsignedTinyInteger 方法會建立一個相當於 UNSIGNED TINYINT 的欄位:
$table->unsignedTinyInteger('votes');ulidMorphs()
ulidMorphs 方法是一個便捷方法,會新增一個相當於 {column}_type 的 VARCHAR 欄位以及相當於 {column}_id 的 CHAR(26) 欄位。
此方法旨在用於定義使用 ULID 識別碼的多型 Eloquent 關聯 所需的欄位。在以下範例中,將會建立 taggable_type 與 taggable_id 欄位:
$table->ulidMorphs('taggable');uuidMorphs()
uuidMorphs 方法是一個便捷方法,會新增一個相當於 {column}_type 的 VARCHAR 欄位以及相當於 {column}_id 的 CHAR(36) 欄位。
此方法旨在用於定義使用 UUID 識別碼的多型 Eloquent 關聯所需的欄位。在以下範例中,將會建立 taggable_type 與 taggable_id 欄位:
$table->uuidMorphs('taggable');ulid()
ulid 方法會建立一個相當於 ULID 的欄位:
$table->ulid('id');uuid()
uuid 方法會建立一個相當於 UUID 的欄位:
$table->uuid('id');vector()
vector 方法會建立一個相當於 vector 的欄位:
$table->vector('embedding', dimensions: 100);在使用 PostgreSQL 時,必須先載入 pgvector 擴充功能,然後才能建立 vector 欄位:
Schema::ensureVectorExtensionExists();year()
year 方法會建立一個相當於 YEAR 的欄位:
$table->year('birth_year');欄位修飾子
除了上面列出的欄位型別之外,在將欄位新增至資料庫表時,還有多個欄位「修飾子」可以使用。例如,若要讓欄位允許為「可為空 (nullable)」,您可以使用 nullable 方法:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('users', function (Blueprint $table) {
$table->string('email')->nullable();
});下表包含所有可用的欄位修飾子。此清單不包含索引修飾子:
| 修飾子 | 說明 |
|---|---|
->after('column') | 將欄位放置於另一個欄位「之後」(MariaDB / MySQL)。 |
->autoIncrement() | 將 INTEGER 欄位設定為自動遞增(主鍵)。 |
->charset('utf8mb4') | 為欄位指定字元集(MariaDB / MySQL)。 |
->collation('utf8mb4_unicode_ci') | 為欄位指定定序。 |
->comment('my comment') | 為欄位新增註解(MariaDB / MySQL / PostgreSQL)。 |
->default($value) | 為欄位指定「預設值」。 |
->first() | 將欄位放置於資料表中的「最前面」(MariaDB / MySQL)。 |
->from($integer) | 設定自動遞增欄位的起始值(MariaDB / MySQL / PostgreSQL)。 |
->instant() | 使用即時 (Instant) 操作新增或修改欄位 (MySQL)。 |
->invisible() | 讓欄位對 SELECT * 查詢「隱藏」(MariaDB / MySQL)。 |
->lock($mode) | 為欄位操作指定鎖定模式 (MySQL)。 |
->nullable($value = true) | 允許插入 NULL 值至欄位中。 |
->storedAs($expression) | 建立儲存型的生成欄位 (Stored Generated Column)(MariaDB / MySQL / PostgreSQL / SQLite)。 |
->unsigned() | 將 INTEGER 欄位設定為 UNSIGNED(MariaDB / MySQL)。 |
->using($expression) | 變更欄位型別時指定轉型運算式 (PostgreSQL)。 |
->useCurrent() | 設定 TIMESTAMP 欄位預設使用 CURRENT_TIMESTAMP。 |
->useCurrentOnUpdate() | 設定 TIMESTAMP 欄位在更新紀錄時使用 CURRENT_TIMESTAMP(MariaDB / MySQL)。 |
->virtualAs($expression) | 建立虛擬型的生成欄位 (Virtual Generated Column)(MariaDB / MySQL / SQLite)。 |
->generatedAs($expression) | 建立具有指定序列選項的識別欄位 (Identity Column) (PostgreSQL)。 |
->always() | 為識別欄位定義序列值優先於輸入值的規則 (PostgreSQL)。 |
預設運算式
default 修飾子接受一個值或一個 Illuminate\Database\Query\Expression 實例。使用 Expression 實例可以防止 Laravel 將該值包覆在引號中,並允許您使用特定於資料庫的函式。這在需要為 JSON 欄位指派預設值時特別有用:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->json('movies')->default(new Expression('(JSON_ARRAY())'));
$table->timestamps();
});
}
};⚠️ 警告
對於預設運算式的支援取決於您的資料庫驅動程式、資料庫版本以及欄位型別。請參閱您的資料庫文件。
欄位順序
使用 MariaDB 或 MySQL 資料庫時,可以使用 after 方法將欄位新增到結構中現有欄位的後面:
$table->after('password', function (Blueprint $table) {
$table->string('address_line1');
$table->string('address_line2');
$table->string('city');
});即時欄位操作
使用 MySQL 時,可以在欄位定義上鏈結 instant 修飾子,以表示應使用 MySQL 的「即時 (instant)」演算法來新增或修改該欄位。這種演算法允許在不重新建構整張資料表的情況下執行特定的結構變更,無論資料表大小如何,幾乎都能瞬間完成:
$table->string('name')->nullable()->instant();即時新增欄位只能將欄位附加到資料表的末端,因此 instant 修飾子無法與 after 或 first 修飾子組合使用。此外,該演算法並不支援所有的欄位型別或操作。如果要求的操作不相容,MySQL 將會拋出錯誤。
請參閱 MySQL 的官方文件以確認哪些操作相容於即時欄位修改。
DDL 鎖定
使用 MySQL 時,可以在欄位、索引或外鍵定義上鏈結 lock 修飾子,以控制結構操作期間的資料表鎖定。MySQL 支援幾種鎖定模式:none 允許同時讀取與寫入、shared 允許同時讀取但阻擋寫入、exclusive 阻擋所有同時存取,而 default 則讓 MySQL 選擇最合適的模式:
$table->string('name')->lock('none');
$table->index('email')->lock('shared');如果要求的鎖定模式與該操作不相容,MySQL 將會拋出錯誤。lock 修飾子可以與 instant 修飾子結合使用,以進一步最佳化結構變更:
$table->string('name')->instant()->lock('none');修改欄位
change 方法允許你修改現有欄位的型別與屬性。例如,你可能想要增加 string 欄位的大小。為了示範 change 方法的效果,讓我們將 name 欄位的大小從 25 增加到 50。要做到這一點,我們只需定義欄位的新狀態,然後呼叫 change 方法:
Schema::table('users', function (Blueprint $table) {
$table->string('name', 50)->change();
});修改欄位時,你必須明確包含想要保留在欄位定義中的所有修飾子——任何未指定的屬性都將被移除。例如,若要保留 unsigned、default 和 comment 屬性,你必須在修改欄位時明確呼叫每個修飾子:
Schema::table('users', function (Blueprint $table) {
$table->integer('votes')->unsigned()->default(1)->comment('my comment')->change();
});change 方法不會改變欄位的索引。因此,你可以在修改欄位時使用索引修飾子來明確新增或刪除索引:
// Add an index...
$table->bigIncrements('id')->primary()->change();
// Drop an index...
$table->char('postal_code', 10)->unique(false)->change();PostgreSQL 欄位修改
在 PostgreSQL 上變更欄位型別時,你可以使用 using 修飾子來指定用於轉型現有值的表達式:
Schema::table('users', function (Blueprint $table) {
$table->date('birthday')->using('birthday::date')->change();
});重新命名欄位
若要重新命名欄位,你可以使用 Schema 建構器提供的 renameColumn 方法:
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('from', 'to');
});刪除欄位
若要刪除欄位,你可以使用 Schema 建構器上的 dropColumn 方法:
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('votes');
});你可以透過將欄位名稱的陣列傳遞給 dropColumn 方法,來從資料表中刪除多個欄位:
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['votes', 'avatar', 'location']);
});可用的命令別名
Laravel 提供了一些方便的方法來刪除常見型別的欄位。下表說明了其中的每個方法:
| 指令 | 說明 |
|---|---|
$table->dropMorphs('morphable'); | 刪除 morphable_type 和 morphable_id 欄位。 |
$table->dropRememberToken(); | 刪除 remember_token 欄位。 |
$table->dropSoftDeletes(); | 刪除 deleted_at 欄位。 |
$table->dropSoftDeletesTz(); | dropSoftDeletes() 方法的別名。 |
$table->dropTimestamps(); | 刪除 created_at 和 updated_at 欄位。 |
$table->dropTimestampsTz(); | dropTimestamps() 方法的別名。 |
索引
建立索引
Laravel 的 Schema 建構器支援多種索引型別。以下範例建立了一個新的 email 欄位,並指定其值必須是唯一的。要建立索引,我們可以將 unique 方法串接在欄位定義之後:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('users', function (Blueprint $table) {
$table->string('email')->unique();
});或者,您可以在定義欄位之後再建立索引。為此,您可以在 Schema 建構器藍圖上呼叫 unique 方法。該方法接收應設定唯一索引的欄位名稱:
$table->unique('email');您甚至可以傳入欄位陣列給索引方法,以建立複合(Compound 或 Composite)索引:
$table->index(['account_id', 'created_at']);建立索引時,Laravel 會自動根據資料表名稱、欄位名稱和索引型別產生索引名稱,但您也可以傳入第二個引數給方法,自行指定索引名稱:
$table->unique('email', 'unique_email');可用的索引型別
Laravel 的 Schema 建構器藍圖類別提供了建立 Laravel 所支援之每種索引型別的方法。每個索引方法都接收一個可選的第二引數來指定索引的名稱。如果省略,名稱將會根據用於該索引的資料表名稱、欄位名稱以及索引型別來推導。下表描述了每個可用的索引方法:
| 指令 | 說明 |
|---|---|
$table->primary('id'); | 新增主鍵。 |
$table->primary(['id', 'parent_id']); | 新增複合主鍵。 |
$table->unique('email'); | 新增唯一索引。 |
$table->index('state'); | 新增索引。 |
$table->fullText('body'); | 新增全文檢索索引(MariaDB / MySQL / PostgreSQL)。 |
$table->fullText('body')->language('english'); | 新增指定語言的全文檢索索引(PostgreSQL)。 |
$table->spatialIndex('location'); | 新增空間索引(SQLite 除外)。 |
線上建立索引
預設情況下,在大資料表上建立索引可能會鎖定資料表,並在建立索引期間阻擋讀取或寫入。當使用 PostgreSQL 或 SQL Server 時,您可以將 online 方法串接在索引定義之後,以在不鎖定資料表的情況下建立索引,讓您的應用程式能在索引建立期間繼續讀取和寫入資料:
$table->string('email')->unique()->online();使用 PostgreSQL 時,這會在索引建立敘述中加上 CONCURRENTLY 選項。使用 SQL Server 時,則會加上 WITH (online = on) 選項。
重新命名索引
要重新命名索引,您可以使用 Schema 建構器藍圖所提供的 renameIndex 方法。該方法接收目前的索引名稱作為其第一個引數,並接收期望的新名稱作為其第二個引數:
$table->renameIndex('from', 'to')刪除索引
要刪除索引,您必須指定索引的名稱。預設情況下,Laravel 會自動根據資料表名稱、受索引的欄位名稱以及索引型別指派索引名稱。以下是一些範例:
| 指令 | 說明 |
|---|---|
$table->dropPrimary('users_id_primary'); | 從 "users" 資料表中刪除主鍵。 |
$table->dropUnique('users_email_unique'); | 從 "users" 資料表中刪除唯一索引。 |
$table->dropIndex('geo_state_index'); | 從 "geo" 資料表中刪除基本索引。 |
$table->dropFullText('posts_body_fulltext'); | 從 "posts" 資料表中刪除全文檢索索引。 |
$table->dropSpatialIndex('geo_location_spatialindex'); | 從 "geo" 資料表中刪除空間索引(SQLite 除外)。 |
如果您將欄位陣列傳入刪除索引的方法中,將會根據資料表名稱、欄位和索引型別產生慣例的索引名稱:
Schema::table('geo', function (Blueprint $table) {
$table->dropIndex(['state']); // Drops index 'geo_state_index'
});外鍵約束
Laravel 也支援建立外鍵約束,用於在資料庫層級強制保持參照完整性。例如,讓我們在 posts 資料表上定義一個 user_id 欄位,該欄位參照 users 資料表的 id 欄位:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('posts', function (Blueprint $table) {
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
});由於這種語法較為冗長,Laravel 提供了其他更簡潔的方法,利用慣例來提供更好的開發者體驗。當使用 foreignId 方法建立欄位時,上述範例可以改寫如下:
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('user_id')->constrained();
});foreignId 方法會建立相當於 UNSIGNED BIGINT 的欄位,而 constrained 方法則會透過慣例來判斷被參照的資料表和欄位。若您的資料表名稱不符合 Laravel 的慣例,可以手動將其傳給 constrained 方法。此外,也可以指定要賦予給生成索引的名稱:
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('user_id')->constrained(
table: 'users', indexName: 'posts_user_id'
);
});您也可以指定約束的 "on delete" 與 "on update" 屬性所要採取的動作:
$table->foreignId('user_id')
->constrained()
->onUpdate('cascade')
->onDelete('cascade');這些動作也提供了另一種更具語意化的語法:
| 方法 | 說明 |
|---|---|
$table->cascadeOnUpdate(); | 更新時應該連動更新 (Cascade)。 |
$table->restrictOnUpdate(); | 更新時應該受限 (Restrict)。 |
$table->nullOnUpdate(); | 更新時應該將外鍵值設為 null。 |
$table->noActionOnUpdate(); | 更新時不採取任何動作 (No Action)。 |
$table->cascadeOnDelete(); | 刪除時應該連動刪除 (Cascade)。 |
$table->restrictOnDelete(); | 刪除時應該受限 (Restrict)。 |
$table->nullOnDelete(); | 刪除時應該將外鍵值設為 null。 |
$table->noActionOnDelete(); | 若存在子紀錄則防止刪除。 |
任何額外的欄位修飾子都必須在 constrained 方法之前呼叫:
$table->foreignId('user_id')
->nullable()
->constrained();刪除外鍵
要刪除外鍵,您可以使用 dropForeign 方法,將要刪除的外鍵約束名稱作為引數傳入。外鍵約束使用與索引相同的命名慣例。換句話說,外鍵約束名稱是基於資料表名稱與約束中的欄位名稱,後面加上 "_foreign" 後綴:
$table->dropForeign('posts_user_id_foreign');或者,您可以將包含持有外鍵之欄位名稱陣列傳給 dropForeign 方法。該陣列會自動使用 Laravel 的約束命名慣例轉換為外鍵約束名稱:
$table->dropForeign(['user_id']);切換外鍵約束狀態
您可以使用下列方法,在 Migration 中啟用或停用外鍵約束:
Schema::enableForeignKeyConstraints();
Schema::disableForeignKeyConstraints();
Schema::withoutForeignKeyConstraints(function () {
// Constraints disabled within this closure...
});⚠️ 警告
SQLite 預設會停用外鍵約束。使用 SQLite 時,請確保在嘗試於 Migration 中建立外鍵之前,已在資料庫設定中啟用外鍵支援。
事件
為了方便起見,每個 migration 操作都會發送一個事件。以下所有事件皆繼承自 Illuminate\Database\Events\MigrationEvent 基底類別:
| 類別 | 說明 |
|---|---|
Illuminate\Database\Events\DatabaseRefreshed | migrate:refresh 命令已執行完成。 |
Illuminate\Database\Events\MigrationsStarted | 一批 Migration 即將開始執行。 |
Illuminate\Database\Events\MigrationsEnded | 一批 Migration 已執行完成。 |
Illuminate\Database\Events\MigrationStarted | 單一 Migration 即將開始執行。 |
Illuminate\Database\Events\MigrationEnded | 單一 Migration 已執行完成。 |
Illuminate\Database\Events\NoPendingMigrations | Migration 命令未找到任何待執行的 Migration。 |
Illuminate\Database\Events\SchemaDumped | 資料庫 Schema 傾印已完成。 |
Illuminate\Database\Events\SchemaLoaded | 現有的資料庫 Schema 傾印檔已載入。 |