php-Laravel:使用Faker播种多个唯一列
作者:互联网
介绍
伙计们,我对模型工厂和多个唯一列有疑问:
背景
我有一个名为Image的模型.此模型的语言支持存储在单独的模型ImageText中. ImageText具有image_id列,语言列和文本列.
ImageText在MySQL中有一个约束,即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());
}
});
问题
但是,当使用模型工厂和fakerr进行播种时,通常会看到以下消息:
[PDOException]
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '76-gn' for key 'image_texts_image_id_language_unique'
这是因为在某个时刻,在for循环内,伪造者将对图像随机两次使用相同的languageCode,从而打破了[‘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,
];
});
但是,然后,您会遇到一个问题,即在创建足够多的imageTexts之后,伪造者将用完languageCodes.
当前解决方案
当前,可以通过为ImageText设置两个不同的工厂来解决此问题,其中一个工厂重置languageCodes的唯一计数器,而种子程序调用该工厂,该工厂在进入for循环之前重置唯一计数器,以创建更多ImageText.但这是代码重复,应该有更好的方法来解决此问题.
问题
有没有办法将您要保存的模型发送到工厂?如果是这样,我可以在工厂内部进行检查,以查看当前Image是否已附加任何ImageText,如果没有,请重置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
是否有可能以某种方式实现这一目标?
解决方法:
我解决了
我进行了很多搜索以寻找解决此问题的方法,结果发现许多其他人也都经历过.如果您在关系的另一端只需要一个元素,则为it’s very straight forward.
增加了“多列唯一限制”使事情变得复杂.我发现的唯一解决方案是“忘记MySQL的限制,只用try-catch捕获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-expression中使用imageId,并添加我们唯一组合中还包括的所有内容,在这种情况下,用’-‘字符分隔.这将生成诸如“ 841-en”,“ 58-bz”,“ 96-xx”等的结果,其中imageId在我们的数据库中始终是真实图像,或者为null.
由于我们将唯一标签与imageId一起粘贴到语言代码上,因此我们知道image_id和languageCode的组合将是唯一的.这正是我们所需要的!
现在,我们可以使用以下命令简单地提取创建的语言代码或我们想要生成的任何其他唯一字段:
$languageCode = explode('-', $imageIdAndLanguageCode)[1];
此方法具有以下优点:
>无需捕获异常
>工厂和播种机可以分开以提高可读性
>代码紧凑
此处的缺点是,您只能生成其中一个键可以表示为regex的键组合.只要有可能,这似乎是解决此问题的好方法.
标签:laravel-seeding,laravel-5-4,laravel,mysql,php 来源: https://codeday.me/bug/20191026/1933885.html