資料庫:Migration

簡介

Migration(移轉)」就像是資料表的版本控制一樣,我們能通過 Migration 來定義並與開發團隊共享專案的資料庫結構定義。讀者是否曾經在從版控拉去更新後,還需要告訴同事要手動新增欄位?資料庫 Migration 就是要解決這樣的問題。

Laravel 的 Schema Facade 提供了一種可建立或修改資料表的功能,該功能不區分資料,可用在所有 Laravel 支援的資料庫系統上。一般來說,Migration 會使用該 Facade 來建立或修改資料庫資料表與欄位。

產生 Migration

我們可以使用 make:migration Artisan 指令 來產生資料庫 Migration。新建立的 Migration 會放在 database/migrations 目錄下。各個 Migration 的檔名都包含了一個時戳,用來讓 Laravel 判斷各個 Migration 的執行順序:

1php artisan make:migration create_flights_table
1php artisan make:migration create_flights_table

Laravel 會使用 Migration 的名稱來嘗試推測資料表的名稱,並嘗試推測該 Migration 是否要建立新資料表。若 Laravel 可判斷檔案名稱,則 Laravel 會預先在產生的 Migration 檔中填入特定的資料表。若無法判斷時,我們只需要在 Migration 檔中手動指定資料表即可。

若想為產生的 Migration 檔指定自訂的路徑,則可在執行 make:migration 指令時使用 --path 選項。給定的路徑應為相對於專案根目錄的相對路徑。

lightbulb

可以安裝 Stub 來自訂 Migration 的 Stub。

壓縮 Migration

在我們持續撰寫專案的同時,我們可能會逐漸累積出越來越多的資料庫 Migration 檔。這樣可能會導致 database/migrations 目錄中包含了數百個 Migration 檔。若有需要的話,我們可以將 Migration 檔「壓縮」進單一 SQL 檔內。要開始壓縮,請執行 schema:dump 指令:

1php artisan schema:dump
2 
3# 傾印目前的資料庫結構,並刪除所有現存的 Migration...
4php artisan schema:dump --prune
1php artisan schema:dump
2 
3# 傾印目前的資料庫結構,並刪除所有現存的 Migration...
4php artisan schema:dump --prune

執行該指令時,Laravel 會將一個「Schema(結構描述)」檔案寫入 database/schema 目錄內。Schema 檔案的名稱對影到資料庫連線的名稱。當要移轉資料庫時,若尚未執行過任何 Migration,Laravel 會先執行目前正在使用的資料庫連線所對應 Schema 檔中的 SQL。執行完 Schema 檔內的陳述式後,Laravel 才會接著執行不在該 Schema 傾印中剩下的 Migration。

若專案的測試使用的資料庫連線與本機開發環境所使用的連線不同時,請確認是否有使用該資料庫連線傾印 Schema 檔案,這樣測試中才能正常的建立資料庫。通常這個步驟應放在將開發環境所使用的資料庫連線傾印出來之後:

1php artisan schema:dump
2php artisan schema:dump --database=testing --prune
1php artisan schema:dump
2php artisan schema:dump --database=testing --prune

請將資料庫 Schema 檔 Commit(簽入) 進版本控制中,好讓團隊中其他的新開發人員可快速建立專案的初始資料庫結構。

exclamation

Migration 壓縮只支援 MySQL、PostgreSQL、SQLite 等資料庫,且會使用資料庫的主控台用戶端。Schema 傾印無法用來復原 In-Memory 的 SQLite 資料庫。

Migration 的架構

Migration 類別中包含了兩個方法:updownup 方法可用來在資料庫中新增新資料表、欄位、索引等;而 down 方法則用來做與 up 方法相反的事。

在這兩個方法中,我們可以使用 Laravel 的 Schema Builder 來以描述性的方法建立與修改資料表。若要瞭解 Schema Builder 中所有可用的方法,請參考 Schema Builder 的說明文件。舉例來說,下列 Migration 會建立一個 flights 資料表:

1<?php
2 
3use Illuminate\Database\Migrations\Migration;
4use Illuminate\Database\Schema\Blueprint;
5use Illuminate\Support\Facades\Schema;
6 
7return new class extends Migration
8{
9 /**
10 * Run the migrations.
11 */
12 public function up(): void
13 {
14 Schema::create('flights', function (Blueprint $table) {
15 $table->id();
16 $table->string('name');
17 $table->string('airline');
18 $table->timestamps();
19 });
20 }
21 
22 /**
23 * Reverse the migrations.
24 */
25 public function down(): void
26 {
27 Schema::drop('flights');
28 }
29};
1<?php
2 
3use Illuminate\Database\Migrations\Migration;
4use Illuminate\Database\Schema\Blueprint;
5use Illuminate\Support\Facades\Schema;
6 
7return new class extends Migration
8{
9 /**
10 * Run the migrations.
11 */
12 public function up(): void
13 {
14 Schema::create('flights', function (Blueprint $table) {
15 $table->id();
16 $table->string('name');
17 $table->string('airline');
18 $table->timestamps();
19 });
20 }
21 
22 /**
23 * Reverse the migrations.
24 */
25 public function down(): void
26 {
27 Schema::drop('flights');
28 }
29};

設定 Migration 的連線

若 Migration 會使用與專案預設資料庫連線不同的資料庫連線,則請在 Migration 中設定 $connection 屬性:

1/**
2 * The database connection that should be used by the migration.
3 *
4 * @var string
5 */
6protected $connection = 'pgsql';
7 
8/**
9 * Run the migrations.
10 */
11public function up(): void
12{
13 // ...
14}
1/**
2 * The database connection that should be used by the migration.
3 *
4 * @var string
5 */
6protected $connection = 'pgsql';
7 
8/**
9 * Run the migrations.
10 */
11public function up(): void
12{
13 // ...
14}

執行 Migration

若要執行所有尚未執行過的 Migration,請執行 migrate Artisan 指令:

