php的seeder是什么,Laravel学习笔记之Seeder填充数据小技巧

说明:本文主要聊一聊Laravel测试数据填充器Seeder的小技巧,同时介绍下Laravel开发插件三件套,这三个插件挺好用哦。同时,作者会将开发过程中的一些截图和代码黏上去,提高阅读效率。

备注:在设计个人博客软件时,总会碰到有分类Category、博客Post、给博客贴的标签Tag、博客内容的评论Comment。

而且,Category与Post是一对多关系One-Many:一个分类下有很多Post,一个Post只能归属于一个Category;Post与Comment是一对多关系One-Many:一篇博客Post下有很多Comment,一条Comment只能归属于一篇Post;Post与Tag是多对多关系Many-Many:一篇Post有很多Tag,一个Tag下有很多Post。

开发环境:Laravel5.2 + MAMP + PHP7 + MySQL5.5

开发插件三件套

在先聊测试数据填充器seeder之前,先装上开发插件三件套,开发神器。先不管这能干些啥,装上再说。

1、barryvdh/laravel-debugbar

composer require barryvdh/laravel-debugbar --dev

2、barryvdh/laravel-ide-helper

composer require barryvdh/laravel-ide-helper --dev

3、mpociot/laravel-test-factory-helper

composer require mpociot/laravel-test-factory-helper --dev

然后在config/app.php文件中填上:

/**

*Develop Plugin

*/

Barryvdh\Debugbar\ServiceProvider::class,

Mpociot\LaravelTestFactoryHelper\TestFactoryHelperServiceProvider::class,

Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,

设计表的字段和关联

设计字段

按照上文提到的Category、Post、Comment和Tag之间的关系创建迁移Migration和模型Model,在项目根目录输入:

php artisan make:model Category -m

php artisan make:model Post -m

php artisan make:model Comment -m

php artisan make:model Tag -m

在各个表的迁移migrations文件中根据表的功能设计字段:

//Category表

class CreateCategoriesTable extends Migration

{

/**

* Run the migrations.

*

* @return void

*/

public function up()

{

Schema::create('categories', function (Blueprint $table) {

$table->increments('id');

$table->string('name')->comment('分类名称');

$table->integer('hot')->comment('分类热度');

$table->string('image')->comment('分类图片');

$table->timestamps();

});

}

/**

* Reverse the migrations.

*

* @return void

*/

public function down()

{

Schema::drop('categories');

}

}

//Post表

class CreatePostsTable extends Migration

{

/**

* Run the migrations.

*

* @return void

*/

public function up()

{

Schema::create('posts', function (Blueprint $table) {

$table->increments('id');

$table->integer('category_id')->unsigned()->comment('外键');

$table->string('title')->comment('标题');

$table->string('slug')->unique()->index()->comment('锚点');

$table->string('summary')->comment('概要');

$table->text('content')->comment('内容');

$table->text('origin')->comment('文章来源');

$table->integer('comment_count')->unsigned()->comment('评论次数');

$table->integer('view_count')->unsigned()->comment('浏览次数');

$table->integer('favorite_count')->unsigned()->comment('点赞次数');

$table->boolean('published')->comment('文章是否发布');

$table->timestamps();

//Post表中category_id字段作为外键,与Category一对多关系

$table->foreign('category_id')

->references('id')

->on('categories')

->onUpdate('cascade')

->onDelete('cascade');

});

}

/**

* Reverse the migrations.

*

* @return void

*/

public function down()

{

//删除表时要删除外键约束,参数为外键名称

Schema::table('posts', function(Blueprint $tabel){

$tabel->dropForeign('posts_category_id_foreign');

});

Schema::drop('posts');

}

}

//Comment表

class CreateCommentsTable extends Migration

{

/**

* Run the migrations.

*

* @return void

*/

public function up()

{

Schema::create('comments', function (Blueprint $table) {

$table->increments('id');

$table->integer('post_id')->unsigned()->comment('外键');

$table->integer('parent_id')->comment('父评论id');

$table->string('parent_name')->comment('父评论标题');

$table->string('username')->comment('评论者用户名');

$table->string('email')->comment('评论者邮箱');

$table->string('blog')->comment('评论者博客地址');

$table->text('content')->comment('评论内容');

$table->timestamps();

//Comment表中post_id字段作为外键,与Post一对多关系

$table->foreign('post_id')

->references('id')

->on('posts')

->onUpdate('cascade')

->onDelete('cascade');

});

}

/**

* Reverse the migrations.

*

* @return void

*/

public function down()

{

//删除表时要删除外键约束,参数为外键名称

Schema::table('comments', function(Blueprint $tabel){

$tabel->dropForeign('comments_post_id_foreign');

});

Schema::drop('comments');

}

}

//Tag表

