摘要:而且,与是一对多关系一个分类下有很多,一个只能归属于一个与是一对多关系一篇博客下有很多,一条只能归属于一篇与是多对多关系一篇有很多,一个下有很多。
说明:本文主要聊一聊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 */ BarryvdhDebugbarServiceProvider::class, MpociotLaravelTestFactoryHelperTestFactoryHelperServiceProvider::class, BarryvdhLaravelIdeHelperIdeHelperServiceProvider::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测试数据填充之前,看下开发插件三件套能干些啥,下文中命令可在项目根目录输入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:
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是一个好用的生成假数据的第三方库,而这个开发插件会自动帮你生成这些属性,不用自己写了。
define(AppUser::class, function (FakerGenerator $faker) { return [ "name" => $faker->name, "email" => $faker->safeEmail, "password" => bcrypt(str_random(10)), "remember_token" => str_random(10), ]; }); $factory->define(AppCategory::class, function (FakerGenerator $faker) { return [ "name" => $faker->name , "hot" => $faker->randomNumber() , "image" => $faker->word , ]; }); $factory->define(AppComment::class, function (FakerGenerator $faker) { return [ "post_id" => function () { return factory(AppPost::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(AppPost::class, function (FakerGenerator $faker) { return [ "category_id" => function () { return factory(AppCategory::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(AppTag::class, function (FakerGenerator $faker) { return [ "name" => $faker->name , "hot" => $faker->randomNumber() , ]; });
在聊第三个debugbar插件前先聊下seeder小技巧,用debugbar来帮助查看。Laravel官方推荐使用模型工厂自动生成测试数据,推荐这么写的:
//先输入指令生成database/seeds/CategoryTableSeeder.php文件: php artisan make:seeder CategoryTableSeeder create()->each(function($category){ $category->posts()->save(factory(AppPost::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(AppCategory::class, 20)->create()->each(function($category){ // $category->posts()->save(factory(AppPost::class)->make()); // }); $faker = FakerFactory::create(); $datas = []; foreach (range(1, 10) as $key => $value) { $datas[] = [ "name" => "category".$faker->randomNumber() , "hot" => $faker->randomNumber() , "image" => $faker->url , "created_at" => CarbonCarbon::now()->toDateTimeString(), "updated_at" => CarbonCarbon::now()->toDateTimeString() ]; } DB::table("categories")->insert($datas); } } class PostTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = FakerFactory::create(); $category_ids = AppCategory::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" => CarbonCarbon::now()->toDateTimeString(), "updated_at" => CarbonCarbon::now()->toDateTimeString() ]; } DB::table("posts")->insert($datas); } } class CommentTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = FakerFactory::create(); $post_ids = AppPost::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" => CarbonCarbon::now()->toDateTimeString(), "updated_at" => CarbonCarbon::now()->toDateTimeString() ]; } DB::table("comments")->insert($datas); } } class TagTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = FakerFactory::create(); $datas = []; foreach (range(1, 10) as $key => $value) { $datas[] = [ "name" => "tag".$faker->randomNumber() , "hot" => $faker->randomNumber() , "created_at" => CarbonCarbon::now()->toDateTimeString(), "updated_at" => CarbonCarbon::now()->toDateTimeString() ]; } DB::table("tags")->insert($datas); } } class PostTagTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = FakerFactory::create(); $post_ids = AppPost::lists("id")->toArray(); $tag_ids = AppTag::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" => CarbonCarbon::now()->toDateTimeString(), "updated_at" => CarbonCarbon::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!
不扯了,在凌晨两点边听音乐边写博客,就容易瞎感慨吧。。
分享下最近发现的一张好图和一篇极赞的文章:
文章链接:Laravel的中大型專案架構
欢迎关注Laravel-China。
RightCapital招聘Laravel DevOps
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/21662.html
摘要:说明本文主要讲述使用作为缓存加快页面访问速度。何不用来做缓存,等到该达到一定浏览页面后再刷新下,效率也很高。可作缓存系统队列系统。 说明:本文主要讲述使用Redis作为缓存加快页面访问速度。同时,作者会将开发过程中的一些截图和代码黏上去,提高阅读效率。 备注:作者最近在学习github上别人的源码时,发现好多在计算一篇博客页面访问量view_count时都是这么做的:利用Laravel...
摘要:本文首发于作者这是一篇基础教程,对标文档中的数据迁移和数据填充。那么,中的数据库迁移概念,就是用于解决团队中保证数据库结构一致的方案。和不同,如果多次执行就会进行多次数据填充。好了,数据迁移和数据填充的基本操作也就这些了。 showImg(https://segmentfault.com/img/remote/1460000012252769?w=648&h=422); 本文首发于 h...
摘要:本文经授权转自社区说明开发者使用部署一个新项目的时候通常会使用快速填充本地数据以方便开发调试扩展包提供了可将数据表里的数据直接转换为文件的功能本项目由团队成员整理发布首发地为社区使用场景通常情况下我们会希望本地开发环境数据与生产完全一致这样 本文经授权转自 PHPHub 社区 说明 开发者使用 Laravel 部署一个新项目的时候, 通常会使用 seeder 快速填充本地数据以方便开发...
阅读 640·2021-11-23 09:51
阅读 3259·2019-08-30 15:54
阅读 374·2019-08-30 15:52
阅读 3093·2019-08-30 13:58
阅读 2880·2019-08-30 13:53
阅读 2660·2019-08-29 14:18
阅读 2389·2019-08-27 10:54
阅读 2319·2019-08-26 18:09