我们在开发的时候经常会需要修改已经写好的创建表的 migration
文件,但是又不想在开发时就写太多的修改或者添加表字段的 migration
文件,导致 migration
文件过多,而如果直接修改了已经创建好的 migration
文件,那就必须要 migrate:refresh
了,但是表中的测试数据重新添加又是件麻烦的事,这时候我们就可以使用laravel的数据填充功能 Seeder
, 在讨论这个 Seeder
之前,我们先来看下如何批量生成测试数据。
laravel框架默认引入了 fzaninotto/Faker
包,这个包能让我们快速的生成一些测试数据。包的开发思路基于 Perl的Data::Faker和ruby的faker
。这个包的具体使用方式在这里 https://github.com/fzaninotto/Faker ,我们来看下在Laravel中如何使用它.
我们去跑一个Laravel 5.3的框架,并且建立好数据库,并执行掉 php artisan migrate
命令。这里自己做,大家应该是可以闭着眼睛做好这些了。
所有的model factories我们都会放在 database/factories/ModelFactory.php
中,我们打开它:
<?php /* | Model Factories | | Here you may define all of your model factories. Model factories give | you a convenient way to create models for testing and seeding your | database. Just tell the factory how a default model should look. | 你可以在这里定义你所有的模型工厂,使用模型工厂可以让我们在单元测试的时候很方便的使用生成 | 的测试数据,同时也可以快速的将测试数据保存到我们的数据库中。 */ $factory->define(App\User::class, function (Faker\Generator $faker) { static $password; return [ 'name' => $faker->name, 'email' => $faker->unique()->email, 'password' => $password ?: $password = bcrypt('secret'), 'remember_token' => str_random(10), ]; });
默认的已经有了一个为 User
模型生成测试数据的方法,这里的 $faker->name,$faker->unique()->email
都是 fzaninotto/Faker
包提供,上面已经给了它的github地址,大家用的时候自己去查找下就行。
怎么使用它呢? 我们打开 php artisan tinker
, 执行
factory(App\User::class)->make();
这句话的意思是,我们会生成一个 User
对象,但是我们会使用模型工厂全局帮助函数 factory()
来生成它,生成的时候就给对象的所有属性赋值。结果如下:
Psy Shell v0.8.0 (PHP 7.0.12 — cli) by Justin Hileman >>> factory(App\User::class)->make(); => App\User {#692 name: "Candace Bins", email: "morar.ethan@gmail.com", } >>> factory(App\User::class)->make(); => App\User {#698 name: "Prof. Alf Graham MD", email: "mpagac@yahoo.com", }
每调用一次都会生成一个具有不同属性值的对象,那如果我们要同时生成多个这样的对象呢?比如我们要生成500个 User
对象,我们只要传入第2个参数即可,如下:
factory(App\User::class, 500)->make();
通常我们会需要将这些生成的数据保存到数据库,那使用 create()
即可
factory(App\User::class, 2)->create();
不过我们一般就不跑到 tinker
中去生成这样测试数据了,我们将这条语句些到 seeds/DatabaseSeeder.php
的 run
方法中:
public function run() { // 清空users表中已经存在的数据 User::truncate(); factory(User::class, 2)->create(); }
然后执行:
php artisan db:seed
就会调用上面这个 run()
方法,执行当中的语句了。
不过在正式开发的时候,$faker数据要依据你的具体情况来用了,比如我们会需要一些真实的数据,比如说一些字典表,那必须是正确的数据,那就会手动指定了,所以通常我们对应每个模型都会去写一个 Seeder
类,然后在 DatabaseSeeder.php
中去调用它们,在具体的 Seeder
类中去调用模型工厂,这样代码会方便管理很多,因为现在还没有讲到 Seeder
类,所以我们现在知道怎么用模型工厂就可以了。
对于模型工厂的数据,我们可能更多的还是用在测试中的(测试以后最后再详说),我们看下测试中怎么用:
我们去建立一个 posts
表, migration
文件如下:
public function up() { Schema::create('posts', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->text('body'); $table->timestamps(); }); }
比如我们要测试这个流程: 当我访问post的路由的时候,给我显示数据库中的post的标题和内容.
我们直接改下 tests/ExampleTest.php
中的代码:
<?php use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseTransactions; use App\Post; class ExampleTest extends TestCase { // 不让测试的数据保存到数据库中,没有这条语句,当执行phpunit时,会 // 在posts表中不断的插入数据,自己测试下 use DatabaseTransactions; /** * A basic functional test example. * * @return void */ public function testBasicExample() { // 通过模型工厂生成带有测试数据属性的$post对象 $post = factory(Post::class)->create(); $this->visit('posts') ->see($post->title); } }
在 database/factories/ModelFactory.php
中定义模型工厂:
$factory->define(App\Post::class, function (Faker\Generator $faker) { return [ 'title' => $faker->sentence, 'body' => $faker->paragraph ]; });
编写路由文件,laravel 5.3路由分的很细,有针对api的,针对web的,还有针对命令行的,针对web的在 routes/web.php
中( 路由这样改个人觉得很好,比5.2好多了)
Route::get('posts', function() { return view('posts')->with('posts', App\Post::all()); });
然后去弄个 resources/views/posts.blade.php
视图
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>zhoujiping.com</title> </head> <body> @foreach ($posts as $post) <article> <h2>{{ $post->title }}</h2> <div class="body">{{ $post->body }}</div> </article> @endforeach </body> </html>
到命令行执行 phpunit
, 是绿色的
我们在回头来说下 fzaninotto/Faker
包, 默认我们生成的测试数据都是英文的,如果你一定要想生成中文的测试数据(其实没啥用),该怎么生成?回到 database/factories/ModelFactory.php
在 fzaninotto/Faker
源码中我们可以看见 fzaninotto/Faker
的包中的关于语言版本相关的都放在Provider中:
而在laravel中$faker是 Faker\Generator
的是一个实例,在 Faker\Generator
中有这样一个方法:
public function addProvider($provider) { array_unshift($this->providers, $provider); }
这样一看就明白了,将需要的语言文件依赖注入进来就可以了。
那我们来改下代码:
$factory->define(App\User::class, function (Faker\Generator $faker) { $faker->addProvider(new Faker\Provider\zh_CN\Person($faker)); static $password; return [ 'name' => $faker->name, 'email' => $faker->unique()->email, 'password' => $password ?: $password = bcrypt('secret'), 'remember_token' => str_random(10), ]; });
我们到tinker中跑一下:
现在生成的姓名就是中文的了,但是该组件对中文的支持类太少,所以你可能会在写 Seeder
的时候用一部分$faker,用一部分手动指定,或者自己写一些数组,随机插入也可以。
本节到这里结束。