1php artisan migrate
1php artisan migrate

若想檢視目前為止已執行了哪些 Migration,可使用 migrate:status Artisan 指令:

1php artisan migrate:status
1php artisan migrate:status

若想檢視 Migration 中會執行的 SQL 陳述式而不實際執行這些 SQL,則可提供 --pretend 旗標給 migrate 指令:

1php artisan migrate --pretend
1php artisan migrate --pretend

獨立執行 Migration

若專案會被部署到多個伺服器上,而 Migration 是部署流程的一部分的話,我們通常不會希望有兩台 Server 同時嘗試執行資料庫移轉。若要避免有兩台 Server 同時執行,可以在呼叫 migrate 指令時加上 isolated 選項。

提供了 isolated 選項後,Laravel 會嘗試使用專案的快取 Driver 來取得一個 Atomic Lock(不可部分完成鎖定),然後再執行 Migration。當 Lock 被鎖住時,嘗試執行 migrate 指令就不會執行。不過,這些不被執行的 migrate 指令仍然會回傳成功的終止狀態代碼:

1php artisan migrate --isolated
1php artisan migrate --isolated
exclamation

若要使用此功能,則應用程式必須要使用 memcached, redis, dynamodb, database, filearray 作為應用程式的預設快取 Driver。另外,所有的伺服器也都必須要連線至相同的中央快取伺服器。

在正式環境中強制執行 Migration

有些 Migration 中的動作是破壞性的,也就是一些會導致資料消失的動作。為了避免在正式環境資料庫中執行這些破壞性的動作,因此在執行指令時,會出現提示要求確認。若要強制該指令不跳出提示直接執行,請使用 --force 旗標:

1php artisan migrate --force
1php artisan migrate --force

復原 Migration

若要復原最後執行的 Migration 動作,可使用 rollback Artisan 指令。該指令會復原最後「一批」執行的 Migration,其中可能包含多個 Migration 檔:

1php artisan migrate:rollback
1php artisan migrate:rollback

我們也可以提供各一個 step 選項給 rollback 指令,以限制要復原的 Migration 數量。舉例來說,下列指令只會復原最後 5 個 Migration:

1php artisan migrate:rollback --step=5
1php artisan migrate:rollback --step=5

只要提供 batch 選項給 rollback 指令,就可以復原特定「批次」的 Migration。這裡的 batch 選項,對應到專案資料庫中 migrations 資料表的 batch 值。舉例來說,下列指令會復原所有第 3 批次的 Migration:

1php artisan migrate:rollback --batch=3
1php artisan migrate:rollback --batch=3

migrate:reset 指令會復原專案中所有的 Migration:

1php artisan migrate:reset
1php artisan migrate:reset

以單一指令來復原並 Migrate

migrate:refresh 指令會將所有的 Migration 都復原回去,並接著執行 migrate 指令。使用該指令,就可以有效率的重建整個資料庫:

1php artisan migrate:refresh
2 
3# 重新整理資料庫,並執行所有的資料庫 Seed...
4php artisan migrate:refresh --seed
1php artisan migrate:refresh
2 
3# 重新整理資料庫,並執行所有的資料庫 Seed...
4php artisan migrate:refresh --seed

我們也可以提供各一個 step 選項給 refresh 指令,以限制要復原並重新 Migrate 的 Migration 數量。舉例來說,下列指令只會復原並重新 Migrate 最後 5 個 Migration:

1php artisan migrate:refresh --step=5
1php artisan migrate:refresh --step=5

刪除所有資料表並 Migrate

migrate:fresh 指令會刪除資料庫中所有資料表,並接著執行 migrate 指令:

1php artisan migrate:fresh
2 
3php artisan migrate:fresh --seed
1php artisan migrate:fresh
2 
3php artisan migrate:fresh --seed
exclamation

不論資料表是否有前置詞 (Prefix),migrate:fresh 指令會刪除所有的資料庫資料表。在使用與其他專案共享的資料庫時,若要與本指令搭配使用請務必注意。

資料表

建立資料表

若要建立新的資料庫資料表,請使用 Schema Facade 上的 create 方法。create 方法接受兩個引數:資料表名稱、以及一個接收 Blueprint 物件的閉包。Blueprint 物件可用來定義新資料表:

1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::create('users', function (Blueprint $table) {
5 $table->id();
6 $table->string('name');
7 $table->string('email');
8 $table->timestamps();
9});
1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::create('users', function (Blueprint $table) {
5 $table->id();
6 $table->string('name');
7 $table->string('email');
8 $table->timestamps();
9});

建立資料表時,我們可以使用任意 Schema Builder 的欄位方法來定義資料表欄位。

檢查資料表與欄位是否存在

我們可以使用 hasTablehasColumn 方法來檢查資料表或欄位是否存在:

1if (Schema::hasTable('users')) {
2 // 「users」資料表存在...
3}
4 
5if (Schema::hasColumn('users', 'email')) {
6 // 「users」資料表存在,且包含一個「email」欄位...
7}
1if (Schema::hasTable('users')) {
2 // 「users」資料表存在...
3}
4 
5if (Schema::hasColumn('users', 'email')) {
6 // 「users」資料表存在,且包含一個「email」欄位...
7}

資料庫連線與資料表選項

若要在非專案預設連線的資料庫連線上做 Schema 動作,請使用 connection 方法:

1Schema::connection('sqlite')->create('users', function (Blueprint $table) {
2 $table->id();
3});
1Schema::connection('sqlite')->create('users', function (Blueprint $table) {
2 $table->id();
3});

此外,還有一些其他的屬性或方法,可用來調整資料表建立中的其他細節。使用 MySQL 時,可使用 engine 屬性來指定資料表的 Storage Engine:

1Schema::create('users', function (Blueprint $table) {
2 $table->engine = 'InnoDB';
3 
4 // ...
5});
1Schema::create('users', function (Blueprint $table) {
2 $table->engine = 'InnoDB';
3 
4 // ...
5});

