Laravel:使用Faker播种多个唯一的列 [英] Laravel: Seeding multiple unique columns with Faker

查看:57
本文介绍了Laravel:使用Faker播种多个唯一的列的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简介

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

背景

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

ImageText MySQL 中有一个约束,即 image_id 和语言的组合必须唯一.

  class CreateImageTextsTable扩展了迁移{公共功能up(){Schema :: create('image_texts',function($ table){...$ table-> unique(['image_id','language']);...});}... 

现在,我希望每个图片在播种后都具有多个 ImageText 模型.对于模型工厂和播种机,这很容易:

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

问题

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

  [PDOException]SQLSTATE [23000]:违反完整性约束:1062键"image_texts_image_id_language_unique"的条目"76-gn"重复 

这是因为在某个时候,伪造者会在for循环内为图像随机两次使用相同的languageCode,从而打破了['image_id','language']的唯一约束.

您可以更新您的 ImageTextFactory 这样说:

  $ factory-> define(App \ Models \ ImageText :: class,function(Faker \ Generator $ faker){返回 ['language'=>$ faker-> unique()-> languageCode,'title'=>$ faker-> word,'文本'=>$ faker->文本,];}); 

但是,当您创建了足够多的imageTexts后,就会出现伪造者用完languageCodes的问题.

当前解决方案

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

问题

是否可以将要保存的模型发送到工厂?如果是这样,我可以在工厂内部进行检查,以查看当前Image是否已附加任何ImageText,如果没有,请重置languageCodes的唯一计数器.我的目标是这样的:

  $ factory-> define(App \ Models \ ImageText :: class,function(Faker \ Generator $ faker){$ firstImageText =空($ image-> imageTexts());返回 ['language'=>$ faker-> unique($ firstImageText)-> languageCode,'title'=>$ faker-> word,'文本'=>$ faker->文本,];}); 

当前当然会给出:

  [ErrorException]未定义的变量:图片 

是否可以通过某种方式实现这一目标?

解决方案

我已解决

我进行了很多搜索以寻找解决此问题的方法,结果发现许多其他人也遇到了这种情况.如果您在关系的另一端只需要一个元素,这很简单.

增加了多列唯一限制",这使事情变得复杂.我发现的唯一解决方案是忘记MySQL的限制,只用try-catch捕获PDO异常来围绕工厂创建".感觉这是一个糟糕的解决方案,因为其他PDOExceptions也将被捕获,而且感觉并不正确".

解决方案

为了完成这项工作,我将播种器分为ImageTableSeeder和ImageTextTableSeeder,它们都非常简单.它们的运行命令都看起来像这样:

 公共函数run(){factory(App \ Models \ ImageText :: class,100)-> create();} 

魔术发生在ImageTextFactory内部:

  $ factory-> define(App \ Models \ ImageText :: class,function(Faker \ Generator $ faker){//选择要附加的图像$ image = App \ Models \ Image :: inRandomOrder()-> first();$ image instanceof App \ Models \ Image?$ imageId = $ image-> id:$ imageId = null;//生成唯一的imageId-languageCode组合$ imageIdAndLanguageCode = $ faker-> unique()-> regexify("/^ $ imageId- [a-z] {2}");$ languageCode = explode('-',$ imageIdAndLanguageCode)[1];返回 ['image_id'=>$ imageId,'language'=>$ languageCode,'title'=>$ faker-> word,'文本'=>$ faker->文本,];}); 

就这样:

  $ 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的键组合.只要有可能,这似乎是解决此问题的好方法.

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. ImageText has an image_id column, a language column and a text column.

ImageText has a constraint in MySQL that the combination image_id and language has to be unique.

class CreateImageTextsTable extends Migration
{

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

            ...

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

            ...

        });
    }

    ...

Now, I want each Image to have several ImageText models after seeding is done. This is easy with model factories and this seeder:

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:

[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'].

You can update your ImageTextFactory to say this:

$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.

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.

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:

$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?

解决方案

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".

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:

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

The magic happens inside the 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.

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!

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.

这篇关于Laravel:使用Faker播种多个唯一的列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