Laravel - 播种关系

Posted

技术标签:

【中文标题】Laravel - 播种关系【英文标题】:Laravel - Seeding Relationships 【发布时间】:2016-05-28 17:11:00 【问题描述】:

在 Laravel 中,数据库播种通常是通过模型工厂来完成的。因此,您使用 Faker 数据为您的模型定义蓝图,并说明您需要多少个实例:

$factory->define(App\User::class, function (Faker\Generator $faker) 
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => bcrypt(str_random(10)),
        'remember_token' => str_random(10),
    ];
);

$user = factory(App\User::class, 50)->create();

但是,假设您的 User 模型与许多其他模型具有 hasMany 关系,例如 Post 模型:

Post:
   id
   name
   body
   user_id

因此,在这种情况下,您希望使用在您的用户表中播种的实际 用户来播种您的帖子表。这似乎没有明确讨论,但我确实在 Laravel 文档中找到了以下内容:

$users = factory(App\User::class, 3)
    ->create()
    ->each(function($u) 
         $u->posts()->save(factory(App\Post::class)->make());
    );

因此,在您的用户工厂中,您为您创建的每个用户创建 X 个帖子。然而,在一个可能有 50 到 75 个模型与用户模型共享关系的大型应用程序中,您的用户播种器基本上最终会用它的所有关系播种整个数据库。

我的问题是:这是处理此问题的最佳方法吗?我能想到的唯一另一件事是先播种用户(不播种任何关系),然后在播种其他模型时根据需要从数据库中提取随机用户。但是,如果它们需要唯一,您必须跟踪使用了哪些用户。此外,这似乎会在播种过程中增加大量额外的查询量。

【问题讨论】:

【参考方案1】:
$factory->define(App\User::class, function (Faker\Generator $faker) 
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => bcrypt(str_random(10)),
        'remember_token' => str_random(10),
    ];
);

$factory->define(App\Post::class, function (Faker\Generator $faker) 
    return [
        'name' => $faker->name,
        'body' => $faker->paragraph(1),
        'user_id' => factory(App\User::class)->create()->id,
    ];
);

所以现在如果您这样做factory(App\Post::class, 4)->create(),它将创建 4 个不同的帖子,并且在此过程中还会创建 4 个不同的用户。

如果您希望所有帖子都使用同一用户,我通常会这样做:

$user = factory(App\User::class)->create();
$posts = factory(App\Posts::class, 40)->create(['user_id' => $user->id]);

【讨论】:

感谢您的回复。我一直在尝试这样的事情。但是,如果我想为其他 20 个模型使用同一组用户,我觉得我的播种机很快就会变得混乱。本质上,我所有的播种逻辑最终都发生在一个文件中,因为“用户”与许多其他模型相关联。但也许没有更好的方法【参考方案2】:

我个人认为管理这些关系的一个 Seeder 类比单独的 Seeder 类更好,因为您将所有逻辑放在一个地方,所以一眼就能看出发生了什么。 (任何知道更好方法的人:请分享):)

一种解决方案可能是:在类中使用一个 DatabaseSeeder 和私有方法,以使“运行”方法更简洁。我在下面有这个例子,它有一个用户、链接、链接用户(多对多)和一个注释(多对一)。

对于多对多关系,我首先创建所有链接,并获取插入的 ID。 (由于 id 是 auto-inc,我认为可以更容易地获取 id(获取最大值),但在本例中无关紧要)。然后创建用户,并为每个用户附加一些随机链接(多对多)。它还为每个用户创建随机注释(多对一示例)。它使用“工厂”方法。

如果您替换“帖子”的“链接”,这应该可以工作。 (然后您可以删除“注释”部分...)

