(一) 有用的数据填充 - Build API For Your Company 系列

说明

在开发当中,当我们完成数据库的设计和实现之后,我们往往会数据库中储存一些伪造的数据进行测试API是否调用成功,而不是真实的数据。这样在开发中你可以不用处理对数据的维护工作。

Laravel 可以非常简单的使用数据填充类 seed classes 帮你生成一些测试数据放到数据库中去。如果你还没有使用过,那么官方的一些简单介绍可以帮你快速的入门。

Faker

创建测试数据的时候你或许遇到很多不爽的问题,比如数据的格式、类型等等。我们用一个非常有用的包来简化工作。

Faker Github
Packagist

用例

如何安装和使用 Faker,可以去Faker的Github快速浏览一下,用一个简单的 Users 表的数据填充来做个示例:

1. 创建一个 UserTableSeeder

在创建seed文件之前,我们先讨论一个问题。或许有人会建议一个原则:One Seeder Per Table,让每张表对应一个seeder文件,确实会让seeder文件更加整齐和清晰。

但是实际开发当中你会发现,很多情况我们其实我们需要填充一组相关的数据进去,比如说你想填充用户数据的时候,顺便填充一些用户的配置数据、活动记录等相关信息进去,如果分开写就会非常烦,需要多次切换一下文件。而且分开写每次运行填充命令的时候你会发现速度很慢,大家的开发时间都是蛮宝贵的,都不想浪费在无聊的等待时间上吧。

所以其实更建议的是将需要填充的数据进行一个逻辑上的分组,这样的开发方式会更加棒。

我有用 way/generators 这个包来创建这些文件,当然如果你喜欢手动创建的话也ok。

php artisan generate:seed User

2. UserTableSeeder.php

app\database\seeds\UserTableSeeder.php 的文件内容:

<?php

// Composer: "fzaninotto/faker": "v1.3.0"
use Faker\Factory as Faker;

class UserTableSeeder extends Seeder {

    public function run()
    {
        $faker = Faker::create();

        for($i = 1; $i <= Config::get('app.seeding.users'); $i++)
        {
            User::create([
                'email' => $faker->email,
                'nickname' => $faker->name,
                'password' => 'password',
                'avatar' => rand(0, 1) ? $faker->imageUrl(100, 100, 'cats') : null,
                'sex' => $faker->randomElement([0, 1]),
                'birthday' => rand(0, 1) ? $faker->dateTimeBetween('-40 years', '-18 years') : null,
                'telephone' => rand(0, 1) ? $faker->phoneNumber : null,
                'cover' => $faker->imageUrl(480, 320)
            ]);
        }
    }

}

里面的内容我就不在解释了,Config::get() 的用法只是让配置更简单、集中一点而已;

你可能看到我们在这段代码里面使用了 User 这个 Eloquent Model 对象,所以运行之前数据填充之前,你可能需要确认一下model是否已经创建好了,或者里面的配置是否正确;

这是 app\models\User.php 的内容:

<?php

use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
use Illuminate\Database\Eloquent\SoftDeletingTrait;

class User extends Eloquent implements UserInterface, RemindableInterface {

    use UserTrait, RemindableTrait, SoftDeletingTrait;

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'users';

   // 省略无关代码  
     ...

下面的内容不会再确认你的数据库表对应的model是否配置正确,如果你想跟着本文走一遍的话,需要你自己创建一下这些model并且配置好(比如对应正确的数据表名称等等)

3. 安全保证.

我们可以写一些代码来保证我们的数据填充操作不会影响到数据安全,app\database\seeds\DatabaseSeeder.php 文件的内容:

<?php

class DatabaseSeeder extends Seeder {

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        if( App::environment() === 'production' )
        {
            exit('你不会是想被开除吧 ?');
        }

        Eloquent::unguard();

        $tables = [
            'users',
        ];

        // 清除测试数据
        foreach ($tables as $table) 
        {
            DB::table($table)->truncate();
        }

        // 填充测试数据
        $this->call('UserTableSeeder');
    }

}

