Генерация начальных данных в Laravel

Задача: необходимо было сгенерировать данных для таблиц people, articles и comments.
При создании моделей использовал следующие команды:

php artisan make:model People -mf
php artisan make:model Article -mf
php artisan make:model Comment -mf

параметр -f отвечает за factory, которые я хотел использовать.

Создались 3 модели, в которые добавил методы для создания связей:

/app/People.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class People extends Model
{
    public function articles()
    {
        return $this->hasMany(Article::class, 'author_id', 'id');
    }
}

/app/Article.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    public function people()
    {
        return $this->belongsTo(People::class, 'author_id', 'id');
    }
}

/app/Comment.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    public function article()
    {
        return $this->belongsTo(Article::class, 'article_id', 'id');
    }

    public function people()
    {
        return $this->belongsTo(People::class, 'author_id', 'id');
    }
}

factory создались по адресу(их я уже немного дополнил):

/database/factories/ArticleFactory.php

use Faker\Generator as Faker;

$factory->define(App\Article::class, function (Faker $faker) {
    return [
        'title' => $faker->name,
    ];
});

/database/factories/PeopleFactory.php

use Faker\Generator as Faker;

$factory->define(App\People::class, function (Faker $faker) {
    return [
        'first_name' => $faker->name,
        'last_name' => $faker->lastName,
        'twitter' => '@' . $faker->firstName,
    ];
});

/database/factories/CommentFactory.php

use Faker\Generator as Faker;

$factory->define(App\Comment::class, function (Faker $faker) {
    return [
        'body' => $faker->text(100),
    ];
});

Следующим шагом генерировал классы начальных данных:

php artisan make:seeder PeopleTableSeeder
php artisan make:seeder ArticleTableSeeder
php artisan make:seeder CommentTableSeeder

Дальше в /database/seeds/DatabaseSeeder.php в методе run объявил вызов созданных классов

public function run()
{
	$this->call(PeopleTableSeeder::class);
	$this->call(ArticleTableSeeder::class);
	$this->call(CommentTableSeeder::class);
}

Прочитал документацию получилось следующее:

database/seeds/PeopleTableSeeder.php

use Illuminate\Database\Seeder;

class PeopleTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\People::class, 10)->create();
    }
}

database/seeds/ArticleTableSeeder.php

use Illuminate\Database\Seeder;

class ArticleTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\Article::class, 3)->make()->each(function ($article) {
            //take random people
            $people = App\People::orderByRaw('RAND()')->first();
            $article->people()->associate($people)->save();
        });
    }
}

database/seeds/CommentTableSeeder.php

use Illuminate\Database\Seeder;

class CommentTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\Comment::class, 1)->make()->each(function ($comment) {
            $people = App\People::orderByRaw('RAND()')->first();
            $article = App\Article::orderByRaw('RAND()')->first();
            $comment->people()->associate($people);
            $comment->article()->associate($article);
            $comment->save();
        });
    }
}

Для того, чтобы использовать уже существующие сущности для привязки, использовал следующую конструкцию

 App\People::orderByRaw('RAND()')->first();

P.S. Как мне подсказали позднее: можно было часть логики генерации упростить и вставить в factory, например factory Article:

<?php 
use Faker\Generator as Faker; 

$factory->define(App\Article::class, function (Faker $faker) {
    return [
        'author_id' => function () {
            return factory(App\People::class)->create()->id;
        },
        'title' => $faker->sentence($nbWords = 6, $variableNbWords = true),
    ];
});

Подводные камни

1. В процессе создания столкнулся с такой проблемой из-за недопонимания связи.
Изначально, в классе ArticleTableSeeder, чтобы привязать people к article, я пытался сделать так

$a->people()->save(factory(App\People::class)->create());

И получал ошибку:

BadMethodCallException : Method Illuminate\Database\Query\Builder::save does not exist.

Оказалось, что так сохранять можно, если в методе people() связь hasMany, hasOne, в моем случае там была реализована связь belongsTo.
Это связь дочернего элемента к родителю и чтобы сохранить, необходимо выполнить ассоциацию с родителем и сохранить:

$people = App\People::orderByRaw('RAND()')->first();
$a->people()->associate($people)->save();

2. Важно понимать разницу между методами make() и create() в FactoryBuilder

factory(App\Comment::class)->make() — создает экземпляр App\Comment, но не сохраняем в БД
factory(App\Comment::class)->create() — создает экземпляр App\Comment и сохраняет в БД

из-за не понимания разницы, не мог понять, почему получаю ошибку вида:

Illuminate\Database\QueryException : SQLSTATE[HY000]: General error: 1364 Field ‘author_id’ doesn’t have a default value (SQL: insert into `articles` (`title`, `updated_at`, `created_at`) values (Ilene Kiehn, 2018-08-03 10:01:56, 2018-08-03 10:01:56))

Полезный материал:
https://laracasts.com/discuss/channels/eloquent/save-on-polymorphic-relation-dont-work
https://laravel.com/docs/5.6/seeding

Оставить комментарий