数据库迁移与填充
数据库迁移
迁移就像是对数据库进行的版本控制,让您的团队能够轻松地去定义和共享程序的数据库结构。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 facade
的 create
方法来创建新的数据库表。 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();
});
创建表时,可以使用数据库结构构建器中的任何 列方法来定义表的列
检查表 / 列是否存在
可以使用 hasTable
和 hasColumn
方法来检查数据表或字段是否存在:
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 时,可以使用 charset
和 collation
属性指定创建表的字符集和排序规则
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);
要删除已存在的表, 可以使用 drop
或 dropIfExists
方法
Schema::drop('users');
Schema::dropIfExists('users');
字段
创建字段
使用 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');
});
可用的字段类型 数据库结构生成器包含构建表时可以指定的各种字段类型:
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