使用 MySQL 時,charsetcollation 屬性可用來指定建立資料表的 Character Set 與 Collection:

1Schema::create('users', function (Blueprint $table) {
2 $table->charset = 'utf8mb4';
3 $table->collation = 'utf8mb4_unicode_ci';
4 
5 // ...
6});
1Schema::create('users', function (Blueprint $table) {
2 $table->charset = 'utf8mb4';
3 $table->collation = 'utf8mb4_unicode_ci';
4 
5 // ...
6});

temporary 方法可用來表示該資料表是「臨時」資料表。臨時資料表只可在目前連線的資料庫工作階段中使用,且會在連線關閉後自動刪除:

1Schema::create('calculations', function (Blueprint $table) {
2 $table->temporary();
3 
4 // ...
5});
1Schema::create('calculations', function (Blueprint $table) {
2 $table->temporary();
3 
4 // ...
5});

若想在資料表上加入「註解」,可在 Table 實體上叫用 comment 方法。資料表註解目前只支援 MySQL 與 Postgres:

1Schema::create('calculations', function (Blueprint $table) {
2 $table->comment('Business calculations');
3 
4 // ...
5});
1Schema::create('calculations', function (Blueprint $table) {
2 $table->comment('Business calculations');
3 
4 // ...
5});

更新資料表

Schema Facade 上的 table 方法可用來更新現有的資料表。與 create 方法類似,table 方法接受兩個因數:資料表名稱,以及一個接收 Blueprint 實體的閉包。使用 Blueprint 實體,即可用來在資料表上新增欄位或索引:

1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('users', function (Blueprint $table) {
5 $table->integer('votes');
6});
1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('users', function (Blueprint $table) {
5 $table->integer('votes');
6});

重新命名或刪除資料表

若要重新命名現有的資料表,可使用 rename 方法:

1use Illuminate\Support\Facades\Schema;
2 
3Schema::rename($from, $to);
1use Illuminate\Support\Facades\Schema;
2 
3Schema::rename($from, $to);

若要移除現有的資料表,可使用 dropdropIfExists 方法:

1Schema::drop('users');
2 
3Schema::dropIfExists('users');
1Schema::drop('users');
2 
3Schema::dropIfExists('users');

與外部索引鍵一起重新命名資料表

在重新命名資料表時,請務必確認該資料表上的外部索引鍵條件(Foreign Key Constraint)是否有直接設定名稱,而不是使用 Laravel 所指定的基於慣例的名稱。若未直接設定名稱,則這些外部索引鍵條件的名稱可能會參照到舊的資料表名稱:

欄位

建立欄位

Schema Facade 上的 table 方法可用來更新現有的資料表。與 create 方法類似,table 方法接受兩個因數:資料表名稱,以及一個接收 Illuminate\Database\Schema\Blueprint 實體的閉包。使用這個 Blueprint 實體,即可用來在資料表上新增欄位或索引:

1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('users', function (Blueprint $table) {
5 $table->integer('votes');
6});
1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('users', function (Blueprint $table) {
5 $table->integer('votes');
6});

可用的欄位型別

Schema Builder Blueprint 提供了多種方法,這些方法對應到可新增至資料庫資料表中各種不同的欄位型別。可用的各個方法列在下表中:

bigIncrements()

bigIncrements 方法建立一個 Auto-Increment(自動遞增)UNSIGNED BIGINT (主索引鍵(Primary Key)) 或相等欄位:

1$table->bigIncrements('id');
1$table->bigIncrements('id');

bigInteger()

bigInteger 方法建立一個 BIGINT 或相等的欄位:

1$table->bigInteger('votes');
1$table->bigInteger('votes');

binary()

binary 方法建立一個 BLOB 或相等欄位:

1$table->binary('photo');
1$table->binary('photo');

boolean()

boolean 方法建立一個 BOOLEAN 或相等欄位:

1$table->boolean('confirmed');
1$table->boolean('confirmed');

char()

char 方法以給定的長度來建立一個 CHAR 或相等欄位:

1$table->char('name', 100);
1$table->char('name', 100);

dateTimeTz()

dateTimeTz 方法以給定的精度 (總位數) 建立一個 DATETIME (含時區) 或相等欄位:

1$table->dateTimeTz('created_at', $precision = 0);
1$table->dateTimeTz('created_at', $precision = 0);

dateTime()

dateTime 方法會使用給定的可選精度 (總位數) 來建立一個 DATETIME 或相等欄位:

1$table->dateTime('created_at', $precision = 0);
1$table->dateTime('created_at', $precision = 0);

date()

date 方法會建立一個 DATE 或相等欄位:

1$table->date('created_at');
1$table->date('created_at');

decimal()

decimal 方法會以給定的精度(Precision) (總位數) 與小數位數(Scale) (小數位數) 來建立一個 DECIMAL 或相等欄位:

1$table->decimal('amount', $precision = 8, $scale = 2);
1$table->decimal('amount', $precision = 8, $scale = 2);

double()

double 方法會以給定的精度(Precision) (總位數) 與小數位數(Scale) (小數位數) 來建立一個 DOUBLE 或相等欄位:

1$table->double('amount', 8, 2);
1$table->double('amount', 8, 2);

enum()

enum 方法以給定的有效值來建立一個 ENUM 或相等欄位:

1$table->enum('difficulty', ['easy', 'hard']);
1$table->enum('difficulty', ['easy', 'hard']);

float()

float 方法會以給定的精度(Precision) (總位數) 與小數位數(Scale) (小數位數) 來建立一個 FLOAT 或相等欄位:

1$table->float('amount', 8, 2);
1$table->float('amount', 8, 2);

foreignId()

foreignId 方法會建立一個 UNSIGNED BIGINT 或相等的欄位:

1$table->foreignId('user_id');
1$table->foreignId('user_id');

foreignIdFor()

foreighIdFor 方法會以給定的 Model 類別來建立一個 {欄位}_id UNSIGNED BIGINT 或相等欄位:

1$table->foreignIdFor(User::class);
1$table->foreignIdFor(User::class);

foreignUlid()

foreignUlid 方法會建立一個 ULID 或相等欄位:

1$table->foreignUlid('user_id');
1$table->foreignUlid('user_id');

foreignUuid()

foreignUuid 方法會建立一個 UUID 或相等欄位:

1$table->foreignUuid('user_id');
1$table->foreignUuid('user_id');

geometryCollection()

geometryCollection 方法會建立一個 GEOMETRYCOLLECTION 或相等欄位:

1$table->geometryCollection('positions');
1$table->geometryCollection('positions');

geometry()

geometry 方法建立一個 GEOMETRY 或相等欄位:

1$table->geometry('positions');
1$table->geometry('positions');

id()

id 欄位為 bigIncrements 方法的別名。預設情況下,該方法會建立一個 id 欄位。不過,若想為該欄位指定不同的名稱,也可以傳入欄位名稱:

1$table->id();
1$table->id();

increments()

increments 方法會建立一個 Auto-Increment(自動遞增)UNSIGNED INTEGER 或同等欄位作為主索引鍵(Primary Key)

1$table->increments('id');
1$table->increments('id');

integer()

integer 方法建立一個 INTEGER 或相等的欄位:

1$table->integer('votes');
1$table->integer('votes');

ipAddress()

ipAddress 方法會建立一個 VARCHAR 或相等欄位:

1$table->ipAddress('visitor');
1$table->ipAddress('visitor');

json()

json 方法會建立一個 JSON 或相等欄位:

1$table->json('options');
1$table->json('options');

jsonb()

jsonb 方法會建立一個 JSONB 或相等欄位:

1$table->jsonb('options');
1$table->jsonb('options');

lineString()

lineString 方法建立一個 LINESTRING 或相等的欄位:

1$table->lineString('positions');
1$table->lineString('positions');

longText()

longText 方法建立一個 LONGTEXT 或相等欄位:

1$table->longText('description');
1$table->longText('description');

macAddress()

macAddress 方法會建立一個用來保存 MAC 位址的欄位。在某些資料庫系統,如 PostgreSQL 中,有專門的欄位可用來保存這種類型的資料。在其他資料庫系統,則會使用相等的字串欄位:

1$table->macAddress('device');
1$table->macAddress('device');

mediumIncrements()

mediumIncrements 方法會建立一個 Auto-Increment(自動遞增)UNSIGNED MEDIUMINT 或同等欄位作為主索引鍵(Primary Key)

1$table->mediumIncrements('id');
1$table->mediumIncrements('id');

mediumInteger()

mediumInteger 方法建立一個 MEDIUMINT 或相等的欄位:

1$table->mediumInteger('votes');
1$table->mediumInteger('votes');

mediumText()

mediumText 方法建立一個 MEDIUMTEXT 或相等的欄位:

1$table->mediumText('description');
1$table->mediumText('description');

morphs()

morphs 是一個方便方法,會新增一個 {欄位}_id UNSIGNED BIGINT 或相等欄位,以及一個 {欄位}_type VARCHAR 或想等欄位。

該方法主要是要給多型 Eloquent 關聯定義欄位用的。在下列範例中,會建立 taggable_idtaggable_type 欄位:

1$table->morphs('taggable');
1$table->morphs('taggable');

multiLineString()

multiLineString 方法建立一個 MULTILINESTRING 或相等的欄位:

1$table->multiLineString('positions');
1$table->multiLineString('positions');

multiPoint()

multiPoint 方法建立一個 MULTIPOINT 或相等的欄位:

1$table->multiPoint('positions');
1$table->multiPoint('positions');

multiPolygon()

multiPolygon 方法建立一個 MULTIPOLYGON 或相等的欄位:

1$table->multiPolygon('positions');
1$table->multiPolygon('positions');

nullableTimestamps()

nullabaleTimestamps 方法是 timestamps 方法的別名:

1$table->nullableTimestamps(0);
1$table->nullableTimestamps(0);

nullableMorphs()

該方法與 morphs 方法類似。不過,使用 nullableMorphs 方法建立的欄位會是「nullable」的:

1$table->nullableMorphs('taggable');
1$table->nullableMorphs('taggable');

nullableUlidMorphs()

該方法與 ulidMorphs 方法類似。不過,使用 ulidMorphs 方法建立的欄位會是「nullable」的:

1$table->nullableUlidMorphs('taggable');
1$table->nullableUlidMorphs('taggable');

nullableUuidMorphs()

該方法與 uuidMorphs 方法類似。不過,使用 nullableMorphs 方法建立的欄位會是「nullable」的:

1$table->nullableUuidMorphs('taggable');
1$table->nullableUuidMorphs('taggable');

point()

point 方法會建立一個 POINT 或相等欄位:

1$table->point('position');
1$table->point('position');

polygon()

polygon 方法建立一個 POLYGON 或相等的欄位:

1$table->polygon('position');
1$table->polygon('position');

rememberToken()

rememberToken 方法會建立一個 Nullable 的 VARCHAR(100) 或相等的欄位,用於存放目前的「記住我」身份驗證權杖

1$table->rememberToken();
1$table->rememberToken();

set()

set 方法會以給定的有效值來建立一個 SET 或相等欄位:

1$table->set('flavors', ['strawberry', 'vanilla']);
1$table->set('flavors', ['strawberry', 'vanilla']);

smallIncrements()

smallIncrements 方法會建立一個 Auto-Increment(自動遞增)UNSIGNED SMALLINT 或同等欄位作為主索引鍵(Primary Key)

1$table->smallIncrements('id');
1$table->smallIncrements('id');

smallInteger()

smallInteger 方法建立一個 SMALLINT 或相等的欄位:

1$table->smallInteger('votes');
1$table->smallInteger('votes');

softDeletesTz()

