Ответ 1
Вы также можете использовать saveMany. Например:
factory(User::class, 10)->create()->each(function ($user) {
$user->posts()->saveMany(factory(Posts::class, 5)->make());
});
В 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 (без посева каких-либо отношений), а затем вытащить случайных пользователей из БД по мере необходимости, пока вы высеиваете другие Модели. Однако в тех случаях, когда они должны быть уникальными, вам нужно будет отслеживать, какие пользователи были использованы. Кроме того, похоже, что это добавит много дополнительных запросов к процессу посева.
Вы также можете использовать saveMany. Например:
factory(User::class, 10)->create()->each(function ($user) {
$user->posts()->saveMany(factory(Posts::class, 5)->make());
});
Лично я считаю, что один класс 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();
}
}
$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]);
Вы можете сделать это, используя закрытие в 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]);
});
ПРИМЕЧАНИЕ. Этот метод не создает ненужные записи в базе данных, вместо этого переданные атрибуты назначаются ПЕРЕД созданием связанных записей.