laravel 入门(7)迁移文件和填充器
定义迁移#
数据表的每次变动(创建、修改、删除)都对应一个迁移文件,这些迁移文件位于 database/migrations 目录下,以日期时间为条件确定执行的先后顺序。每个迁移文件中包含一个迁移类,这个迁移类有两部分组成:负责执行数据库迁移的 up 方法,以及负责回滚此次迁移的 down 方法。
创建迁移文件#
Laravel 提供了一个 Artisan 命令 make:migration 帮助我们快速生成数据库迁移文件,该命名包含一个参数,就是要创建的迁移的名称,比如要创建 users 表对应迁移文件,可以通过 php artisan make:migration create_users_table 命令来完成。
此外,这个 Artisan 命令还支持两个可选的选项,--create= 用于指定要创建的数据表名称,以及 --table= 用于指定要修改的数据表名称,前者在定义创建数据表迁移文件时使用,后者在定义更新数据表迁移文件时使用,比如我们还是以 users 表为例:
php artisan make:migration create_users_table --create=users # 创建数据表迁移
php artisan make:migration alter_users_add_nickname --table=users # 更新数据表迁移
创建数据表#
有了迁移文件后,就可以在迁移文件对应迁移类的 up 方法中编写创建数据表的逻辑了,以 create_users_table 迁移为例:
public function up()
{
Schema::create('users', function (Blueprint $table) {
...
});
}
我们对数据库的迁移操作都是基于 Schema 门面来完成(底层对应的类是 Illuminate\Database\Schema\Builder),比如创建数据表,需要调用该门面的 create 方法,该方法的第一个参数是要创建的数据表的名称,第二个参数是一个闭包,其中定义的是新增数据表的所有字段信息。
创建新字段#
接下来,我们看一下如何为创建的数据表添加字段,前面我们已经说了,这个操作在 Schema::ceate 方法的第二个参数的闭包函数中完成:
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
我们为该闭包函数注入了 Blueprint 类实例,并通过该实例提供的方法完成数据表字段的定义。Blueprint 类为我们提供了丰富的数据表字段定义方法,通过这些方法我们完成所有与数据表字段相关的操作,包括新增字段、删除字段、修改字段、添加索引和外键等等。
关于 Blueprint 类提供的新增数据表字段方法,可以通过查看文档一目了然
构建字段额外属性#
绝大部分数据表字段都有自己的属性,比如长度、是否为空、默认值、注释信息等,比如我们可以为 users 表的 name 字段设置一些额外属性,将其长度设置为 100(默认是255),并且为该字段添加注释信息:
$table->string('name', 100)->comment('用户名');
关于字段其它可以设置的额外属性,可以查看文档中列改修器部分列出的属性列表。
删除表#
删除数据表很简单,调用 Schema::drop 方法即可,但是我们还有一个更好的方法 dropIfExists,该方法会先检查数据表是否存在,存在才会删除,create_users_table 迁移的 down 方法就是这么做的:
public function down()
{
Schema::dropIfExists('users');
}
修改表#
数据表创建完成后,并不意味着一成不变,随着需求的变动,可能需要对数据表结构进行调整,要修改一个数据表字段,千万不要直接到创建表的迁移文件中直接修改,而是以增量的方式新增一个迁移文件,比如我们如果要为 users 表新增一个 nickname 字段,可以通过以下命令新增一个迁移文件:
php artisan make:migration alter_users_add_nickname --table=users
这样就会在 database/migrations 目录下新增一个迁移文件,
我们是通过 Schema 门面上的 table 方法来修改数据表结构。
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('nickname', 100)->after('name')->nullable()->comment('用户昵称');
});
}
接下来,我们通过这个新增迁移类的 down 方法来演示如何删除一个已存在的字段:
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('nickname');
});
}
索引和外键#
添加索引#
对字段设置索引我们已经在 create_users_table 中看到过了:
$table->string('email')->unique();
通过这种调用方式,我们将 email 字段设置为了唯一索引。我们还可以为某个字段设置普通索引:
$table->string('name')->index();
主键索引通过 increments 方法创建,该方法会创建一个自动增长的主键索引:
$table->increments('id');
除了上述这些调用方式外,我们还可以通过另一种方式来为字段创建索引:
$table->primary('id');
$table->index('name');
$table->unique('email');
这样做的一个好处是一次支持传入多个字段,从而构建混合索引:
$table->index(['name', 'email']);
移除索引#
移除索引也很简单:
$table->dropIndex('name');
$table->dropUnique('email');
$table->dropPrimary('id');
添加、移除外键#
所谓外键指的是一张表的字段 A 引用另一张表的字段 B,那么字段 A 就是外键,通过外键可以建立起两张表之间的关联关系,这样,数据表之间就是有关联的了,而不是一个个孤立的数据集。
在迁移类中,如果我们想建立文章表中的 user_id 字段与用户表中的 id 之间的关联关系,可以通过这种方式来定义外键索引来实现:
$table->foreign('user_id')->references('id')->on('users');
如果你还想进一步指定外键约束(级联删除和更新,比如我们删除了 users 表中的某个 id 对应记录,那么其在文章表中对应 user_id 的所有文章会被删除,好危险!),可以通过 onDelete 和 onUpdate 方法来实现:
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade');
如果你想要删除外键索引,可以通过 dropForeign 方法来实现:
$table->dropForeign(['user_id']);
或者通过完整的外键索引名称来删除:
$table->dropForeign('posts_user_id_foreign');
注:不推荐使用外键,更不要使用外键约束功能,因为影响数据库性能,而且级联删除有可能造成非常严重的无法挽回的后果。关联关系我们建议通过业务逻辑代码来实现,比如后面介绍的 Eloquent ORM 专门提供了常见的关联关系方法。
运行迁移#
至此,我们已经介绍完了常见的数据表结构定义,基本能满足你99%的日常需求,接下来,我们来运行上面定义的迁移文件执行数据库变更。常见的操作有两种,一种是执行变更,一种是回滚变更。
执行变更很简单,通过:
php artisan migrate
就可以按照迁移文件生成时间的先后顺序依次执行所有的数据库迁移。
回滚要稍微复杂点,Laravel 支持多种形式的回滚,如果只回滚最后一个迁移文件的变更,可以通过:
php artisan migrate:rollback
来实现,如果要回滚多个迁移文件的变更,可以通过 --step= 指定步数(按照迁移文件生成时间逆序执行):
php artisan migrate:rollback --step=5
如果是要回滚所有迁移文件的变更,将数据库恢复到初始状态,需要运行以下命令:
php artisan migrate:reset
友情提示:在本地开发环境,如果数据库没有安装在本地,比如你使用的是 Homestead 或 Laradcok 的话,需要登录到对应环境执行 migrate 相关迁移命令,否则会因连不上数据库而报错。
填充器简介#
在应用根目录的 database/seeds 目录下,默认包含一个 DatabaseSeeder.php 文件。这就是 Laravel 自带的一个填充器示例文件,该填充器类提供了一个 run 方法,当我们运行填充命令时,就会调用该方法执行数据库填充。
填充器的运行#
Laravel 提供了两种方式来运行填充器:一种是独立的填充命令,另一种是在运行迁移命令时通过指定标识选项在创建数据表时填充。
独立的填充命令如下:
php artisan make:seeder UserTableSeeder
php artisan db:seed
php artisan db:seed --class=UsersTableSeeder
上述第一个 Artisan 命令会以 DatabaseSeeder 为入口类,调用该类的 run 方法,你可以将所有对其他填充器的调用定义在该方法中,例如:
$this->call(UsersTableSeeder::class);
这样,就可以一次性调用所有填充器啦。
当然,你也可以通过 --class= 选项指定运行某个填充器类的 run 方法。
此外,在某些时候,你可能希望在运行迁移命令的同时填充测试数据,尤其是在初始化一些演示项目的时候。这可以通过不指定值的 --seed 选项来实现:
php artisan migrate --seed
php artisan migrate:refresh --seed
第一条命令用于执行迁移命令时运行填充器类 DatabaseSeeder 填充数据,第二条命令用于回滚所有迁移并重新运行迁移同时填充初始化数据。
编写填充器类#
介绍完如何运行填充器,是时候来编写第一个填充器类了。我们可以通过如下 Artisan 命令为 users 表快速创建一个填充器类 UsersTableSeeder:
php artisan make:seeder UsersTableSeeder
该命令会在 database/seeds 目录下创建一个 UsersTableSeeder 填充器类,初始化代码如下:
<?php
use Illuminate\Database\Seeder;
class UsersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
}
}
我们可以将填充逻辑编写到 run 方法里:
public function run()
{
DB::table('users')->insert([
'name' => str_random(10),
'email' => str_random(10).'@gmail.com',
'password' => bcrypt('secret'),
]);
}
这里我们借助了查询构建器来插入数据,指定用户名和邮箱为长度不大于10的随机字符串,邮箱后缀是 @gmail.com,密码是对 secret 字符串进行加密后的字符串。
这样,我们就编写好了第一个填充器类,接下来,我们可以通过指定填充器类的方式将这条记录插入到数据库:
php artisan db:seed --class=UsersTableSeeder
你还可以在 DatabaseSeeder 类的 run 方法中运行这个填充器类:
public function run()
{
$this->call(UsersTableSeeder::class);
}
如果有多个填充器类,想要一次性运行,可以将它们都放到这个方法中调用。然后运行如下 Artisan 命令即可:
php artisan db:seed
通过模型工厂填充数据#
以上编写填充器类填充数据到数据库虽然已经很方便了,但是每次插入一条记录都要编写一条语句或者手动指定插入数据,如果需要填充的测试数据有成千上万条,那不是要崩溃掉。有没有一种机制可以支持一次定义,多次填充呢?为了解决这个问题,我们需要引入一种模式,一次定义填充规则,在每次具体运行时,通过指定填充次数来决定填充多少条记录。模型工厂的概念应运而生:我们在一个 Eloquent 模型类上定义一个工厂方法,通过指定规则批量插入填充数据。
你可以想象,有了模型工厂的加持,会为我们日后测试带来多大的便利。现在,我们先抛开测试不谈,赶紧来看下如何在 Laravel 中定义模型工厂。
创建模型工厂#
命令:
php artisan make:factory UserFactory --model=User
模型工厂位于 database/factories 目录下,Laravel 自带了一个用于填充 User 模型的模型工厂 UserFactory.php:
<?php
use Faker\Generator as Faker;
$factory->define(App\User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
'remember_token' => str_random(10),
];
});
注:在 Laravel 6.0 中,请使用 \Illuminate\Support\Str::random(10) 替代 str_random(10) 调用。
在模型工厂文件中,我们通过 $factory->define
方法来定义 User 模型的模型工厂,该方法的第一个参数是模型类,第二个参数是一个匿名函数,在该匿名函数中我们通过 Faker 类库提供的方法来定义字段规则,Faker 类库提供了丰富的字段规则帮助我们生成伪造字段值,这些规则可以在官方文档中查看,这里,我们使用 $faker->name
生成用户名,$faker->unique()->safeEmail
生成唯一的邮箱地址,最后再将这些字段模型返回。
调用模型工厂#
在调用这些模型工厂的时候,需要借助 Laravel 提供的全局辅助函数 factory(),比如我们在 UsersTableSeeder 的 run 方法中通过模型工厂改写数据填充方法:
public function run()
{
/*DB::table('users')->insert([
'name' => str_random(10),
'email' => str_random(10).'@gmail.com',
'password' => bcrypt('secret'),
]);*/
factory(\App\User::class, 5)->create();
}
我们将之前手动编写填写数据的方式注释起来,替换成新的模型工厂的方式,代码瞬间简洁了很多,由于我们在 UserFactory.php 中全局定义了 User 模型的模型工厂,所以在这里只需调用 factory 方法,传入对应模型类和要填充的记录数即可,最后再调用 create 方法让变更生效。非常方便,也真正实现了一次定义,多处复用,以及在运行时指定填充记录数。
运行填充器的方式还是和填充器类中介绍的一样。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?