(还有一种方法可以确保您拥有 1 个具有您自己的登录凭据的有效用户。)

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    
        // Create random links
        factory(App\Link::class, 100)->create();

        // Fetch the link ids
        $link_ids = App\Link::all('id')->pluck('id')->toArray();

        // Create random users
        factory(App\User::class, 50)->create()->each(function ($user) use ($link_ids) 

            // Example: Many-to-many relations
            $this->attachRandomLinksToUser($user->id, $link_ids);

            // Example: Many-to-one relations
            $this->createNotesForUserId( $user->id );
        );

        // Make sure you have a user to login with (your own email, name and password)
        $this->updateCredentialsForTestLogin('john@doe.com', 'John Doe', 'my-password');
    

    /**
     * @param $user_id
     * @param $link_ids
     * @return void
     */
    private function attachRandomLinksToUser($user_id, $link_ids)
    
        $amount = random_int( 0, count($link_ids) ); // The amount of links for this user
        echo "Attach " . $amount . " link(s) to user " . $user_id . "\n";

        if($amount > 0) 
            $keys = (array)array_rand($link_ids, $amount); // Random links

            foreach($keys as $key) 
                DB::table('link_user')->insert([
                    'link_id' => $link_ids[$key],
                    'user_id' => $user_id,
                ]);
            
        
    

    /**
     * @param $user_id
     * @return void
     */
    private function createNotesForUserId($user_id)
    
        $amount = random_int(10, 50);
        factory(App\Note::class, $amount)->create([
            'user_id' => $user_id
        ]);
    

    /**
     * @param $email
     * @param $name
     * @param $password
     * @return void
     */
    private function updateCredentialsForTestLogin($email, $name, $password)
    
        $user = App\User::where('email', $email)->first();
        if(!$user) 
            $user = App\User::find(1);
        
        $user->name = $name;
        $user->email = $email;
        $user->password = bcrypt($password); // Or whatever you use for password encryption
        $user->save();
    

【讨论】:

【参考方案3】:

您也可以使用 saveMany。例如:

factory(User::class, 10)->create()->each(function ($user) 
    $user->posts()->saveMany(factory(Posts::class, 5)->make());
);

【讨论】:

直到看到这个答案,我才知道saveMany()。谢谢你。 :) saveMany 与 create 相比有什么区别/优势?【参考方案4】:

您可以使用 ModelFactory 中的闭包来执行此操作,如 here 所述。

此解决方案也可以干净优雅地与播种机配合使用。

$factory->define(App\User::class, function (Faker\Generator $faker) 
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => bcrypt(str_random(10)),
        'remember_token' => str_random(10),
    ];
);

$factory->define(App\Post::class, function (Faker\Generator $faker) 
    return [
        'name' => $faker->name,
        'body' => $faker->paragraph(1),
        'user_id' => function() 
            return factory(App\User::class)->create()->id;
        ,
    ];
);

对于你的播种机,使用如下简单的东西:

//create 10 users
factory(User::class, 10)->create()->each(function ($user) 
    //create 5 posts for each user
    factory(Post::class, 5)->create(['user_id'=>$user->id]);
);

注意:此方法不会在数据库中创建不需要的条目,而是在创建关联记录之前分配传递的属性。

【讨论】:

应该是最好的答案:) 谢谢!您的回答为我节省了很多时间。 最佳答案之一。此外,这些天你可以在 post factory 'user_id' =&gt; factory(User::class) 中使用稍微简单的方法,不再需要 clojure。【参考方案5】:

我想分享我为向许多用户插入许多帖子所采取的方法:`

factory(App\User::class, 50)->create() 
                ->each( 
                    function ($u) 
                        factory(App\Post::class, 10)->create()
                                ->each(
                                    function($p) use (&$u)  
                                        $u->posts()->save($p)->make();
                                    
                                );
                    
                );

`

在整天寻找一种建立关系的方法之后,这个解决方法对我有用

【讨论】:

【参考方案6】:

这在 laravel v8 中对我有用

for ($i=0; $i<=2; $i++) 
    $user = \App\Models\User::factory(1)->create()->first();
    $product = \App\Models\Product::factory(1)->create(['user_id' => $user->id])->first();

【讨论】:

似乎有很多无意义的代码。 User::factory()-&gt;count(3)-&gt;hasProducts()-&gt;create(); 做同样的事情。【参考方案7】:

我使用定制的 relateOrCreate 函数在数据库中找到该模型的随机条目。如果不存在,则创建一个新的:

function relateOrCreate($class) 
    $instances = $class::all();
    $instance;

    if (count($instances) > 0) 
        $randomIndex = rand(0, (count($instances) - 1));
        $instance = $instances[$randomIndex];
    
    else 
        $instance = $class::factory()->create();
    

    return $instance;

然后我就这样使用它:

$relatedUser = relateOrCreate(User::class);

return [
    'user_id' => $relatedUser->id,
    // ...
];

【讨论】:

从数据库中选择一个随机行比从数据库中选择每一行然后随机选择一个更有意义。似乎也没有必要,因为如果您正在为数据库播种,您就知道是否存在其他记录。

以上是关于Laravel - 播种关系的主要内容,如果未能解决你的问题,请参考以下文章

Laravel - 播种多对多关系

Laravel:当表有两个外键时,为具有关系的数据库表播种

Laravel 4 db 种子特定播种器文件

Laravel 5 播种

Laravel 8 Jetstream:无法使用使用工厂和播种机播种的帐户登录

laravel 8 播种,SQLSTATE[23000]:违反完整性约束: