说明:本文主要聊一聊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

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

Seeder填充测试数据

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

1、barryvdh/laravel-ide-helper

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

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

不仅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字段属性的方法:

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:

实际上才刚刚输入几个数据呢,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:

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!

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

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

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

  1. Laravel学习笔记汇总——Collection方法详解

    ## Laravel学习笔记汇总--Collection方法详解 本文参考:https:// laravel.com/docs/8.x/collections // 返回整个底层的数组 collect ...

  2. JavaWeb黑马旅游网-学习笔记05【分类数据展示功能】

    Java后端 学习路线 笔记汇总表[黑马程序员] JavaWeb黑马旅游网-学习笔记01[准备工作] JavaWeb黑马旅游网-学习笔记02[注册功能] JavaWeb黑马旅游网-学习笔记03[登陆和 ...

  3. 学习笔记(五)——数据适配器、数据表、数据网格视图控件的综合应用。

    学习笔记(五)--数据适配器.数据表.数据网格视图控件的综合应用. 1.  批量修改 修改包括增加,删除以及更新3个操作,所以声明实力化3个SQL命令分别应用于插入,删除以及修改 将声明的SQL命令连 ...

  4. HALCON 20.11:深度学习笔记(3)---Data(数据)

    HALCON 20.11:深度学习笔记(3)---Data(数据) HALCON 20.11.0.0中,实现了深度学习方法.其中,关于术语"数据"的介绍如下: 术语"数据 ...

  5. R学习笔记(4): 使用外部数据

    来源于:R学习笔记(4): 使用外部数据 博客:心内求法 鉴于内存的非持久性和容量限制,一个有效的数据处理工具必须能够使用外部数据:能够从外部获取大量的数据,也能够将处理结果保存.R中提供了一系列的函 ...

  6. SDN软件定义网络 学习笔记(4)--数据平面

    SDN软件定义网络 学习笔记(4)--数据平面 1. 简介 2. SDN数据平面架构 2.1 传统网络交换设备架构 2.2 SDN交换设备架构 2.3 数据平面架构图 3. SDN芯片与交换机 3.1 ...

  7. 【Vue】学习笔记-组件传值的数据累加器

    [Vue]学习笔记-组件传值的数据累加器 前言 父级组件 购物车组件 计数器组件 常见错误总结 前言 组件传值的数据累加器可以分为三个部分 App.vue为父级组件 Carts.vue表示购物车 Co ...

  8. SQLite学习笔记(七)-- 数据插入、更新和删除(C++实现)

    1.代码实例 代码说明 本例主要说明如何对数据表进行插入.更新和删除操作. 测试平台 1.开发语言:C++ 2.开发工具:VS2015 3.操作系统:Win7 X64 测试数据说明 测试表为Stude ...

  9. Python学习笔记---merge和concat数据合并(1)

    Python学习笔记-merge和concat数据合并(1) Python学习笔记-merge和concat数据合并(2) 文章目录 Python学习笔记---merge和concat数据合并(1) ...

  10. 数据库学习笔记(一) | 数据(Data)的定义

    数据库学习笔记(一) | 数据(Data)的定义和种类 什么是数据(Data) 结构化数据(Structured Data) 半结构化数据(Semi-structured Data) 非结构化数据(U ...

最新文章

  1. 【Java】IO Stream详细解读
  2. 利用DBMS_ADVISOR.TUNE_MVIEW包生成物化视图创建语句
  3. Modelsim-altera 仿真 顶层原理图的解决办法
  4. iText关于中文的解决方案
  5. C语言中常见的内存相关的Bugs
  6. hadoop22---wait,notify
  7. 【计算机组成与设计学习笔记】(一)
  8. 陕西省计算机分数线,2019陕西省各大学录取分数线最新汇总
  9. V4L2 pixel format 格式参考
  10. Alertmanager 通知模板
  11. open3d使用知识拾遗
  12. 腾讯企业邮箱 POP3/SMTP 设置
  13. Excel条形图也可以变身高大上
  14. http://ai.taobao.com/?pid=mm_40428920_1105750338_109783200329
  15. android 序列化存储对象,android中对象序列化存储
  16. 解决 Agent JAR loaded but agent failed to initialize
  17. L160. 相交链表
  18. Python开发的Web在线学习教育培训网课系统
  19. 聊聊Monolisa和JetBrains Mono字体在编码中的体验
  20. 选择护肤品时应该注意的功效成分!

热门文章

  1. Mybatis代码自动生成配置文件
  2. HBuilderX接夜神Android模拟器调试
  3. linux 中signal机制如何应用(一)
  4. 【Uplift】模拟数据篇
  5. c语言switch求利息,switch语句 利息计算器
  6. 【达梦数据库DM8】DM8基本操作及DCA考试感悟分享
  7. php确保多进程同时写入一个文件,php多进程读写同一个文件锁的问题及flock详解...
  8. CDN加速的四大解决方案
  9. tomcat 日志拆分
  10. 使用PHP编写的基于MySQL博客模板-可直接使用