摘要:而且,與是一對(duì)多關(guān)系一個(gè)分類下有很多,一個(gè)只能歸屬于一個(gè)與是一對(duì)多關(guān)系一篇博客下有很多,一條只能歸屬于一篇與是多對(duì)多關(guān)系一篇有很多,一個(gè)下有很多。
說明:本文主要聊一聊Laravel測試數(shù)據(jù)填充器Seeder的小技巧,同時(shí)介紹下Laravel開發(fā)插件三件套,這三個(gè)插件挺好用哦。同時(shí),作者會(huì)將開發(fā)過程中的一些截圖和代碼黏上去,提高閱讀效率。
備注:在設(shè)計(jì)個(gè)人博客軟件時(shí),總會(huì)碰到有分類Category、博客Post、給博客貼的標(biāo)簽Tag、博客內(nèi)容的評(píng)論Comment。
而且,Category與Post是一對(duì)多關(guān)系One-Many:一個(gè)分類下有很多Post,一個(gè)Post只能歸屬于一個(gè)Category;Post與Comment是一對(duì)多關(guān)系One-Many:一篇博客Post下有很多Comment,一條Comment只能歸屬于一篇Post;Post與Tag是多對(duì)多關(guān)系Many-Many:一篇Post有很多Tag,一個(gè)Tag下有很多Post。
開發(fā)環(huán)境:Laravel5.2 + MAMP + PHP7 + MySQL5.5
在先聊測試數(shù)據(jù)填充器seeder之前,先裝上開發(fā)插件三件套,開發(fā)神器。先不管這能干些啥,裝上再說。
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,設(shè)計(jì)表的字段和關(guān)聯(lián) 設(shè)計(jì)字段
按照上文提到的Category、Post、Comment和Tag之間的關(guān)系創(chuàng)建遷移Migration和模型Model,在項(xiàng)目根目錄輸入:
php artisan make:model Category -m php artisan make:model Post -m php artisan make:model Comment -m php artisan make:model Tag -m
在各個(gè)表的遷移migrations文件中根據(jù)表的功能設(shè)計(jì)字段:
//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("標(biāo)題"); $table->string("slug")->unique()->index()->comment("錨點(diǎn)"); $table->string("summary")->comment("概要"); $table->text("content")->comment("內(nèi)容"); $table->text("origin")->comment("文章來源"); $table->integer("comment_count")->unsigned()->comment("評(píng)論次數(shù)"); $table->integer("view_count")->unsigned()->comment("瀏覽次數(shù)"); $table->integer("favorite_count")->unsigned()->comment("點(diǎn)贊次數(shù)"); $table->boolean("published")->comment("文章是否發(fā)布"); $table->timestamps(); //Post表中category_id字段作為外鍵,與Category一對(duì)多關(guān)系 $table->foreign("category_id") ->references("id") ->on("categories") ->onUpdate("cascade") ->onDelete("cascade"); }); } /** * Reverse the migrations. * * @return void */ public function down() { //刪除表時(shí)要?jiǎng)h除外鍵約束,參數(shù)為外鍵名稱 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("父評(píng)論id"); $table->string("parent_name")->comment("父評(píng)論標(biāo)題"); $table->string("username")->comment("評(píng)論者用戶名"); $table->string("email")->comment("評(píng)論者郵箱"); $table->string("blog")->comment("評(píng)論者博客地址"); $table->text("content")->comment("評(píng)論內(nèi)容"); $table->timestamps(); //Comment表中post_id字段作為外鍵,與Post一對(duì)多關(guān)系 $table->foreign("post_id") ->references("id") ->on("posts") ->onUpdate("cascade") ->onDelete("cascade"); }); } /** * Reverse the migrations. * * @return void */ public function down() { //刪除表時(shí)要?jiǎng)h除外鍵約束,參數(shù)為外鍵名稱 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("標(biāo)簽名稱"); $table->integer("hot")->unsigned()->comment("標(biāo)簽熱度"); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop("tags"); } }
由于Post表與Tag表是多對(duì)多關(guān)系,還需要一張存放兩者關(guān)系的表:
//多對(duì)多關(guān)系,中間表的命名laravel默認(rèn)按照兩張表字母排序來的,寫成tag_post會(huì)找不到中間表 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"); } }設(shè)計(jì)關(guān)聯(lián)
寫上Migration后,還得在Model里寫上關(guān)聯(lián):
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(); } }
然后執(zhí)行遷移:
php artisan migrate
數(shù)據(jù)庫中會(huì)生成新建表,表的關(guān)系如下:
好,在聊到seeder測試數(shù)據(jù)填充之前,看下開發(fā)插件三件套能干些啥,下文中命令可在項(xiàng)目根目錄輸入php artisan指令列表中查看。
1、barryvdh/laravel-ide-helper
執(zhí)行php artisan ide-helper:generate指令前:
執(zhí)行php artisan ide-helper:generate指令后:
不僅Facade模式的Route由之前的反白了變?yōu)榭梢远ㄎ坏皆创a了,而且輸入Config Facade時(shí)還方法自動(dòng)補(bǔ)全auto complete,這個(gè)很方便啊。
輸入指令php artisan ide-helper:models后,看看各個(gè)Model,如Post這個(gè)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(); } }
根據(jù)遷移到庫里的表生成字段屬性和對(duì)應(yīng)的方法提示,在控制器里輸入方法時(shí)會(huì)自動(dòng)補(bǔ)全auto complete字段屬性的方法:
2、mpociot/laravel-test-factory-helper
輸入指令php artisan test-factory-helper:generate后,database/factory/ModelFactory.php模型工廠文件會(huì)自動(dòng)生成各個(gè)模型對(duì)應(yīng)字段數(shù)據(jù)。Faker是一個(gè)好用的生成假數(shù)據(jù)的第三方庫,而這個(gè)開發(fā)插件會(huì)自動(dòng)幫你生成這些屬性,不用自己寫了。
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() , ]; });
在聊第三個(gè)debugbar插件前先聊下seeder小技巧,用debugbar來幫助查看。Laravel官方推薦使用模型工廠自動(dòng)生成測試數(shù)據(jù),推薦這么寫的:
//先輸入指令生成database/seeds/CategoryTableSeeder.php文件: php artisan make:seeder CategoryTableSeeder create()->each(function($category){ $category->posts()->save(factory(AppPost::class)->make()); }); } } //然后php artisan db:seed執(zhí)行數(shù)據(jù)填充
但是這種方式效率并不高,因?yàn)槊恳淮蝐reate()都是一次query,而且每生成一個(gè)Category也就對(duì)應(yīng)生成一個(gè)Post,當(dāng)然可以在each()里每一次Category繼續(xù)foreach()生成幾個(gè)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查看執(zhí)行了15次query,耗時(shí)7.11ms:
實(shí)際上才剛剛輸入幾個(gè)數(shù)據(jù)呢,Category插入了10個(gè),Post插入了5個(gè)。
可以用DB::table()->insert()批量插入,拷貝ModelFactory.php中表的字段定義放入每一個(gè)表對(duì)應(yīng)Seeder,當(dāng)然可以有些字段為便利也適當(dāng)修改對(duì)應(yīng)假數(shù)據(jù)。
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,順序不能顛倒,尤其有關(guān)聯(lián)關(guān)系的表:
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個(gè)Category、10個(gè)Post、50個(gè)Comments、10個(gè)Tag和PostTag表中多對(duì)多關(guān)系,共有9個(gè)Query耗時(shí)13.52ms:
It is working!!!
表的遷移Migration和關(guān)聯(lián)Relationship都已設(shè)計(jì)好,測試數(shù)據(jù)也已經(jīng)Seeder好了,就可以根據(jù)Repository模式來設(shè)計(jì)一些數(shù)據(jù)庫邏輯了。準(zhǔn)備趁著端午節(jié)研究下Repository模式的測試,PHPUnit結(jié)合Mockery包來TDD測試也是一種不錯(cuò)的玩法。
M(Model)-V(View)-C(Controller)模式去組織代碼,很多時(shí)候也未必指導(dǎo)性很強(qiáng),給Model加一個(gè)Repository,給Controller加一個(gè)Service,給View加一個(gè)Presenter,或許代碼結(jié)構(gòu)更清晰。具體可看下面分享的一篇文章。
最近一直在給自己充電,研究MySQL,PHPUnit,Laravel,上班并按時(shí)打卡,看博客文章,每天喝紅牛。很多不會(huì),有些之前沒咋學(xué)過,哎,頭疼。后悔以前讀書太少,書到用時(shí)方恨少,人丑還需多讀書。
研究生學(xué)習(xí)機(jī)器人的,本打算以后讀博搞搞機(jī)器人的(研一時(shí)真是這么想真是這么準(zhǔn)備的,too young too simple)。現(xiàn)在做PHP小碼農(nóng)了,只因當(dāng)時(shí)看到智能機(jī)就激動(dòng)得不行,決定以后做個(gè)碼農(nóng)試試吧,搞不好是條生路,哈哈。讀書時(shí)覺悟太晚,耗費(fèi)了青春,其實(shí)我早該踏入這條路的嘛,呵呵。Follow My Heart!
不扯了,在凌晨兩點(diǎn)邊聽音樂邊寫博客,就容易瞎感慨吧。。
分享下最近發(fā)現(xiàn)的一張好圖和一篇極贊的文章:
文章鏈接:Laravel的中大型專案架構(gòu)
歡迎關(guān)注Laravel-China。
RightCapital招聘Laravel DevOps
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/21662.html
摘要:說明本文主要講述使用作為緩存加快頁面訪問速度。何不用來做緩存,等到該達(dá)到一定瀏覽頁面后再刷新下,效率也很高。可作緩存系統(tǒng)隊(duì)列系統(tǒng)。 說明:本文主要講述使用Redis作為緩存加快頁面訪問速度。同時(shí),作者會(huì)將開發(fā)過程中的一些截圖和代碼黏上去,提高閱讀效率。 備注:作者最近在學(xué)習(xí)github上別人的源碼時(shí),發(fā)現(xiàn)好多在計(jì)算一篇博客頁面訪問量view_count時(shí)都是這么做的:利用Laravel...
摘要:本文首發(fā)于作者這是一篇基礎(chǔ)教程,對(duì)標(biāo)文檔中的數(shù)據(jù)遷移和數(shù)據(jù)填充。那么,中的數(shù)據(jù)庫遷移概念,就是用于解決團(tuán)隊(duì)中保證數(shù)據(jù)庫結(jié)構(gòu)一致的方案。和不同,如果多次執(zhí)行就會(huì)進(jìn)行多次數(shù)據(jù)填充。好了,數(shù)據(jù)遷移和數(shù)據(jù)填充的基本操作也就這些了。 showImg(https://segmentfault.com/img/remote/1460000012252769?w=648&h=422); 本文首發(fā)于 h...
摘要:本文經(jīng)授權(quán)轉(zhuǎn)自社區(qū)說明開發(fā)者使用部署一個(gè)新項(xiàng)目的時(shí)候通常會(huì)使用快速填充本地?cái)?shù)據(jù)以方便開發(fā)調(diào)試擴(kuò)展包提供了可將數(shù)據(jù)表里的數(shù)據(jù)直接轉(zhuǎn)換為文件的功能本項(xiàng)目由團(tuán)隊(duì)成員整理發(fā)布首發(fā)地為社區(qū)使用場景通常情況下我們會(huì)希望本地開發(fā)環(huán)境數(shù)據(jù)與生產(chǎn)完全一致這樣 本文經(jīng)授權(quán)轉(zhuǎn)自 PHPHub 社區(qū) 說明 開發(fā)者使用 Laravel 部署一個(gè)新項(xiàng)目的時(shí)候, 通常會(huì)使用 seeder 快速填充本地?cái)?shù)據(jù)以方便開發(fā)...
閱讀 691·2021-11-23 09:51
閱讀 3281·2019-08-30 15:54
閱讀 445·2019-08-30 15:52
閱讀 3117·2019-08-30 13:58
閱讀 2920·2019-08-30 13:53
閱讀 2689·2019-08-29 14:18
閱讀 2421·2019-08-27 10:54
閱讀 2371·2019-08-26 18:09