注意:当数据库存在外键关系的时候,删除数据会是一件头疼的事情,所以需要在truncate数据之前关闭外键检查:

DB::statement('SET FOREIGN_KEY_CHECKS=0;');

在填充完数据之后,再回滚一下设置,重新启用外键检查:

DB::statement('SET FOREIGN_KEY_CHECKS=1;');

4. 考虑一些数据相关的情况

你可能需要插入一些相关的数据,比如说这些数据来自于前面已经填充的 Users 表的内容,那我们如何来填充这些测试数据?

考虑实现一个简单的博客系统的时候,每个用户可能会有多个博客,每个博客会有多个评论,如何填充这些数据进去:

新建一个 Blog 对象数据填充文件:

php artisan generate:seed Blog

这是 app\database\seeds\BlogTableSeeder.php 文件的内容:

<?php

// Composer: "fzaninotto/faker": "v1.3.0"
use Faker\Factory as Faker;

class BlogTableSeeder extends Seeder {

    public function run()
    {
        $faker = Faker::create();

        // Get User id list
        $userIds = User::lists('id');

        for($i = 1; $i <= Config::get('app.seeding.blogs'); $i++)
        {
            Blog::create([
                'user_id' => $faker->randomElement($userIds),
                'content' => $faker->paragraph(10),
            ]);
        }

        // Get Blog id list
        $blogIds = Blog::lists('id');

        for($i = 1; $i <= Config::get('app.seeding.comments'); $i++)
        {
            Comment::create([
                'user_id' => $faker->randomElement($userIds),
                'blog_id' => $faker->randomElement($blogIds),
                'content' => $faker->sentence(10),
            ]);
        }
    }
}

在运行迁移命令之前,需要你在 app\database\seeds\DatabaseSeeder.php 调用一下填充文件, 当涉及填充多个数据库表的时候,你可能需要注意一下填充文件的调用顺序。

<?php

class DatabaseSeeder extends Seeder {

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        if( App::environment() === 'production' )
        {
            exit('你不会是想被开除吧 ?');
        }

        Eloquent::unguard();

        $tables = [
            'users',
            'books',
        ];

        DB::statement('SET FOREIGN_KEY_CHECKS=0;');

        // 清除测试数据
        foreach ($tables as $table) 
        {
            DB::table($table)->truncate();
        }

        // 填充测试数据
        $this->call('UserTableSeeder');
        $this->call('BlogTableSeeder');

        DB::statement('SET FOREIGN_KEY_CHECKS=1;');
    }
}

运行一下数据填充命令

$ php artisan db:seed

5. 在什么时候运行填充命令

其实用的最多的是当部署到一个新的环境,或者修改数据迁移文件之后,我们可能需要切换到项目目录下运行一下数据填充命令,更方便的可以在运行迁移的时候附加上填充测试数据的选项:

$ php artisan migrate --seed

或者有时候需要 refresh 一下数据库:

$ php artisan migrate:refresh --seed

6. 结束

大多数时候,我们这样的做法是能够完成测试数据的填充工作,当然有时候也有可能需要填充一些准备好的样本数据,比如说样本图片,你的业务逻辑`(可以理解为数据之间的关系)也会更复杂,就需要你写更多的seeder代码来完成这些目标。如果你有这样的需求,可以推荐你看看本系列文章的推荐阅读书籍里面的第一章节,作者有拿他们公司的的一个项目做示例,或许对你有更好的帮助。


-- 本系列文章持续更新中

-- Laravel 爱好者 JobsLong

Remote. Open. Engineer.
本帖已被设为精华帖!
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 13
Summer
if( App::environment() === 'production' )
{
    exit('你不会是想被开除吧 ?');
}

这个给力, 哈哈哈.

9年前 评论
Summer

非常给力的文章, 我稍微做了下排版, 希望不要介意 :sunny:

9年前 评论
Summer

@JobsLong :+1:

9年前 评论

精华帖就该撩出来点个赞~ 照着玩了一下,晓文赞赞哒~~ 可我还是被两年前的包坑了一丢丢:joy_cat:

7年前 评论

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