数据库迁移与填充

数据库迁移

迁移就像是对数据库进行的版本控制,让您的团队能够轻松地去定义和共享程序的数据库结构。Laravel 的 Schema facade 提供了数据库相关的支持,可以在所有 Laravel 支持的数据库管理系统中创建和操作表。通常,迁移将使用此 Facade 创建和修改数据库的数据表和字段

生成迁移

使用 make:migration Artisan 命令 来创建迁移。新的迁移文件会放在 database/migrations 目录。所有的迁移文件名称都会包含一个时间戳,Laravel 将据此决定迁移文件运行的顺序。

php artisan make:migration create_flights_table

迁移文件内容

<?php

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

class CreateFlightsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->id();
            // 添加的内容
            $table->string('name');
            $table->string('airline');
            $table->timestamps();
        });
    }

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

运行迁移

执行 Artisan 命令 migrate,来运行所有未执行过的迁移

php artisan migrate

如果你想查看目前已经执行了哪些迁移,可以使用 Artisan 命令 migrate:status

php artisan migrate:status

回滚迁移

若要回滚最后一次迁移, 可以使用 rollback 命令。 此命令将回滚最后一次迁移的操作,其中可能包含多个迁移文件

php artisan migrate:rollback

可以在 rollback 命令后面加上 step 参数,来限制回滚迁移的个数。 例如,以下命令将回滚最近五次迁移

php artisan migrate:rollback --step=5

migrate:reset 命令可以回滚应用程序中的所有迁移

php artisan migrate:reset

使用单个命令同时进行回滚和迁移操作

migrate:refresh 命令不仅会回滚数据库的所有迁移还会接着运行 migrate 命令。 这个命令可以高效地重建整个数据库:

php artisan migrate:refresh

// 重置数据库,并运行所有的 seeds...
php artisan migrate:refresh --seed

使用 refresh 命令并提供 step 参数来回滚并再执行最后指定的迁移数。例如, 以下命令将回滚并重新执行最后五次迁移

php artisan migrate:refresh --step=5

删除所有表然后执行迁移

使用 migrate:fresh 会删去数据库中的所有表,随后执行命令 migrate

php artisan migrate:fresh

php artisan migrate:fresh --seed

数据表

创建数据表

可以使用 Schema facadecreate 方法来创建新的数据库表。 create 方法接受两个参数:第一个参数为数据表的名称,第二个参数是 Closure ,此闭包会接收一个用于定义新数据表的 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();
});

创建表时,可以使用数据库结构构建器中的任何 列方法来定义表的列

检查表 / 列是否存在

可以使用 hasTablehasColumn 方法来检查数据表或字段是否存在:

if (Schema::hasTable('users')) {
    // "users" 表存在...
}

if (Schema::hasColumn('users', 'email')) {
    // "users" 表存在,并且有 "email" 列...
}

数据库连接 & 表选项

如果要对非默认连接的数据库连接执行结构操作,可以使用 connection 方法

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

还可以使用其他一些属性和方法来定义表创建的其他地方。使用 MySQL 时,可以使用 engine 属性指定表的存储引擎

Schema::create('users', function (Blueprint $table) {
    $table->engine = 'InnoDB';

    // ...
});

使用 MySQL 时,可以使用 charsetcollation 属性指定创建表的字符集和排序规则

Schema::create('users', function (Blueprint $table) {
    $table->charset = 'utf8mb4';
    $table->collation = 'utf8mb4_unicode_ci';

    // ...
});

表的更新

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);

要删除已存在的表, 可以使用 dropdropIfExists 方法

Schema::drop('users');

Schema::dropIfExists('users');

字段

创建字段

使用 Schema facadetable 方法可以更新现有的数据表。如同 create 方法一样,table 方法会接受两个参数:一个是数据表的名称,另一个则是接收可以用来向表中添加字段的 Blueprint 实例的闭包

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

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

可用的字段类型 数据库结构生成器包含构建表时可以指定的各种字段类型:

Command Description
$table->bigIncrements('id'); 递增 ID(主键),相当于「UNSIGNED BIG INTEGER」
$table->bigInteger('votes'); 相当于 BIGINT
$table->binary('data'); 相当于 BLOB
$table->boolean('confirmed'); 相当于 BOOLEAN
$table->char('name', 100); 相当于带有长度的 CHAR
$table->date('created_at'); 相当于 DATE
$table->dateTime('created_at'); 相当于 DATETIME
$table->dateTimeTz('created_at'); 相当于带时区 DATETIME
$table->decimal('amount', 8, 2); 相当于带有精度与基数 DECIMAL
$table->double('amount', 8, 2); 相当于带有精度与基数 DOUBLE
$table->enum('level', ['easy', 'hard']); 相当于 ENUM
$table->float('amount', 8, 2); 相当于带有精度与基数 FLOAT
$table->geometry('positions'); 相当于 GEOMETRY
$table->geometryCollection('positions'); 相当于 GEOMETRYCOLLECTION
$table->increments('id'); 递增的 ID (主键),相当于「UNSIGNED INTEGER」
$table->integer('votes'); 相当于 INTEGER
$table->ipAddress('visitor'); 相当于 IP 地址
$table->json('options'); 相当于 JSON
$table->jsonb('options'); 相当于 JSONB
$table->lineString('positions'); 相当于 LINESTRING
$table->longText('description'); 相当于 LONGTEXT
$table->macAddress('device'); 相当于 MAC 地址
$table->mediumIncrements('id'); 递增 ID (主键) ,相当于「UNSIGNED MEDIUM INTEGER」
$table->mediumInteger('votes'); 相当于 MEDIUMINT
$table->mediumText('description'); 相当于 MEDIUMTEXT
$table->morphs('taggable'); 相当于加入递增的 taggable_id 与字符串 taggable_type
$table->multiLineString('positions'); 相当于 MULTILINESTRING
$table->multiPoint('positions'); 相当于 MULTIPOINT
$table->multiPolygon('positions'); 相当于 MULTIPOLYGON
$table->nullableMorphs('taggable'); 相当于可空版本的 morphs() 字段
$table->nullableTimestamps(); 相当于可空版本的 timestamps() 字段
$table->point('position'); 相当于 POINT
$table->polygon('positions'); 相当于 POLYGON
$table->rememberToken(); 相当于可空版本的 VARCHAR(100) 的 remember_token 字段
$table->smallIncrements('id'); 递增 ID (主键) ,相当于「UNSIGNED SMALL INTEGER」
$table->smallInteger('votes'); 相当于 SMALLINT
$table->softDeletes(); 相当于为软删除添加一个可空的 deleted_at 字段
$table->softDeletesTz(); 相当于为软删除添加一个可空的 带时区的 deleted_at 字段
$table->string('name', 100); 相当于带长度的 VARCHAR
$table->text('description'); 相当于 TEXT
$table->time('sunrise'); 相当于 TIME
$table->timeTz('sunrise'); 相当于带时区的 TIME
$table->timestamp('added_on'); 相当于 TIMESTAMP
$table->timestampTz('added_on'); 相当于带时区的 TIMESTAMP
$table->timestamps(); 相当于可空的 created_at 和 updated_at TIMESTAMP
$table->timestampsTz(); 相当于可空且带时区的 created_at 和 updated_atTIMESTAMP
$table->tinyIncrements('id'); 相当于自动递增 UNSIGNED TINYINT
$table->tinyInteger('votes'); 相当于 TINYINT
$table->unsignedBigInteger('votes'); 相当于 Unsigned BIGINT
$table->unsignedDecimal('amount', 8, 2); 相当于带有精度和基数的 UNSIGNED DECIMAL
$table->unsignedInteger('votes'); 相当于 Unsigned INT
$table->unsignedMediumInteger('votes'); 相当于 Unsigned MEDIUMINT
$table->unsignedSmallInteger('votes'); 相当于 Unsigned SMALLINT
$table->unsignedTinyInteger('votes'); 相当于 Unsigned TINYINT
$table->uuid('id'); 相当于 UUID
$table->year('birth_year'); 相当于 YEAR

字段修饰

除了上述列出的字段类型之外, 还有几个可以在添加字段时使用的 修饰符 。例如,如果要把字段设置为 可空,就使用 nullable 方法

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

Schema::table('users', function (Blueprint $table) {
    $table->string('email')->nullable();
});

下表时所有可用的列修饰符。此列表不包括索引修饰符

修饰符 描述
->after('column') 将该列放在其它字段「之后」(MySQL)
->autoIncrement() 设置 INTEGER 类型的列为自动递增 (主键)
->charset('utf8mb4') 为该列指定字符集 (MySQL)
->collation('utf8mb4_unicode_ci') 为该列指定排序规则 (MySQL/PostgreSQL/SQL Server)
->comment('my comment') 为该列添加注释 (MySQL/PostgreSQL)
->default($value) 为该列指定一个「默认值」
->first() 将该列放在该表「首位」 (MySQL)
->from($integer) 设置自动递增字段的起始值 (MySQL / PostgreSQL)
->nullable($value = true) 允许 NULL 值插入到该列
->storedAs($expression) 创建一个存储生成的列 (MySQL)
->unsigned() 设置 INTEGER 类型的字段为 UNSIGNED (MySQL)
->useCurrent() 设置 TIMESTAMP 类型的列使用 CURRENT_TIMESTAMP 作为默认值
->useCurrentOnUpdate() 将 TIMESTAMP 类型的列设置为在更新时使用 CURRENT_TIMESTAMP 作为新值
->virtualAs($expression) 创建一个虚拟生成的列 (MySQL)
->generatedAs($expression) 使用指定的序列选项创建标识列 (PostgreSQL)
->always() 定义序列值优先于标识列的输入 (PostgreSQL)

修改字段

在修饰字段之前,请确保你已经通过 Composer 包管理器安装了 doctrine/dbal 包。Doctrine DBAL 库用于确定字段的当前状态, 并创建对该字段进行指定调整所需的 SQL 查询

composer require doctrine/dbal

更新字段属性

change 方法可以将现有的字段类型修改为新的类型或修改属性。 比如,你可能想增加。字符串字段的长度,可以使用 change 方法把 name 字段的长度从 25 增加到 50

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

同样可以使用 nullable 将字段修改为允许为空

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

只有以下字段类型能被 修改

bigInteger, binary, boolean, date, dateTime, dateTimeTz, decimal, integer,
json, longText, mediumText, smallInteger, string, text, time,
unsignedBigInteger, unsignedInteger, unsignedSmallInteger, 和 uuid, timestamp

重命名字段

可以使用结构生成器上的 renameColumn 方法来重命名字段。在重命名字段前, 请确保你的 composer.json 文件内已经加入 doctrine/dbal 依赖

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

当前不支持 enum 类型的字段重命名

删除字段

可以使用结构生成器上的 dropColumn 方法来删除字段。如果使用的是 SQLite 数据库,必须在调用 dropColumn 方法之前通过 Composer 包管理器安装了 doctrine/dbal

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

可以传递一个字段数组给 dropColumn 方法来删除多个字段

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

不支持在使用 SQLite 数据库时在单个迁移中删除或修改多个字段

可用的命令别名

命令 说明
$table->dropMorphs('morphable'); 删除 morphable_id 和 morphable_type 字段
$table->dropRememberToken(); 删除 remember_token 字段
$table->dropSoftDeletes(); 删除 deleted_at 字段
$table->dropSoftDeletesTz(); dropSoftDeletes() 方法的别名
$table->dropTimestamps(); 删除 created_at 和 updated_at 字段
$table->dropTimestampsTz(); dropTimestamps() 方法别名

索引

创建索引

结构生成器支持多种类型的索引。首先,先指定字段值唯一,即简单地在字段定义 之后链式调用 unique 方法来创建索引,例如

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

Schema::table('users', function (Blueprint $table) {
    $table->string('email')->unique();
});

或者,也可以在定义完字段之后创建索引。例如

$table->unique('email');

甚至可以将数组传递给索引方法来创建一个复合(或合成)索引

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

Laravel 会自动生成一个合理的索引名称,但也可以传递第二个参数来自定义索引名称

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

可用的索引类型

命令 说明
$table->primary('id'); 添加主键
$table->primary(['id', 'parent_id']); 添加复合主键
$table->unique('email'); 添加唯一索引
$table->index('state'); 添加普通索引
$table->spatialIndex('location'); 添加空间索引(不支持 SQLite)

索引长度 & MySQL / MariaDB

默认情况下,Laravel 使用 utf8mb4 编码。如果你是在版本低于 5.7.7 的 MySQL 或者版本低于 10.2.2 的 MariaDB 上创建索引,那就需要手动配置数据库迁移的默认字符串长度。 也就是说,你可以通过在 App\Providers\AppServiceProvider 类的 boot 方法中调用 Schema::defaultStringLength 方法来配置默认字符串长度:

use Illuminate\Support\Facades\Schema;

/**
 * 引导任何应用程序「全局配置」
 *
 * @return void
 */
public function boot()
{
    Schema::defaultStringLength(191);
}

当然,你也可以选择开启数据库的 innodb_large_prefix 选项。至于如何正确开启,请自行查阅数据库文档

重命名索引

若要重命名索引,需要调用 renameIndex 方法。此方法接受当前索引名称作为其第一个参数,并将所需名称作为其第二个参数

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

删除索引

若要移除索引, 则必须指定索引的名称。Laravel 默认会自动给索引分配合理的名称。其将数据表名称、索引的字段名称及索引类型简单地连接在了一起。举例如下

命令 说明
$table->dropPrimary('users_id_primary'); 从 「users」 表中删除主键
$table->dropUnique('users_email_unique'); 从 「users」 表中删除 unique 索引
$table->dropIndex('geo_state_index'); 从 「geo」 表中删除基本索引
$table->dropSpatialIndex('geo_location_spatialindex'); 从 「geo」 表中删除空间索引(不支持 SQLite

如果将字段数组传给 dropIndex 方法,会删除根据表名、字段和键类型生成的索引名称

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

外键约束

Laravel 还支持创建用于在数据库层中的强制引用完整性的外键约束。例如,让我们在 posts 表上定义一个引用 users 表的 id 字段的 user_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 额外提供了更简洁的方法,基于约定来提供更好的开发人员体验。上面的示例还可以这么写:

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

foreignId 方法是 unsignedBigInteger 的别名,而 constrained 方法将使用约定来确定所引用的表名和列名。如果表名与约定不匹配,可以通过将表名作为参数传递给 constrained 方法来指定表名:

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

可以为约束的「on delete」和「on update」属性指定所需的操作:

$table->foreignId('user_id')
      ->constrained()
      ->onUpdate('cascade')
      ->onDelete('cascade');

当使用任意 字段修饰符 的时候,必须在调用 constrained 之前调用:

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

删除外键

要删除一个外键,你需要使用 dropForeign 方法,将要删除的外键约束作为参数传递。外键约束采用的命名方式与索引相同。即,将数据表名称和约束的字段连接起来,再加上 _foreign 后缀:

$table->dropForeign('posts_user_id_foreign');

或者,可以给 dropForeign 方法传递一个数组,该数组包含要删除的外键的列名。数组将根据 Laravel 的 结构生成器使用的约束名称约定自动转换:

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

更改外键约束

可以在迁移文件中使用以下方法来开启或关闭外键约束:

Schema::enableForeignKeyConstraints();

Schema::disableForeignKeyConstraints();

数据库填充

Laravel 可以用 seed 类轻松地为数据库填充测试数据。所有的 seed 类都存放在 database/seeds 目录下。你可以任意为 seed 类命名,但是更应该遵守类似 UsersTableSeeder 的命名规范。Laravel 默认定义的一个 DatabaseSeeder 类。可以在这个类中使用 call 方法来运行其它的 seed 类从而控制数据填充的顺序

编写 Seeders

运行 Artisan 命令 make:seeder 生成 Seeder,框架生成的 seeders 都放在 database/seeders 目录下:

php artisan make:seeder UserSeeder

在默认的 DatabaseSeeder 类中的 run 方法中添加一条数据插入语句

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

class DatabaseSeeder extends Seeder
{
    /**
     * 执行数据库填充
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->insert([
            'name' => Str::random(10),
            'email' => Str::random(10).'@gmail.com',
            'password' => Hash::make('password'),
        ]);
    }
}

使用模型工厂

手动为每个模型填充指定属性很麻烦。作为替代方案,你可以使用 model 工厂 轻松地生成大量数据库数据。

例如,创建 50 个用户并为每个用户创建关联

use App\Models\User;

/**
 * 执行数据库填充
 *
 * @return void
 */
public function run()
{
    User::factory()
            ->count(50)
            ->hasPosts(1)
            ->create();
}

调用其他 Seeders

DatabaseSeeder 类中,你可以使用 call 方法来运行其它的 seed 类。使用 call 方法可以将数据填充拆分成多个文件,这样就不会使单个 seeder 变得非常大。只需简单传递要运行的 seeder 类名称即可

/**
 * 执行数据库填充
 *
 * @return void
 */
public function run()
{
    $this->call([
        UserSeeder::class,
        PostSeeder::class,
        CommentSeeder::class,
    ]);
}

运行 Seeders

可以使用 Artisan 命令 db:seed 来填充数据库。默认情况下, db:seed 命令将运行 Database\Seeders\DatabaseSeeder 类,这个类又可以调用其他 seed 类。不过,您也可以使用 --class 选项来指定一个特定的 seeder 类

php artisan db:seed

php artisan db:seed --class=UserSeeder

还可以使用 migrate:fresh 命令结合 --seed 选项,这将删除数据库中所有表并重新运行所有迁移。此命令对于完全重建数据库非常有用

php artisan migrate:fresh --seed
powered by GitbookEdit Time: 2023-04-08 10:28:32