Laravel - Относящиеся отношения

В 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();

Однако, скажем, ваша модель пользователя имеет отношение 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());
    });

Итак, в вашем User factory вы создаете X количество сообщений для каждого созданного вами пользователя. Тем не менее, в большом приложении, где, возможно, 50 - 75 моделей разделяют отношения с пользовательской моделью, ваш пользовательский сеятель по существу заканчивает всю базу данных всеми ее отношениями.

Мой вопрос: это лучший способ справиться с этим? Единственное, что я могу придумать, это сначала Seed the Users (без посева каких-либо отношений), а затем вытащить случайных пользователей из БД по мере необходимости, пока вы высеиваете другие Модели. Однако в тех случаях, когда они должны быть уникальными, вам нужно будет отслеживать, какие пользователи были использованы. Кроме того, похоже, что это добавит много дополнительных запросов к процессу посева.

Ответ 1

Вы также можете использовать saveMany. Например:

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

Ответ 2

Лично я считаю, что один класс Seeder для управления этими отношениями лучше разделяет классы сеялок, потому что у вас есть вся логика в одном месте, поэтому в одном взгляде вы можете видеть, что происходит. (Любой, кто знает лучший подход: пожалуйста, поделитесь):)

Решением может быть: один DatabaseSeeder и частные методы внутри класса, чтобы метод "run" был немного чище. У меня есть этот пример ниже, в котором есть User, Link, LinkUser (многие-ко-многим) и заметки (много-к-одному).

Для отношений "многие ко многим" я сначала создаю все ссылки и получаю вставленные идентификаторы. (поскольку идентификаторы являются auto-inc, я думаю, что идентификаторы можно было бы получить легче (получить max), но в данном примере это не имеет значения). Затем создайте пользователей и присоедините некоторые случайные ссылки каждому пользователю (многие-ко-многим). Он также создает случайные заметки для каждого пользователя (пример много-к-одному). Он использует методы factory.

Если вы замените "ссылку" для своей "публикации", это должно сработать. (Вы можете удалить раздел "Примечание", затем...)

(Существует также способ убедиться, что у вас есть 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('[email protected]', '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

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

Ответ 4

Вы можете сделать это, используя закрытие в ModelFactory, как обсуждалось здесь.

Это решение работает чисто и элегантно с сеятелями.

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

ПРИМЕЧАНИЕ. Этот метод не создает ненужные записи в базе данных, вместо этого переданные атрибуты назначаются ПЕРЕД созданием связанных записей.