softDeletesTz 方法會以給定的可選精度(Precision) (總位數) 新增一個 Nullable 的 deleted_at TIMESTAMP (含時區) 或相等欄位。該欄位主要是給 Eloquent「軟刪除」功能使用的,用來保存 deleted_at 時戳:

1$table->softDeletesTz($column = 'deleted_at', $precision = 0);
1$table->softDeletesTz($column = 'deleted_at', $precision = 0);

softDeletes()

softDeletes 方法會以給定的可選精度(Precision) (總位數) 新增一個 Nullable 的 deleted_at TIMESTAMP 或相等欄位。該欄位主要是給 Eloquent「軟刪除」功能使用的,用來保存 deleted_at 時戳:

1$table->softDeletes($column = 'deleted_at', $precision = 0);
1$table->softDeletes($column = 'deleted_at', $precision = 0);

string()

string 方法以給定的長度來建立一個 VARCHAR 或相等欄位:

1$table->string('name', 100);
1$table->string('name', 100);

text()

text 方法會建立一個 TEXT 或相等欄位:

1$table->text('description');
1$table->text('description');

timeTz()

timeTz 方法以給定的精度 (總位數) 建立一個 TIME (含時區) 或相等欄位:

1$table->timeTz('sunrise', $precision = 0);
1$table->timeTz('sunrise', $precision = 0);

time()

time 方法會使用給定的可選精度 (總位數) 來建立一個 TIME 或相等欄位:

1$table->time('sunrise', $precision = 0);
1$table->time('sunrise', $precision = 0);

timestampTz()

timestampTz 方法以給定的精度 (總位數) 建立一個 TIMESTAMP (含時區) 或相等欄位:

1$table->timestampTz('added_at', $precision = 0);
1$table->timestampTz('added_at', $precision = 0);

timestamp()

timestamp 方法會使用給定的可選精度 (總位數) 來建立一個 TIMESTAMP 或相等欄位:

1$table->timestamp('added_at', $precision = 0);
1$table->timestamp('added_at', $precision = 0);

timestampsTz()

timestampsTz 方法以給定可選精度(Precision) (總位數) 建立 TIMESTAMP (含時區) 或相等的 created_atupdated_at 欄位:

1$table->timestampsTz($precision = 0);
1$table->timestampsTz($precision = 0);

timestamps()

timestamps 方法以給定可選精度(Precision) (總位數) 建立 TIMESTAMP 或相等的 created_atupdated_at 欄位:

1$table->timestamps($precision = 0);
1$table->timestamps($precision = 0);

tinyIncrements()

tinyIncrements 方法會建立一個 Auto-Increment(自動遞增)UNSIGNED TINYINT 或同等欄位作為主索引鍵(Primary Key)

1$table->tinyIncrements('id');
1$table->tinyIncrements('id');

tinyInteger()

tinyInteger 方法建立一個 TINYINT 或相等的欄位:

1$table->tinyInteger('votes');
1$table->tinyInteger('votes');

tinyText()

tinyText 方法建立一個 TINYTEXT 或相等欄位:

1$table->tinyText('notes');
1$table->tinyText('notes');

unsignedBigInteger()

unsignedBigInteger 方法會建立一個 UNSIGNED BIGINT 或相等的欄位:

1$table->unsignedBigInteger('votes');
1$table->unsignedBigInteger('votes');

unsignedDecimal()

unsignedDecimal 方法會以給定的可選精度(Precision) (總位數) 與小數位數(Scale) (小數位數) 來建立一個 UNSIGNED DECIMAL 或相等欄位:

1$table->unsignedDecimal('amount', $precision = 8, $scale = 2);
1$table->unsignedDecimal('amount', $precision = 8, $scale = 2);

unsignedInteger()

unsignedInteger 方法會建立一個 UNSIGNED INTEGER 或相等的欄位:

1$table->unsignedInteger('votes');
1$table->unsignedInteger('votes');

unsignedMediumInteger()

unsignedMediumInteger 方法會建立一個 UNSIGNED MEDIUMINT 或相等的欄位:

1$table->unsignedMediumInteger('votes');
1$table->unsignedMediumInteger('votes');

unsignedSmallInteger()

unsignedSmallInteger 方法會建立一個 UNSIGNED SMALLINT 或相等的欄位:

1$table->unsignedSmallInteger('votes');
1$table->unsignedSmallInteger('votes');

unsignedTinyInteger()

unsignedTinyInteger 方法會建立一個 UNSIGNED TINYINT 或相等的欄位:

1$table->unsignedTinyInteger('votes');
1$table->unsignedTinyInteger('votes');

ulidMorphs()

ulidMorphs 是一個方便方法,會新增一個 {欄位}_id CHAR(26) 或相等欄位,以及一個 {欄位}_type VARCHAR 或相等欄位。

該方法主要是要給使用 ULID 作為識別字元的多型 Eloquent 關聯定義欄位用的。在下列範例中,會建立 taggable_idtaggable_type 欄位:

1$table->ulidMorphs('taggable');
1$table->ulidMorphs('taggable');

uuidMorphs()

uuidMorphs 是一個方便方法,會新增一個 {欄位}_id CHAR(36) 或相等欄位,以及一個 {欄位}_type VARCHAR 或想等欄位。

該方法主要是要給使用 UUID 作為識別字元的多型 Eloquent 關聯定義欄位用的。在下列範例中,會建立 taggable_idtaggable_type 欄位:

1$table->uuidMorphs('taggable');
1$table->uuidMorphs('taggable');

ulid()

ulid 方法會建立一個 ULID 或相等欄位:

1$table->ulid('id');
1$table->ulid('id');

uuid()

uuid 方法會建立一個 UUID 或相等欄位:

1$table->uuid('id');
1$table->uuid('id');

year()

year 方法會建立一個 YEAR 或相等欄位:

1$table->year('birth_year');
1$table->year('birth_year');

欄位修飾詞

