2017-04-04 26 views
1

介紹Laravel:播種多個獨特的列與法克爾

怎麼了夥計,我有一個關於模型的工廠和多個唯一列問題:

背景

我有一個型號命名圖片。此型號具有存儲在單獨型號中的語言支持,ImageTextImageText有一個image_id列,一個語言列和一個文本列。

ImageTextMySQL的的約束,該組合image_id和語言必須是唯一的。

class CreateImageTextsTable extends Migration 
{ 

    public function up() 
    { 
     Schema::create('image_texts', function ($table) { 

      ... 

      $table->unique(['image_id', 'language']); 

      ... 

     }); 
    } 

    ... 

現在,我希望每個圖片播種完成後有幾個ImageText模型。這是很容易與模型的工廠,這播種機:

factory(App\Models\Image::class, 100)->create()->each(function ($image) { 
    $max = rand(0, 10); 
    for ($i = 0; $i < $max; $i++) { 
     $image->imageTexts()->save(factory(App\Models\ImageText::class)->create()); 
    } 
}); 

問題

然而,播種這種使用模式工廠和攤販時,你往往留下了這樣的信息:

[PDOException]                             
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '76-gn' for key 'image_texts_image_id_language_unique' 

這是因爲在某些情況下,在for循環中,faker會爲圖像隨機使用同一個語言代碼兩次,從而打破['image_id','language']的唯一約束。

您可以更新ImageTextFactory這樣說:

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

    return [ 
     'language' => $faker->unique()->languageCode, 
     'title' => $faker->word, 
     'text' => $faker->text, 
    ]; 
}); 

但是,你反而得到了攤販將耗盡languageCodes已經創造出足夠的imageTexts後問題。

目前的解決方案

這是目前具有用於ImageText,其中一個爲languageCodes復位獨特的計數器和播種機調用工廠,進入for循環之前重置TE獨特的櫃檯兩個不同的工廠解決創建更多的ImageTexts。但這是代碼重複,應該有更好的方法來解決這個問題。

問題

有沒有辦法送你節省到工廠模式?如果是這樣,我可以在工廠內部進行檢查,以查看當前Image是否已經附加了任何ImageTexts,如果沒有,則重置languageCodes的唯一計數器。我的目標將是這樣的:

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

    $firstImageText = empty($image->imageTexts()); 

    return [ 
     'language' => $faker->unique($firstImageText)->languageCode, 
     'title' => $faker->word, 
     'text' => $faker->text, 
    ]; 
}); 

這當然目前給出:

[ErrorException]   
Undefined variable: image 

是否有可能以某種方式實現這一目標?

回答

1

我解決了它

我搜索了很多爲解決這個問題,發現很多人也經歷過。如果您只需要關係另一端的一個元素,it's very straight forward

「多列唯一限制」的添加使這變得複雜。我發現的唯一解決方案是「忘記MySQL的限制,只是爲了PDO例外而繞過工廠創建」。這感覺像是一個糟糕的解決方案,因爲其他PDOExceptions也會被捕獲,並且它感覺不到「正確」。

解決方案

爲了使這項工作我分了播種機,以ImageTableSeeder和ImageTextTableSeeder,他們都非常直截了當。他們的運行命令都這個樣子:

public function run() 
{ 
    factory(App\Models\ImageText::class, 100)->create(); 
} 

的魔法ImageTextFactory內發生了:

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

    // Pick an image to attach to 
    $image = App\Models\Image::inRandomOrder()->first(); 
    $image instanceof App\Models\Image ? $imageId = $image->id : $imageId = null; 

    // Generate unique imageId-languageCode combination 
    $imageIdAndLanguageCode = $faker->unique()->regexify("/^$imageId-[a-z]{2}"); 
    $languageCode = explode('-', $imageIdAndLanguageCode)[1]; 

    return [ 
     'image_id' => $imageId, 
     'language' => $languageCode, 
     'title' => $faker->word, 
     'text' => $faker->text, 
    ]; 
}); 

這是它:

$imageIdAndLanguageCode = $faker->unique()->regexify("/^$imageId-[a-z]{2}"); 

我們使用的圖像標識的regexify表達和添加任何也包含在我們獨特的組合中,在這種情況下用' - '字符分隔。這將產生類似於「841-en」,「58-bz」,「96-xx」等的結果,其中imageId總是數據庫中的真實圖像,或者爲null。

由於我們將唯一標記與imageId一起粘貼到語言代碼,因此我們知道image_id和languageCode的組合將是唯一的。這正是我們需要的!

現在,我們可以簡單的提取創建語言代碼,或任何其他獨特的領域,我們希望產生,具有:

$languageCode = explode('-', $imageIdAndLanguageCode)[1]; 

這種方法具有以下優點:

  • 沒有必要趕例外
  • 工廠和播種機可分離以提高可讀性
  • 代碼緊湊

這裏的缺點是您只能生成組合鍵,其中一個鍵可以表示爲正則表達式。只要這是可能的,這似乎是解決這個問題的好方法。

0

您的解決方案僅適用於可作爲組合重新組合的事物。有許多用例,其中多個獨立的Faker生成的數字/字符串/其他對象的組合需要是唯一的,並且不能被重新整理。

對於這樣的情況下,你可以做一些事情,像這樣:

$factory->define(App\Models\YourModel::class, function (Faker\Generator $faker) { 
    static $combos; 
    $combos = $combos ?: []; 
    $faker1 = $faker->something(); 
    while($faker2 = $faker->somethingElse() && in_array([$faker1, $faker2], $combos) {} 
    $combos[] = [$faker1, $faker2]; 
    return ['field1' => $faker1, 'field2' => $faker2]; 
}); 

對於您的具體問題/使用的情況下,這裏的在同一行的解決方案:

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

    static $combos; 
    $combos = $combos ?: []; 

    // Pick an image to attach to 
    $image = App\Models\Image::inRandomOrder()->first(); 
    $image instanceof App\Models\Image ? $imageId = $image->id : $imageId = null; 

    // Generate unique imageId-languageCode combination 
    while($languageCode = $faker->languageCode && in_array([$imageId, $languageCode], $combos) {} 
    $combos[] = [$imageId, $languageCode]; 

    return [ 
     'image_id' => $imageId, 
     'language' => $languageCode, 
     'title' => $faker->word, 
     'text' => $faker->text, 
    ]; 
});