入门篇(三):通过填充器快速填充测试数据


我们在前两篇教程中分别介绍了如何连接到数据库,以及如何通过迁移文件定义表结构来创建或修改数据表,接下来,是时候在数据表里添加内容了。在 Laravel 框架中,如果想要快速填充测试数据到数据库,可以借助框架提供的填充器功能,通过填充器,我们可以非常方便地为不同数据表快速填充测试数据。

填充器简介

在应用根目录的 database/seeds 目录下,默认包含一个 DatabaseSeeder.php 文件。这就是 Laravel 自带的一个填充器示例文件,该填充器类提供了一个 run 方法,当我们运行填充命令时,就会调用该方法执行数据库填充。

填充器的运行

Laravel 提供了两种方式来运行填充器:一种是独立的填充命令,另一种是在运行迁移命令时通过指定标识选项在创建数据表时填充。

独立的填充命令如下:

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

这样,就可以在数据表 users 中看到新填充的用户记录了:

当然,我们可以在 UsersTableSeeder 类中定义多条插入语句来一次性插入多条记录。

通过模型工厂填充数据

以上编写填充器类填充数据到数据库虽然已经很方便了,但是每次插入一条记录都要编写一条语句或者手动指定插入数据,如果需要填充的测试数据有成千上万条,那不是要崩溃掉。有没有一种机制可以支持一次定义,多次填充呢?为了解决这个问题,我们需要引入一种模式,一次定义填充规则,在每次具体运行时,通过指定填充次数来决定填充多少条记录。模型工厂的概念应运而生:我们在一个 Eloquent 模型类(后面马上会讲到)上定义一个工厂方法,通过指定规则批量插入填充数据。

你可以想象,有了模型工厂的加持,会为我们日后测试带来多大的便利。现在,我们先抛开测试不谈,赶紧来看下如何在 Laravel 中定义模型工厂。

创建模型工厂

模型工厂位于 database/factories 目录下,Laravel 自带了一个用于填充 User 模型的模型工厂 UserFactory.php

<?php

use Faker\Generator as Faker;

/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/

$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(),比如我们在 UsersTableSeederrun 方法中通过模型工厂改写数据填充方法:

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 方法让变更生效。非常方便,也真正实现了一次定义,多处复用,以及在运行时指定填充记录数。

运行填充器的方式还是和填充器类中介绍的一样。比如我们还是通过运行 php artisan db:seed 命令来填充数据到数据库,此时,就可以看到新填充了 5 条记录:

注:本教程都以 Laravel 自带的 users 及对应 User 模型类为例进行演示,对于其他数据表和对应模型类,依样画葫芦就好。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 入门篇(二):通过迁移文件定义数据表结构

>> 下一篇: 入门篇(四):通过查询构建器实现简单的增删改查操作