除了上列欄位型別外,還有多種可在將欄位新增至資料表時使用的欄位「修飾詞」。舉例來說,若要將欄位設為「Nullable」,可使用 nullable 方法:

1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('users', function (Blueprint $table) {
5 $table->string('email')->nullable();
6});
1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('users', function (Blueprint $table) {
5 $table->string('email')->nullable();
6});

下表中包含了所有可用的修飾詞。該列表中未包含索引修飾詞

修飾詞說明
->after('column')將欄位放在另一個欄位「之後(After)」(MySQL)。
->autoIncrement()將 INTEGER 欄位設為 Auto-Increment(自動遞增) (主索引鍵(Primary Key))。
->charset('utf8mb4')指定用於該欄位的 Character Set (MySQL)。
->collation('utf8mb4_unicode_ci')指定用於該欄位的 Collation (MySQL/PostgreSQL/SQL Server)。
->comment('my comment')為該欄位新增註解 (MySQL/PostgreSQL)。
->default($value)為欄位指定「預設(Default)」值。
->first()將欄位放在資料表中的「第一個(First)」欄位 (MySQL)。
->from($integer)設定 Auto-Increment(自動遞增) 欄位的起始值 (MySQL / PostgreSQL)。
->invisible()讓該欄位在 SELECT * 查詢中「不可見(Invisible)」(MySQL)。
->nullable($value = true)允許將 NULL 值插入該欄位中。
->storedAs($expression)建立一個 Stored Generated 的欄位 (MySQL / PostgreSQL)。
->unsigned()將 INTEGER 欄位設為 UNSIGNED (MySQL)。
->useCurrent()設定 TIMESTAMP 欄位使用 CURRENT_TIMESTAMP 作為預設值。
->useCurrentOnUpdate()在資料更新時,將 TIMESTAMP 欄位設為 CURRENT_TIMESTAMP。
->virtualAs($expression)建立 Virtual Generated 欄位 (MySQL)。
->generatedAs($expression)以指定的 Sequence(順序) 選項來建立 Identity 欄位 (PostgreSQL)。
->always()定義一個優先使用 Sequence 值而不使用輸入值的 Identity 欄位 (PostgreSQL)。
->isGeometry()將 Spatial 欄位的型別設為 geometry —— 即 geography 的預設型別 (PostgreSQL)。

預設運算式

default 修飾詞可接受 Illuminate\Database\Query\Expression 實體。使用 Expression 實體時,Laravel 就不會將輸入值包裝在引號內,能讓我們使用資料庫所提供的函式。有一些狀況特別適合使用這個方法,如要給 JSON 欄位指定預設值時:

1<?php
2 
3use Illuminate\Support\Facades\Schema;
4use Illuminate\Database\Schema\Blueprint;
5use Illuminate\Database\Query\Expression;
6use Illuminate\Database\Migrations\Migration;
7 
8return new class extends Migration
9{
10 /**
11 * Run the migrations.
12 */
13 public function up(): void
14 {
15 Schema::create('flights', function (Blueprint $table) {
16 $table->id();
17 $table->json('movies')->default(new Expression('(JSON_ARRAY())'));
18 $table->timestamps();
19 });
20 }
21};
1<?php
2 
3use Illuminate\Support\Facades\Schema;
4use Illuminate\Database\Schema\Blueprint;
5use Illuminate\Database\Query\Expression;
6use Illuminate\Database\Migrations\Migration;
7 
8return new class extends Migration
9{
10 /**
11 * Run the migrations.
12 */
13 public function up(): void
14 {
15 Schema::create('flights', function (Blueprint $table) {
16 $table->id();
17 $table->json('movies')->default(new Expression('(JSON_ARRAY())'));
18 $table->timestamps();
19 });
20 }
21};
exclamation

對於預設運算式的支援程度會因資料庫 Driver、資料庫版本、欄位型別等而有所不同。請參考資料庫的說明文件。

欄位順序

在使用 MySQL 資料庫時,可使用 after 方法來將欄位插入到資料表結構中的某個現有欄位之後:

1$table->after('password', function (Blueprint $table) {
2 $table->string('address_line1');
3 $table->string('address_line2');
4 $table->string('city');
5});
1$table->after('password', function (Blueprint $table) {
2 $table->string('address_line1');
3 $table->string('address_line2');
4 $table->string('city');
5});

修改欄位

使用 change 欄位,即可修改現有欄位的型別或屬性。舉例來說,我們可以用來增加 string 欄位的大小。來看看使用 change 方法的例子,我們來把 name 欄位的大小從 25 增加到 50。若要增加 name 欄位的大小,我們只需要定義該欄位的新狀態,然後呼叫 change 方法即可:

1Schema::table('users', function (Blueprint $table) {
2 $table->string('name', 50)->change();
3});
1Schema::table('users', function (Blueprint $table) {
2 $table->string('name', 50)->change();
3});

修改欄位時,必須明顯包含所有要保留在該欄位上的修飾子 (Modifier)。若有未提供的修飾子,則這些屬性會被移除。舉例來說,若要保留 unsigneddefault、與 comment 屬性,則在修改欄位時必須明顯呼叫這幾個修飾子:

1Schema::table('users', function (Blueprint $table) {
2 $table->integer('votes')->unsigned()->default(1)->comment('my comment')->change();
3});
1Schema::table('users', function (Blueprint $table) {
2 $table->integer('votes')->unsigned()->default(1)->comment('my comment')->change();
3});

在 SQLite 上修改欄位

若專案使用 SQLite 資料庫,則必須使用 Composer 套件管理員來安裝 doctrine/dbal 套件,才可以修改欄位。Doctrine DBAL 函式庫要用來判斷目前欄位的狀態,並用以建立要修改欄位所需要的 SQL 查詢:

1composer require doctrine/dbal
1composer require doctrine/dbal

若有需要修改使用 timestamp 方法建立的欄位,則必須在 config/database.php 設定檔中加上下列設定:

1use Illuminate\Database\DBAL\TimestampType;
2 
3'dbal' => [
4 'types' => [
5 'timestamp' => TimestampType::class,
6 ],
7],
1use Illuminate\Database\DBAL\TimestampType;
2 
3'dbal' => [
4 'types' => [
5 'timestamp' => TimestampType::class,
6 ],
7],
exclamation

使用 doctrine/dbal 套件時,可修改的欄位型別有:bigInteger, binary, boolean, char, date, dateTime, dateTimeTz, decimal, double, integer, json, longText, mediumText, smallInteger, string, text, time, tinyText, unsignedBigInteger, unsignedInteger, unsignedSmallInteger, 與 uuid`。

重新命名欄位

若要重新命名欄位,可使用 Schema Builder 所提供的 renameColumn 方法:

1Schema::table('users', function (Blueprint $table) {
2 $table->renameColumn('from', 'to');
3});
1Schema::table('users', function (Blueprint $table) {
2 $table->renameColumn('from', 'to');
3});

在舊版資料庫中重新命名欄位

若你使用的資料庫版本比下列版本還要老舊,則請確定有在重新命名欄位前使用 Composer 套件管理員安裝 doctrine/dbal 函式庫:

  • MySQL < 8.0.3
  • MariaDB < 10.5.2
  • SQLite < 3.25.0

刪除欄位

若要移除欄位,可使用 Schema Builder 所提供的 dropColumn 方法:

1Schema::table('users', function (Blueprint $table) {
2 $table->dropColumn('votes');
3});
1Schema::table('users', function (Blueprint $table) {
2 $table->dropColumn('votes');
3});

可以傳入一組欄位名稱的陣列給 dropColumn 方法來一次移除多個欄位:

1Schema::table('users', function (Blueprint $table) {
2 $table->dropColumn(['votes', 'avatar', 'location']);
3});
1Schema::table('users', function (Blueprint $table) {
2 $table->dropColumn(['votes', 'avatar', 'location']);
3});

在舊版資料庫中移除欄位

若使用 3.35.0 版以前的 SQLite,則必須先使用 Composer 套件管理員安裝 doctrine/dbal 套件,才能使用 dropColumn。使用這個套件時,不支援在單一 Migration 內移除或修改多個欄位。

可用的指令別名

Laravel 提供了多種可用來刪除常見欄位型別的方便方法。下表中說明了這些方法:

指令說明
$table->dropMorphs('morphable');刪除 morphable_idmorphable_type 欄位。
$table->dropRememberToken();刪除 remember_token 欄位。
$table->dropSoftDeletes();刪除 deleted_at 欄位。
$table->dropSoftDeletesTz();dropSoftDeletes() 方法的別名。
$table->dropTimestamps();刪除 created_atupdated_at 欄位。
$table->dropTimestampsTz();dropTimestamps() 方法的別名。

索引

建立索引

Laravel 的 Schema Builder 支援多種類型的索引。下列為一個建立新 email 欄位並指定該欄位值不可重複(Unique)的範例。若要建立索引,我們可以將 unique 方法串聯到欄位定義之後呼叫:

1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('users', function (Blueprint $table) {
5 $table->string('email')->unique();
6});
1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('users', function (Blueprint $table) {
5 $table->string('email')->unique();
6});

或者,我們也可以在定義完欄位後再建立索引。若要在定義欄位完後才建立索引,我們需要在 Schema Builder Blueprint 上呼叫 unique 方法。該方法的引數為要設為 Unique 索引的欄位名稱:

1$table->unique('email');
1$table->unique('email');

我們也可以傳入一組欄位的陣列給索引方法,以建立一個複合式(Compound) / 組合式(Composite)的索引

1$table->index(['account_id', 'created_at']);
1$table->index(['account_id', 'created_at']);

建立索引時,Laravel 會自動依據資料表名稱、欄位名稱、索引類型等來產生索引名稱。不過,我們也可以傳入第二個因數給該方法來自行指定索引名稱:

1$table->unique('email', 'unique_email');
1$table->unique('email', 'unique_email');

可用的索引類型

Laravel 的 Schema Builder Blueprint 為 Laravel 所支援的各種索引類型都提供了方法。每個索引方法都接受可選的第二引數,可用來指定索引的名稱。若未提供索引名稱,則會自動使用索引的資料表名稱、欄位名稱、以及索引型別等來產生索引名稱。下表為可用的索引方法:

指令說明
$table->primary('id');新增主索引鍵(Primary Key)
$table->primary(['id', 'parent_id']);新增複合式索引鍵(Composite Keys)
$table->unique('email');新增 Unique(不重複) 索引
$table->index('state');新增索引。
$table->fullText('body');新增全文索引 (MySQL/PostgreSQL)。
$table->fullText('body')->language('english');以指定的語言來新增全文索引 (PostgreSQL)。
$table->spatialIndex('location');新增 Spatial(空間) 索引 (除了 SQLite)。

重新命名索引

若要重新命名索引,可使用 Schema Builder Blueprint 提供的 renameIndex 方法。該方法的第一個引數為目前的索引名稱,而第二個引數則為要修改的名稱:

1$table->renameIndex('from', 'to')
1$table->renameIndex('from', 'to')
exclamation

若專案使用 SQLite 資料庫,則在使用 renameIndex 前必須先使用 Composer 套件管理員安裝 doctrine/dbal 套件:

刪除索引

若要刪除索引,則需要指定索引的名稱。預設情況下,Laravel 會自動依照資料表名稱、索引欄位名稱、以及索引類型來指派索引名稱。範例如下:

指令說明
$table->dropPrimary('users_id_primary');在「users」資料表內刪除主索引鍵(Primary Key)
$table->dropUnique('users_email_unique');從「users」資料表中刪除 Unique 索引。
$table->dropIndex('geo_state_index');從「geo」資料表中刪除一般索引。
$table->dropFullText('posts_body_fulltext');從「users」資料表中刪除全文索引。
$table->dropSpatialIndex('geo_location_spatialindex');從「geo」資料表中刪除 Spatial 索引 (除了 SQLite)。

在刪除索引上時,若傳入一組欄位陣列給該方法,則會自動依照資料表名稱、欄位名稱、索引型別等產生慣例式的索引名稱:

1Schema::table('geo', function (Blueprint $table) {
2 $table->dropIndex(['state']); // 刪除 'geo_state_index' 索引
3});
1Schema::table('geo', function (Blueprint $table) {
2 $table->dropIndex(['state']); // 刪除 'geo_state_index' 索引
3});

Foreign Key Constraint

在 Laravel 中,也可以建立 Foreign Key Constraint(外部索引鍵的條件約束)。使用 Foreigh Key Constraint,就可在資料庫層級上強制確保參照的完整性。舉例來說,我們來在 posts 資料表上定義一個參照到 users 資料表 id 欄位的 user_id 欄位:

1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('posts', function (Blueprint $table) {
5 $table->unsignedBigInteger('user_id');
6 
7 $table->foreign('user_id')->references('id')->on('users');
8});
1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('posts', function (Blueprint $table) {
5 $table->unsignedBigInteger('user_id');
6 
7 $table->foreign('user_id')->references('id')->on('users');
8});

由於這個語法有點複雜,因此 Laravel 提供了一個額外的、簡潔的方法。這種方法使用慣例,來提供更好的開發者經驗(DX)。在使用 foreignID 方法建立欄位時,上方的範例可以被這樣改寫:

1Schema::table('posts', function (Blueprint $table) {
2 $table->foreignId('user_id')->constrained();
3});
1Schema::table('posts', function (Blueprint $table) {
2 $table->foreignId('user_id')->constrained();
3});

foreignId 方法會建立一個 UNSIGNED BIGINT 或相等欄位,而 constrained 方法會使用慣例來判斷要參照的資料表與欄位。若表名不符合 Laravel 的慣例,可在 constrained 方法的第二個引數上指定資料表名稱:

1Schema::table('posts', function (Blueprint $table) {
2 $table->foreignId('user_id')->constrained('users');
3});
1Schema::table('posts', function (Blueprint $table) {
2 $table->foreignId('user_id')->constrained('users');
3});

也可以指定 Constraint「on delete」與「on update」屬性的動作:

1$table->foreignId('user_id')
2 ->constrained()
3 ->onUpdate('cascade')
4 ->onDelete('cascade');
1$table->foreignId('user_id')
2 ->constrained()
3 ->onUpdate('cascade')
4 ->onDelete('cascade');

或者,也有比較描述性的語法可以設定這些動作:

方法說明
$table->cascadeOnUpdate();更新時應串聯更新 (Cascade)。
$table->restrictOnUpdate();更新時應限制更新 (Restricted)。
$table->cascadeOnDelete();刪除時應串聯刪除 (Cascade)。
$table->restrictOnDelete();刪除時應限制刪除 (Restricted)。
$table->nullOnDelete();刪除時應將外部索引鍵設為 null。

若有額外的欄位修飾詞,應將其放在 constrained 方法前呼叫:

1$table->foreignId('user_id')
2 ->nullable()
3 ->constrained();
1$table->foreignId('user_id')
2 ->nullable()
3 ->constrained();

刪除外部索引鍵

若要刪除 Foreign Key(外部索引鍵),可使用 dropForeign 方法,傳入要刪除的 Foreign Key Constraint(外部索引鍵條件約束) 名稱即可。Foreign Key Constraint 使用與索引相同的命名規範。換句話說,Foreign Key Constraint 的名稱會使用要約束的資料表名稱與欄位名稱組成,並在後方加上「_foreign」後置詞:

1$table->dropForeign('posts_user_id_foreign');
1$table->dropForeign('posts_user_id_foreign');

或者,也可以傳入一組包含欄位名稱的陣列給 dropForeign 方法。這組陣列中應包含 Foreign Key 的名稱。傳入該陣列後,Laravel 會使用約束的命名管理來將該陣列轉換為 Foreign Key Constraint 的名稱:

1$table->dropForeign(['user_id']);
1$table->dropForeign(['user_id']);

啟用/禁用 Foreign Key Constraint

可以使用下列方法來在 Migration 中啟用或禁用 Foreign Key Constraint:

1Schema::enableForeignKeyConstraints();
2 
3Schema::disableForeignKeyConstraints();
4 
5Schema::withoutForeignKeyConstraints(function () {
6 // 此閉包內會禁用外部索引鍵條件限制 (Foreign Key Constraint)...
7});
1Schema::enableForeignKeyConstraints();
2 
3Schema::disableForeignKeyConstraints();
4 
5Schema::withoutForeignKeyConstraints(function () {
6 // 此閉包內會禁用外部索引鍵條件限制 (Foreign Key Constraint)...
7});
exclamation

SQLite 預設會禁用 Foreign Key Constraint。使用 SQLite 時,在 Migration 中建立 Foreign Key Constraint 前,請先檢查是否有在資料庫設定中啟用 Foreign Key 支援。此外,SQLite 只支援在建立資料表時設定 Foreign Key,而無法在修改資料表時新增

Event

為了讓開發更方便,Migration 中的各個動作都會分派 [Event(/docs/master/events)](事件)。下列的所有 Event 都繼承了 Illuminate\Database\Events\MigrationEvent 類別:

類別說明
Illuminate\Database\Events\MigrationsStarted即將執行一批 Migration。
Illuminate\Database\Events\MigrationsEnded已完成執行一批 Migration。
Illuminate\Database\Events\MigrationStarted即將執行單一 Migration。
Illuminate\Database\Events\MigrationEnded已完成執行單一 Migration。
Illuminate\Database\Events\SchemaDumped已傾印完成資料庫結構。
Illuminate\Database\Events\SchemaLoaded已載入現有的資料庫結構傾印。
翻譯進度
100% 已翻譯
更新時間:
2024年6月30日 上午8:27:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.