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