class CreateTagsTable extends Migration

{

/**

* Run the migrations.

*

* @return void

*/

public function up()

{

Schema::create('tags', function (Blueprint $table) {

$table->increments('id');

$table->string('name')->comment('标签名称');

$table->integer('hot')->unsigned()->comment('标签热度');

$table->timestamps();

});

}

/**

* Reverse the migrations.

*

* @return void

*/

public function down()

{

Schema::drop('tags');

}

}

由于Post表与Tag表是多对多关系,还需要一张存放两者关系的表:

//多对多关系,中间表的命名laravel默认按照两张表字母排序来的,写成tag_post会找不到中间表

php artisan make:migration create_post_tag_table --create=post_tag

然后填上中间表的字段:

class CreatePostTagTable extends Migration

{

/**

* Run the migrations.

*

* @return void

*/

public function up()

{

Schema::create('post_tag', function (Blueprint $table) {

$table->increments('id');

$table->integer('post_id')->unsigned();

$table->integer('tag_id')->unsigned();

$table->timestamps();

//post_id字段作为外键

$table->foreign('post_id')

->references('id')

->on('posts')

->onUpdate('cascade')

->onDelete('cascade');

//tag_id字段作为外键

$table->foreign('tag_id')

->references('id')

->on('tags')

->onUpdate('cascade')

->onDelete('cascade');

});

}

/**

* Reverse the migrations.

*

* @return void

*/

public function down()

{

Schema::table('post_tag', function(Blueprint $tabel){

$tabel->dropForeign('post_tag_post_id_foreign');

$tabel->dropForeign('post_tag_tag_id_foreign');

});

Schema::drop('post_tag');

}

}

设计关联

写上Migration后,还得在Model里写上关联:

class Category extends Model

{

//Category-Post:One-Many

public function posts()

{

return $this->hasMany(Post::class);

}

}

class Post extends Model

{

//Post-Category:Many-One

public function category()

{

return $this->belongsTo(Category::class);

}

//Post-Comment:One-Many

public function comments()

{

return $this->hasMany(Comment::class);

}

//Post-Tag:Many-Many

public function tags()

{

return $this->belongsToMany(Tag::class)->withTimestamps();

}

}

class Comment extends Model

{

//Comment-Post:Many-One

public function post()

{

return $this->belongsTo(Post::class);

}

}

class Tag extends Model

{

//Tag-Post:Many-Many

public function posts()

{

return $this->belongsToMany(Post::class)->withTimestamps();

}

}

然后执行迁移:

php artisan migrate

数据库中会生成新建表,表的关系如下:

bVxWZT?w=1124&h=1238

Seeder填充测试数据

好,在聊到seeder测试数据填充之前,看下开发插件三件套能干些啥,下文中命令可在项目根目录输入php artisan指令列表中查看。

1、barryvdh/laravel-ide-helper

执行php artisan ide-helper:generate指令前:

bVxW0m?w=1212&h=664

执行php artisan ide-helper:generate指令后:

bVxW0n?w=1188&h=606

不仅Facade模式的Route由之前的反白了变为可以定位到源码了,而且输入Config Facade时还方法自动补全auto complete,这个很方便啊。

输入指令php artisan ide-helper:models后,看看各个Model,如Post这个Model:

namespace App;

use Illuminate\Database\Eloquent\Model;

/**

* App\Post

*

* @property integer $id

* @property integer $category_id 外键

* @property string $title 标题

* @property string $slug 锚点

* @property string $summary 概要

* @property string $content 内容

* @property string $origin 文章来源

* @property integer $comment_count 评论次数

* @property integer $view_count 浏览次数

* @property integer $favorite_count 点赞次数

* @property boolean $published 文章是否发布

* @property \Carbon\Carbon $created_at

* @property \Carbon\Carbon $updated_at

* @property-read \App\Category $category

* @property-read \Illuminate\Database\Eloquent\Collection|\App\Comment[] $comments

* @property-read \Illuminate\Database\Eloquent\Collection|\App\Tag[] $tags

* @method static \Illuminate\Database\Query\Builder|\App\Post whereId($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereCategoryId($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereTitle($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereSlug($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereSummary($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereContent($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereOrigin($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereCommentCount($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereViewCount($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereFavoriteCount($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post wherePublished($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereCreatedAt($value)

* @method static \Illuminate\Database\Query\Builder|\App\Post whereUpdatedAt($value)

* @mixin \Eloquent

*/

class Post extends Model

{

//Post-Category:Many-One

public function category()

{

return $this->belongsTo(Category::class);

}

//Post-Comment:One-Many

public function comments()

{

return $this->hasMany(Comment::class);

}

//Post-Tag:Many-Many

public function tags()

{

return $this->belongsToMany(Tag::class)->withTimestamps();

}

}

