Laravel:使用Faker播种多个唯一的列

Rke*_*key 6 php mysql laravel laravel-seeding laravel-5.4

介绍

伙计们,我对模型工厂和多个唯一列有疑问:

背景

我有一个名为Image的模型。此模型的语言支持存储在单独的模型ImageText中ImageText具有image_id列,语言列和文本列。

ImageTextMySQL中有一个约束,即image_id和语言的组合必须唯一。

class CreateImageTextsTable extends Migration
{

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

            ...

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

            ...

        });
    }

    ...
Run Code Online (Sandbox Code Playgroud)

现在,我希望每个图像在完成播种后都具有多个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());
    }
});
Run Code Online (Sandbox Code Playgroud)

问题

但是,当使用模型工厂和fakerr进行播种时,通常会看到以下消息:

[PDOException]                                                                                                                 
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '76-gn' for key 'image_texts_image_id_language_unique'
Run Code Online (Sandbox Code Playgroud)

这是因为在某个时候,在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,
    ];
});
Run Code Online (Sandbox Code Playgroud)

但是,然后,您会遇到一个问题,即在创建足够多的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,
    ];
});
Run Code Online (Sandbox Code Playgroud)

当然,哪个会给出:

[ErrorException]           
Undefined variable: image
Run Code Online (Sandbox Code Playgroud)

是否有可能以某种方式实现这一目标?

Rke*_*key 7

我解决了

我搜索了很多解决这个问题的方法,发现很多其他人也遇到过。如果您只需要关系的另一端的一个元素,那就很简单了

添加“多列唯一限制”使这变得复杂。我找到的唯一解决方案是“忘记 MySQL 限制,只需用 try-catch for PDO-exceptions 来包围工厂创建”。这感觉是一个糟糕的解决方案,因为其他 PDOExceptions 也会被捕获,而且它只是感觉不“正确”。

解决方案

为了完成这项工作,我将播种机分为 ImageTableSeeder 和 ImageTextTableSeeder,它们都非常简单。他们的运行命令都是这样的:

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

魔法发生在 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,
    ];
});
Run Code Online (Sandbox Code Playgroud)

就是这个:

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

我们在 regexify-expression 中使用 imageId 并添加也包含在我们独特组合中的任何内容,在这种情况下用“-”字符分隔。这将生成“841-en”、“58-bz”、“96-xx”等结果,其中 imageId 始终是我们数据库中的真实图像,或者为空。

由于我们将 unique 标签与 imageId 一起粘贴到语言代码中,因此我们知道image_id 和 languageCode 的组合将是 unique。这正是我们所需要的!

现在我们可以简单地提取创建的语言代码,或者我们想要生成的任何其他唯一字段,使用:

$languageCode = explode('-', $imageIdAndLanguageCode)[1];
Run Code Online (Sandbox Code Playgroud)

这种方法有以下优点:

  • 不需要捕捉异常
  • 工厂和播种机可以分开以提高可读性
  • 代码紧凑

这里的缺点是您只能生成其中一个键可以表示为正则表达式的键组合。只要可能,这似乎是解决此问题的好方法。