Laravel 的模型工厂使用小结

file
Laravel 5.1 之后新增了一个名为模型工厂的功能,用来快速构建一个「假」的模型。

让我们构建一个小小的应用程序来深入了解一下这个功能。这些有两个最大的测试和数据填充的用例。

开始

现在假设我们要做一个简单的报修系统,用来收集用户的问题反馈。报修的用户使用自带的 users 表,然后再创建一个新的问题表。

安装一个新的应用程序:

laravel new support

接下来,用 artisan 命令来创建我们的问题表的模型和迁移。 使用 make:model 命令传递 -m 或 -migration 来创建。

php artisan make:model Issues -m

这条命令会在 app/ 中生成 Issues.php 和 database/migrations/ 中生成 create_issues_table 迁移文件。

现在我们要在 app/User.php 模型文件中关联用户表与问题表之间的一对多关系:

public function issues()
{
  return $this->hasMany('issues');
}

如果你不想使用默认的配置,就编辑 .env 并更改数据库信息。

现在我们来填写这些迁移文件。

创建迁移

打开 database/migrations/datetime_create_issues_table.php 并如下修改 up 方法:

public function up()
{
  Schema::create('issues', function (Blueprint $table) {
      $table->increments('id');
      $table->integer('user_id');
      $table->string('subject');
      $table->text('description');
      $table->timestamps();
  });
}

现在运行迁移:

php artisan migrate

它应该输出以下内容:

Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrated: 2015_10_03_141020_create_issues_table

接下来,我们来填充数据,以便我们有数据来进行其他操作。

建立数据填充

数据填充是一种用程序运行的方式将数据插入到数据库中,其优点就是可以将虚拟数据快速地导入到应用程序中。 可以节省不少捏数据的时间呢!

先为用户表填充数据。 用以下命令生成编写数据填充的文件:

php artisan make:seeder UserTableSeeder

当然,问题表的数据填充也要创建

php artisan make:seeder IssueTableSeeder

现在打开 database/seeds/DatabaseSeeder.php 文件,并修改 run 方法:

public function run()
{
  Model::unguard();

  $this->call(UserTableSeeder::class);
  $this->call(IssueTableSeeder::class);

  Model::reguard();
}

记得么?用户和问题是有关联关系的,也就是说我们不能就这样运行这些填充方法。接下来我们看看如何用模型工厂来向程序说明他们之间的关联关系。

创建模型工厂

以前填充数据的时候,可以使用 Eloquent 或 Laravel的查询构建器。 而现在,引入模型工厂之后,就可以使用它们来构建可用于测试的填充的数据和「虚拟」模型。

打开 database/factories/ModelFactory.php,你会看到下面这些:

$factory->define(App\User::class, function (Faker\Generator $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => bcrypt(str_random(10)),
        'remember_token' => str_random(10),
    ];
});

解释一下,我们把 App\User::class 模型作为 define() 的第一个参数,然后定义了字段中的数据的回调。 这个回调中引入了 Faker,它是一个会生成假数据的 PHP 库,功能强大,可用于多种不同的场类型。 这是一个示例:

  • $fake->name – “John Smith”
  • $faker->email – “tkshlerin@collins.com”

注意:如果你的程序要发送真实的电子邮件,那就要使用 $faker->safeEmail。 原因是 $faker->email 可能会生成一个真实的电子邮件,而 safeEmail 以测试目的使用了由 RFC2606 保留的 example.org 。

现在,为问题模型创建一个新的工厂:

$factory->define(App\Issues::class, function (Faker\Generator $faker) {
    return [
        'subject' => $faker->sentence(5),
        'description' => $faker->text(),
    ];
});

现在来看看数据填充的类,并使用这些工厂生成数据。

打开 UserTableSeeder 并修改 run 方法:

public function run()
{
  factory(App\User::class, 2)->create()->each(function($u) {
    $u->issues()->save(factory(App\Issues::class)->make());
  });
}

现在简单讲一下这段代码的作用:在 factory(App\User::class, 2)->create() 这句中写明了,我们需要构建 User 类,在数据库中创建两个用户。然后用 collection 的 each 方法为每个被创建的用户建立关联的问题数据。

运行迁移填充的命令:

$ php artisan migrate --seed
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrated: 2015_10_03_141020_create_issues_table
Seeded: UserTableSeeder

现在我们数据库里面就有我们要的表,表中包含了模型工厂为我们生成的数据。

用 Laravel 模型工厂进行测试

拥有模型工厂的主要好处是我们可以在测试套件中使用这些。 为我们的问题创建一个新的测试:

php artisan make:test IssuesTest

打开 tests/IssuesTest.php 并添加一个新的方法来测试问题的创建:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class IssuesTest extends TestCase
{
    use DatabaseTransactions;

    public function testIssueCreation()
    {
      factory(App\Issues::class)->create([
          'subject' => 'NAIL POPS!'
      ]);

      $this->seeInDatabase('issues', ['subject' => 'NAIL POPS!']);
    }
}

在这个测试中,我们使用 DatabaseTransactions trait,以便每个测试都被包装在一个事务中。我们在 testIssueCreation 中写测试添加问题的功能。 create 方法传递关联数组,能覆盖原始模型工厂定义中存储的内容。然后我们使用 seeInDatabase 去搜索数据库中是否存在给定的字段和值。

看到这里你应该知道怎么用模型工厂去测试和填充数据了吧?不懂就翻回去多看几遍,懂了的话,来看看其他一些进阶的功能!

其他特点

现在有件尴尬的事情,就是我忘了在数据库中添加一个字段用于写入问题的解决方法。

多工厂类型

在为这个字段 completed 添加新的迁移之后,我们需要对模型工厂进行调整,定义一个这样的新工厂:

$factory->defineAs(App\Issues::class, 'completed', function ($faker) use ($factory) {
    $issue = $factory->raw(App\Issues::class);

    return array_merge($issue, ['completed' => true]);
});

使用数据库的字段 completed 作为第二个参数,在回调中,我们创建了一个新的问题,并将 completed 合并这个问题里面。

也就是说,如果你想要创建一个「已完成」的问题,那就要这样子写:

$completedIssue = factory(App\Issue::class, 'completed')->make();

make VS create

你可能已经注意到,上面调用了 ->make() 而不是 ->create()。 这两种方法做了两件不同的事情:create 尝试将其存储在数据库中,跟 Eloquent 中的 save 方法一样。 而 make 仅仅只是创建了模型,不会向数据库插入数据。 如果你熟悉 Eloquent,make 执行起来就像这样:

$issue = new \App\Issue(['subject' => 'My Subject']);

扩展

在 Laravel 5.3.17 中你可以在模型工厂定义不同的「状态」。

现在我们要为用户定义身份:

$factory->define(App\User::class, function (Faker\Generator $faker) {
  return [
    'name' => $faker->name,
    'email' => $faker->safeEmail,
  ];
});

这个用户可以是管理员:

$factory->state(\App\User::class, 'admin', function (\Faker\Generator $faker) {
  return [
    'is_admin' => 1,
  ];
});

或者是主席:

$factory->state(\App\User::class, 'moderator', function (\Faker\Generator $faker) {
  return [
    'is_moderator' => 1,
  ];
});

然后你可以这样调用:

// Create 5 users
factory(\App\User::class, 5)->create();

// Create 5 Admins
factory(\App\User::class, 5)->states('admin')->create();

// Create 5 Moderators
factory(\App\User::class, 5)->states('moderator')->create();

// Create 5 Admins that are also moderators
factory(\App\User::class, 5)->states('admin', 'moderator')->create();

本文翻译改编自 Eric L. BarnesLearn to use Model Factories in Laravel

本作品采用《CC 协议》,转载必须注明作者和本文链接
Stay Hungry, Stay Foolish.
本帖由系统于 6年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2

刚好在写公司项目需要填充一下数据,记得以前翻译过类似的。找了好久才找到:joy: 现在重新发上来存着方便以后查看

6年前 评论

在运行命令 php artisan migrate --seed 时,会报如下错误:

  [Symfony\Component\Debug\Exception\FatalErrorException]
  Class 'issues' not found

需要将 app/User.php中模型文件关联用户表与问题表的一对多关系改为:

public function issues()
{
    return $this->hasMany('App\Issues');
}
6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!