Laravel:使用 Faker 播种多个独特的列

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43202886/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 15:43:01  来源:igfitidea点击:

Laravel: Seeding multiple unique columns with Faker

phpmysqllaravellaravel-5.4laravel-seeding

提问by Rkey

Introduction

介绍

What up folks, I got a question about model factories and multiple unique columns:

伙计们,我有一个关于模型工厂和多个独特列的问题:

Background

背景

I have a model named Image. This model has language support stored in a separate model, ImageText. ImageTexthas an image_idcolumn, a language column and a text column.

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

ImageTexthas a constraint in MySQLthat the combination image_idand language has to be unique.

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

class CreateImageTextsTable extends Migration
{

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

            ...

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

            ...

        });
    }

    ...

Now, I want each Imageto have several ImageTextmodels after seeding is done. This is easy with model factories and this seeder:

现在,我希望每个Image在播种完成后都有几个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());
    }
});

Problem

问题

However, when seeding this using model factories and faker, you are often left with this message:

但是,在使用模型工厂和 faker 进行播种时,您经常会看到以下消息:

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

This is because at some point, inside that for loop, the faker will random the same languageCode twice for an image, breaking the unique constraint for ['image_id', 'language'].

这是因为在某个时刻,在 for 循环中,伪造者会为图像随机两次相同的语言代码,打破 ['image_id', 'language'] 的唯一约束。

You can update your ImageTextFactoryto say this:

你可以更新你的ImageTextFactory说这个:

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

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

But then, you instead get the problem that the faker will run out of languageCodes after enough imageTexts have been created.

但是,您会遇到这样的问题,即在创建了足够多的 imageTexts 后,faker 将耗尽 languageCodes。

Current solution

当前解决方案

This is currently solved by having two different factories for the ImageText, where one resets the unique counter for languageCodes and the seeder calls the factory which resets te unique counter before entering the for loop to create further ImageTexts. But this is code duplication, and there should be a better way to solve this.

目前通过为 ImageText 设置两个不同的工厂来解决这个问题,其中一个重置 languageCodes 的唯一计数器,seeder 调用工厂,在进入 for 循环以创建更多 ImageText 之前重置唯一计数器。但这是代码重复,应该有更好的方法来解决这个问题。

The question

问题

Is there a way to send the model you are saving on into the factory? If so, I could have a check inside the factory to see if the current Image has any ImageTexts attached already and if it doesn't, reset the unique counter for languageCodes. My goal would be something like this:

有没有办法将您保存的模型发送到工厂?如果是这样,我可以在工厂内部进行检查,看看当前图像是否已经附加了任何 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,
    ];
});

Which of course currently gives:

当然目前给出:

[ErrorException]           
Undefined variable: image

Is it possible to achieve this somehow?

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

采纳答案by Rkey

I solved it

我解决了

I searched a lot for a solution to this problem and found that many others also experienced it. If you only need one element on the other end of your relation, it's very straight forward.

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

The addition of the "multi column unique restriction" is what made this complicated. The only solution I found was "Forget the MySQL restriction and just surround the factory creation with a try-catch for PDO-exceptions". This felt like a bad solution since other PDOExceptions would also get caught, and it just didn't feel "right".

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

Solution

解决方案

To make this work I divided the seeders to ImageTableSeeder and ImageTextTableSeeder, and they are both very straight forward. Their run commands both look like this:

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

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

The magic happens inside the ImageTextFactory:

魔法发生在 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,
    ];
});

This is it:

就是这个:

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

We use the imageId in a regexify-expression and add whatever is also included in our unique combination, separated in this case with a '-' character. This will generate results like "841-en", "58-bz", "96-xx" etc. where the imageId is always a real image in our database, or null.

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

Since we stick the unique tag to the language code together with the imageId, we know that the combination of the image_id and the languageCode will be unique. This is exactly what we need!

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

Now we can simply extract the created language code, or whatever other unique field we wanted to generate, with:

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

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

This approach has the following advantages:

这种方法有以下优点:

  • No need to catch exceptions
  • Factories and Seeders can be separated for readability
  • Code is compact
  • 无需捕捉异常
  • 工厂和播种机可以分开以提高可读性
  • 代码紧凑

The disadvantage here is that you can only generate key combinations where one of the keys can be expressed as regex. As long as that's possible, this seems like a good approach to solving this problem.

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

回答by Paras

Your solution only works for things that can be regexified as a combination. There are many use cases where a combination of multiple separate Faker generated numbers/strings/other objects need to be unique and cannot be regexified.

您的解决方案仅适用于可以作为组合正则化的事物。在许多用例中,多个单独的 Faker 生成的数字/字符串/其他对象的组合需要是唯一的并且不能被正则化。

For such cases you can do something like so:

对于这种情况,您可以执行以下操作:

$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];
});

For your specific question / use case, here's a solution on the same lines:

对于您的特定问题/用例,以下是同一行的解决方案:

$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,
    ];
});