根据迁移到库里的表生成字段属性和对应的方法提示,在控制器里输入方法时会自动补全auto complete字段属性的方法:

bVxW0C?w=1358&h=916

2、mpociot/laravel-test-factory-helper

输入指令php artisan test-factory-helper:generate后,database/factory/ModelFactory.php模型工厂文件会自动生成各个模型对应字段数据。Faker是一个好用的生成假数据的第三方库,而这个开发插件会自动帮你生成这些属性,不用自己写了。

/*

|--------------------------------------------------------------------------

| 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) {

return [

'name' => $faker->name,

'email' => $faker->safeEmail,

'password' => bcrypt(str_random(10)),

'remember_token' => str_random(10),

];

});

$factory->define(App\Category::class, function (Faker\Generator $faker) {

return [

'name' => $faker->name ,

'hot' => $faker->randomNumber() ,

'image' => $faker->word ,

];

});

$factory->define(App\Comment::class, function (Faker\Generator $faker) {

return [

'post_id' => function () {

return factory(App\Post::class)->create()->id;

} ,

'parent_id' => $faker->randomNumber() ,

'parent_name' => $faker->word ,

'username' => $faker->userName ,

'email' => $faker->safeEmail ,

'blog' => $faker->word ,

'content' => $faker->text ,

];

});

$factory->define(App\Post::class, function (Faker\Generator $faker) {

return [

'category_id' => function () {

return factory(App\Category::class)->create()->id;

} ,

'title' => $faker->word ,

'slug' => $faker->slug ,//修改为slug

'summary' => $faker->word ,

'content' => $faker->text ,

'origin' => $faker->text ,

'comment_count' => $faker->randomNumber() ,

'view_count' => $faker->randomNumber() ,

'favorite_count' => $faker->randomNumber() ,

'published' => $faker->boolean ,

];

});

$factory->define(App\Tag::class, function (Faker\Generator $faker) {

return [

'name' => $faker->name ,

'hot' => $faker->randomNumber() ,

];

});

在聊第三个debugbar插件前先聊下seeder小技巧,用debugbar来帮助查看。Laravel官方推荐使用模型工厂自动生成测试数据,推荐这么写的:

//先输入指令生成database/seeds/CategoryTableSeeder.php文件: php artisan make:seeder CategoryTableSeeder

use Illuminate\Database\Seeder;

class CategoryTableSeeder extends Seeder

{

/**

* Run the database seeds.

*

* @return void

*/

public function run()

{

factory(\App\Category::class, 5)->create()->each(function($category){

$category->posts()->save(factory(\App\Post::class)->make());

});

}

}

//然后php artisan db:seed执行数据填充

但是这种方式效率并不高,因为每一次create()都是一次query,而且每生成一个Category也就对应生成一个Post,当然可以在each()里每一次Category继续foreach()生成几个Post,但每一次foreach也是一次query,效率更差。可以用debugbar小能手看看。先在DatabaseSeeder.php文件中填上这次要填充的Seeder:

public function run()

{

// $this->call(UsersTableSeeder::class);

$this->call(CategoryTableSeeder::class);

}

在路由文件中写上:

Route::get('/artisan', function () {

$exitCode = Artisan::call('db:seed');

return $exitCode;

});

输入路由/artisan后用debugbar查看执行了15次query,耗时7.11ms:

bVxW2d?w=2554&h=1018

实际上才刚刚输入几个数据呢,Category插入了10个,Post插入了5个。

可以用DB::table()->insert()批量插入,拷贝ModelFactory.php中表的字段定义放入每一个表对应Seeder,当然可以有些字段为便利也适当修改对应假数据。

class CategoryTableSeeder extends Seeder

{

/**

* Run the database seeds.

*

* @return void

*/

public function run()

{

// factory(\App\Category::class, 20)->create()->each(function($category){

// $category->posts()->save(factory(\App\Post::class)->make());

// });

$faker = Faker\Factory::create();

$datas = [];

foreach (range(1, 10) as $key => $value) {

$datas[] = [

'name' => 'category'.$faker->randomNumber() ,

'hot' => $faker->randomNumber() ,

'image' => $faker->url ,

'created_at' => \Carbon\Carbon::now()->toDateTimeString(),

'updated_at' => \Carbon\Carbon::now()->toDateTimeString()

];

}

DB::table('categories')->insert($datas);

}

}

class PostTableSeeder extends Seeder

