Migrations

资料库迁移是以执行一个个档案来逐步建立资料库表单的作法,可以纪录资料库变化的过程。逐步变更可以降低对已上线系统的影响,也能在出错的时候退回到还能正常运作时的资料库结构。

可以先来看看 Laravel 预先建立好的 Migration 档案

// database\migrations\2014_10_12_000000_create_users_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

这是用来建立 User 表单的迁移,首先可以看到主要分成了 up / down 两个功能, up 就是推进迁移时执行, down 则是退回迁移步骤时执行。

up 不一定是建立或增加栏位,可能会是删除栏位,变更栏位属性(移除 Foreign key 等),或是删除表单也有可能,而 down 中需要对应 up 的内容执行相反的指令。

如果还没跑过可以先跑一次 Migration 建立使用者登入相关的表单

sail artisan migrate

成功的话可以在资料库看到新建的表单

建立 Migration

用指令建立 Migration 档案

sail artisan make:migration <migratoin_name>

这样建出来的 Migration 会放在 database/migrations 目录下,并且 up / down 方法都是空的。

如果 Migration 是用来建立表单的话,可以在指令中指定要建立的表单名称

 sail artisan make:migration create_todos_table --create todos

这样建立的 Migration 中就会预设好建立表单的指令, down 也会填好删除表单的指令。

// database\migrations\XXXX_XX_XX_XXXXX_create_todos_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTodosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('todos', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('todo');
    }
}

另外说一下 Migration 档案都会在档名前面加上时间戳,以辨别 Migration 的执行顺序。

如果不是想建立表单而是变更某个表单的内容的话,指令会变成

sail artisan make:migration update_todos_table --table todos

另外如果想指定 migrations 建立的位置,指令可以加上 path 参数

sail artisan make:migration update_todos_table  --table todos --path database/migrations/Todos

path 会是相对於专案目录的位置

Migration 建立表单

如果是在 Migration 中增改删除表单,都是利用 Schema 这个帮手。

Schema::create() 方法会建立新的表单

public function up()
{
    Schema::create('todos', function (Blueprint $table) {
        $table->id();
        $table->timestamps();
    });
}

up() 里是建立表单的话,down() 里就会写删除表单的方法

public function down()
{
    Schema::dropIfExists('todos');
}

关於表单的名称,一般会加上 's' ,这样之後建立 Eloquent ORM 的模型时,就不用在特地宣告模型对应的表单,Eloquent ORM 会自动对应,例如 Flight 模型会自动找 flights 表单进行查询。

当然如果名称对不上的话,手动宣告模型对应的表单就好。

Migration 建立栏位

建立表单的同时,可以宣告表单要有的栏位。

栏位的定义包含名称、型别、是否可为空等,这边列一些比较常用的。

$table->uuid('id'); // id -> 第一个参数是栏位名称,其他方法也一样

建立值为 UUID 的栏位,通常用来当主键,或宣告外键栏位。

$table->boolean('confirmed');
$table->integer('votes');
$table->float('amount', 3, 2); // 3->数字总数 , 2->小数位数 ,例: 1.23
$table->string('name', 100); //100->字数上限
$table->longText('description');
$table->dateTime('created_at');

常用型别以及日期时间型别

$table->json('options');

可以把大量资料包成 json 存起来,要用的时候再解析,我们这通常用来保存机器的通讯数据原始资料。

$table->timestamps();

一次建立常用的 created_at 跟 updated_at 栏位。

$table->string('description')->nullable();

nullable 宣告该栏位可为空值,没有宣告 nullable 的话当栏位没值会报错。

$table->bool('receive_ads')->default(true);

当未宣告栏位值的话自动填入预设的值,就不用宣告 nullable 了。

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

宣告必须为独特值的栏位,该栏位内的所有值必须是独一无二的,写入时若发现有重复会报错。

宣告关联

建立表单时也可以加上表单间的关联性,通常是宣告一个外键後绑定到其他表单的主键上。

$table->uuid('user_id');

$table->foreign('user_id')->references('id')->on('users');

要注意的是当宣告关连到 users 时, users 表单必须已经建立,而且有 id 这个栏位。

这是资料库层的限制,之後当写入值的时候如果 user_id 为空或找不到任何 users 表单中的参照,会报错。

可以进一步宣告当资料被删除的话要如何处里关联的资料。

像是建立一个 user_settings 表单,当中的资料都会关连到 user

public function up()
{
    Schema::create('user_settings', function (Blueprint $table) {
        $table->uuid('id');

        $table->uuid('user_id');

        $table->foreign('user_id')->references('id')->on('users')
            ->onDelete('cascade');

        $table->timestamps();
    });
}

宣告 onDelete('cascade') 的话,当 user 被删除时,跟他关联的 user_setting 资料也会被删除。

其他选项

onDelete('restrict'); //阻止删除动作,除非 user_setting 被删除不然 user 删不了
onDelete('set null'); //删除後 user_setting 的 user_id 为 null
                      //要将 user_id 设为 nullable 不然会报错

也有 onUpdate,当关联资料被更新时执行,选项跟 onDelete 一样。

更名、删除表单

可以更改既有的表单名称

Schema::rename($from, $to);

或是删除表单

Schema::drop('users'); //找不到表单的话会报错

Schema::dropIfExists('users'); 

变更栏位

要变更现存的表单内的栏位的话,用 Schema::table()

public function up()
{
    Schema::table('user_settings', function (Blueprint $table) {
        //
    });
}

如果是新增栏位,做法跟建立表单时相同,直接宣告就好

Schema::table('user_settings', function (Blueprint $table) {
    $table->integer('height');
});

如果要变更现有的栏位,首先要安装套件

sail composer require doctrine/dbal

然後用套件的 change 方法宣告是要变更现有栏位。

Schema::table('user_settings', function (Blueprint $table) {  
    $table->string('name', 50)->nullable()->change(); 
});

套件也有提供变更栏位名称的方法

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

然後是删除栏位的方法,这个也是套件的功能

Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('votes');
    $table->dropColumn(['avatar', 'location']); //删除多个
});

移除、变更外键关联

想将之前设定的外键关联移除的话

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

这样只会将关联性移除,栏位会保留

如果是要变更现有的关联关系的话,只能先移除关联後建立新的关联

$table->dropForeign(['user_id']);
$table->foreign('user_id')->references('id')->on('users')
            ->onDelete('set_null');

删除的话也是一样

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

设定主键

一般会用 id 栏位作为主键值

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

若查询时没有指定栏位,就会以主键栏位做查询。

也可以在宣告完所有栏位後再指定 primary 的栏位 ,阅读上会比较容易辨识到主键的设定

$table->uuid('id');

...

$table->primary('id');

注意 primary 宣告必须在栏位被建立後才能执行,不然会报错。

除了主键外有时也需要必须唯独特值的栏位

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

跟 primary 一样,也可以事後宣告

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

另外偶尔会有需要联合多个栏位为键值的设计

$table->primary(['account_id', 'created_at'],);
$table->unique(['account_id', 'created_at']); 

要移除键值的设定的话,以预设的键值名称作为参照

$table->dropPrimary('users_id_primary'); //移除 users 表单中 id 的 primary 设定
$table->dropUnique('users_email_unique'); //移除 users 表单中 email 的 unique 键

Laravel 预设的键值名称会以 "表单_栏位_属性" 构成,如果不想要预设的名称的话,可以在宣告键值时设定名称

$table->primary(['account_id', 'created_at'],"account_created_at");

之後移除时以自订的名称作为参照

$table->dropPrimary('account_created_at');

执行 Migration

执行 migration 的基础指令是

sail artisan migrate

这个指令会从最後一次 migration 的档案之後开始执行 migration。

如果想退回 migration

sail artisan migrate:rollback //退回一个 migration
sail artisan migrate:rollback --step=5  //退回 5 个
sail artisan migrate:reset //退回全部 

如果想要从头开始执行 migration

sail artisan migrate:refresh // 一步步退回全部後再执行全部
sail artisan migrate:fresh // 不执行退回而是直接删除所有表单,再执行全部

如果在执行完 migration 後想要植入种子资料

sail artisan migrate --seed

开发上最常用的就是整个资料库清空後重新建表单跟塞种子资料

sail artisan migrate:fresh --seed

References

Laravel Migrations
Laravel running migrations on "app/database/migrations" folder recursively

<<:  07 - Zim - Zsh 配置框架与它的插件

>>:  从零开始学3D游戏设计:环境後制效果

Day28 - HTML 与 CSS (10) - 网页设计

网页设计 环境:将多页面的档案建立,连结确认彼此间的关系 layout (布局):评估多个页面皆会出...

Day 22-制作购物车之前端架构2&Navbar设计

设计的部分就不多做分析,主要呈现实作成果。 以下内容有参考教学影片,底下有附网址。 (内容包括我的不...

Day 24:专案06 - 股市趋势图01 | 单月股市API、Pandas

各位早安,今天是第24天,但其实爬虫的技巧大致上已经教得差不多了,而且我猜会看我的文章的人,应该都想...

Day11 Lab 1 - 简单的Object storage系统

我们的第一个Lab就从Simple object system开始,程序码我放在这 https://...

C#入门之错误处理

在很多情况下,有些错误是我们可以预知的,就比如前面计算两个数相加的代码,在有些情况下,我们可以预知到...