{

/**

* Run the database seeds.

*

* @return void

*/

public function run()

{

$faker = Faker\Factory::create();

$category_ids = \App\Category::lists('id')->toArray();

$datas = [];

foreach (range(1, 10) as $key => $value) {

$datas[] = [

'category_id' => $faker->randomElement($category_ids),

'title' => $faker->word ,

'slug' => $faker->slug ,

'summary' => $faker->word ,

'content' => $faker->text ,

'origin' => $faker->text ,

'comment_count' => $faker->randomNumber() ,

'view_count' => $faker->randomNumber() ,

'favorite_count' => $faker->randomNumber() ,

'published' => $faker->boolean ,

'created_at' => \Carbon\Carbon::now()->toDateTimeString(),

'updated_at' => \Carbon\Carbon::now()->toDateTimeString()

];

}

DB::table('posts')->insert($datas);

}

}

class CommentTableSeeder extends Seeder

{

/**

* Run the database seeds.

*

* @return void

*/

public function run()

{

$faker = Faker\Factory::create();

$post_ids = \App\Post::lists('id')->toArray();

$datas = [];

foreach (range(1, 50) as $key => $value) {

$datas[] = [

'post_id' => $faker->randomElement($post_ids),

'parent_id' => $faker->randomNumber() ,

'parent_name' => $faker->word ,

'username' => $faker->userName ,

'email' => $faker->safeEmail ,

'blog' => $faker->word ,

'content' => $faker->text ,

'created_at' => \Carbon\Carbon::now()->toDateTimeString(),

'updated_at' => \Carbon\Carbon::now()->toDateTimeString()

];

}

DB::table('comments')->insert($datas);

}

}

class TagTableSeeder extends Seeder

{

/**

* Run the database seeds.

*

* @return void

*/

public function run()

{

$faker = Faker\Factory::create();

$datas = [];

foreach (range(1, 10) as $key => $value) {

$datas[] = [

'name' => 'tag'.$faker->randomNumber() ,

'hot' => $faker->randomNumber() ,

'created_at' => \Carbon\Carbon::now()->toDateTimeString(),

'updated_at' => \Carbon\Carbon::now()->toDateTimeString()

];

}

DB::table('tags')->insert($datas);

}

}

class PostTagTableSeeder extends Seeder

{

/**

* Run the database seeds.

*

* @return void

*/

public function run()

{

$faker = Faker\Factory::create();

$post_ids = \App\Post::lists('id')->toArray();

$tag_ids = \App\Tag::lists('id')->toArray();

$datas = [];

foreach (range(1, 20) as $key => $value) {

$datas[] = [

'post_id' => $faker->randomElement($post_ids) ,

'tag_id' => $faker->randomElement($tag_ids) ,

'created_at' => \Carbon\Carbon::now()->toDateTimeString(),

'updated_at' => \Carbon\Carbon::now()->toDateTimeString()

];

}

DB::table('post_tag')->insert($datas);

}

}

在DatabaseSeeder.php中按照顺序依次填上Seeder,顺序不能颠倒,尤其有关联关系的表:

class DatabaseSeeder extends Seeder

{

/**

* Run the database seeds.

*

* @return void

*/

public function run()

{

// $this->call(UsersTableSeeder::class);

$this->call(CategoryTableSeeder::class);

$this->call(PostTableSeeder::class);

$this->call(CommentTableSeeder::class);

$this->call(TagTableSeeder::class);

$this->call(PostTagTableSeeder::class);

}

}

输入路由/artisan后,生成了10个Category、10个Post、50个Comments、10个Tag和PostTag表中多对多关系,共有9个Query耗时13.52ms:

bVxW28?w=2544&h=524

It is working!!!

表的迁移Migration和关联Relationship都已设计好,测试数据也已经Seeder好了,就可以根据Repository模式来设计一些数据库逻辑了。准备趁着端午节研究下Repository模式的测试,PHPUnit结合Mockery包来TDD测试也是一种不错的玩法。

M(Model)-V(View)-C(Controller)模式去组织代码,很多时候也未必指导性很强,给Model加一个Repository,给Controller加一个Service,给View加一个Presenter,或许代码结构更清晰。具体可看下面分享的一篇文章。

最近一直在给自己充电,研究MySQL,PHPUnit,Laravel,上班并按时打卡,看博客文章,每天喝红牛。很多不会,有些之前没咋学过,哎,头疼。后悔以前读书太少,书到用时方恨少,人丑还需多读书。

研究生学习机器人的,本打算以后读博搞搞机器人的(研一时真是这么想真是这么准备的,too young too simple)。现在做PHP小码农了,只因当时看到智能机就激动得不行,决定以后做个码农试试吧,搞不好是条生路,哈哈。读书时觉悟太晚,耗费了青春,其实我早该踏入这条路的嘛,呵呵。Follow My Heart!

不扯了,在凌晨两点边听音乐边写博客,就容易瞎感慨吧。。

分享下最近发现的一张好图和一篇极赞的文章:

bVxW3q?w=1412&h=520

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值