Laravel5.5でほぼ完成されたModelFactoryの使い方(7系まで

Laravelの機能であるModelFactory、意外と使ってない人も多いと思います。
正直私自身、個人で新しく作ろうと始めたWebアプリ開発でちょっと使ってみて、「おおこれは良いんじゃないか」と思ったので少しまとめました。

# 環境

タイトルでも上げているとおり、5.5を前提に記載しています。
5.5で使えるコマンドなども使うので、概念的な部分は5.4でもいけますが5.5推奨。

**ModelFactoryはLaravel8で関数呼び出しからクラスからの呼び出しに変更されました。現状この記事はLaravel7までの作り方になっています。**

8系に関してはucan-labさんが記事を書いてくださってるので、そちらを見ると良いと思います。
[Laravel8で完成されたModelFactoryの使い方](https://qiita.com/ucan-lab/items/788d1a603f2b53ebb999)


# そもそもModelFactoryって何なのか

この点はまだ私もしっかりと把握できていません。
いわゆるテストなどで利用するダミーデータを簡単に用意できるようにする仕組みといったところでしょうか。
ダミーデータの作成自体はFakerが受け持ってる訳ですが、これがModelFactoryとして使われることで良いところを書こうと思います。

# まず設定

はじめにModelFactoryで利用するFakerが英語の言語セットを使うのでこれを日本語のセットを使うように変更をします。

これはソース(DatabaseServiceProvider)を見るとわかりますが、`config/app.php`の設定で解決します。

app.phpのaliasesの下にでも書いてください。

```php
'faker_locale' => 'ja_JP',
```

これを書いておくことで、名前などのダミーデータが日本語に変わります。
一応設定を変更した後はconfigのキャッシュをクリアしておきましょう。(`php artisan config:clear`)

### もしもFakerにグローバルに追加のプロバイダを読み込ませたい場合

AppServiceProviderでFackerGeneratorをextendしてしまえば良いと思います。

```php
$this->app->extend(\Faker\Generator::class, function(FakerGenerator $faker) {
    $faker->addProvider(new \Faker\Provider\XXXX\XXXXXX($faker));
});
```

ローカルに読み込ませる場合はこの後のModelFactory内で読み込ませましょう。

# モデルとFactoryを作る

最初の時点でUserモデルと、UserFactoryは存在すると思います。
自分で作るにはコマンドで作るのが楽です。

## モデルと一緒にFactoryも作る

```
php artisan make:model "Model\Post" --factory
```

`Model\Post`とすると自動的に`app/Model/Post.php`として作られるので便利。
そしてオプションとして`--factory`を指定していますが、`-f`でも問題ありません。(5.5で`-f`は`--factory`の省略形となっています。`--force`の省略形ではありません。)

Factoryの方は、`database/factories/ModelPostFactory.php`として生成されます。

```php
<?php

use Faker\Generator as Faker;

$factory->define(App\Model\Post::class, function (Faker $faker) {
    return [
    ];
});

```

## Factory単体を作る

```
php artisan make:factory PostFactory
```

一応モデルも`-m`オプションで指定することで生成時に埋め込めますが使っても使わなくてもあまり変わらない。

# 例

よくある、users-posts-commentsの例です。

`users`はそのまま、`posts`は`(id,user_id,title,body)`です。`comments`はわざと`(id,body)`のみ。追加で`post_comments`テーブルは`(post_id,comment_id)`があるとします。

pivot環境下の話もしたかったので。

## UserFactory

UserFactoryは初期のままです。

```php
$factory->define(App\User::class, function (Faker $faker) {
    static $password;

    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => $password ?: $password = bcrypt('secret'),
        'remember_token' => str_random(10),
    ];
});
```

一応ここで、書き方を説明しておくと。。。といってもわかりますよね。
カラム名と値を渡しています。

faker自体の使い方は割愛しますが、そんな難しいものでもないので大丈夫でしょう。

## ModelPostFactory

ModelPostFactoryはこんな感じ。

```php
$factory->define(App\Model\Post::class, function (Faker $faker) {
    return [
        'user_id' => function() {
            return factory(App\User::class)->create()->id;
        },
        'title' => $faker->name,
        'body' => $faker->name,
    ];
});
```

user_idの部分はこのFactoryをcreateしたときに、Userをcreateしたidを返してくれるように書けるので便利。

`$faker->name`の箇所は割愛させて。

## ModelCommentFactory

ModelCommentFactoryはこんな感じ。

```php
$factory->define(App\Model\Post::class, function (Faker $faker) {
    return [
        'body' => $faker->name,
    ];
});
```

## app/Model/Post.php

ここで、多対多なのでPostモデルだけ確認しておきます。

```php
<?php

namespace App\Model;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $table = 'posts';

    public function comments()
    {
        return $this->belongsToMany(Comment::class, 'post_comments');
    }
}
```

同様に、PostへのhasManyのリレーションがUser.phpに存在する前提とします。


# ダミーデータを用意する

ダミーデータを作る方法です。

基本的に作るにはseederに記載します。
ここでは割愛したいので最初からあるDatabaseSeederのrunメソッドに書く前提とします。

## リレーションなどを気にせずに単体で作る

これは簡単です。

```php
factory(App\Model\User::class, 10)->create();
```

これだけです。10の部分は回数で10回インサートがされます。

## リレーションを意識して作る

これは先程のModelPostFactoryでの書き方です。
Factoryの設定側に書くことで解決できます。

## 多対多のリレーションなども気にしながら作る

これは少しむずかしいですが、慣れてしまえばちょいちょいです。

```php
factory(App\Model\Post::class, 5)
    ->create()
    ->each(function(App\Model\Post $post) {
        $post->comments()->saveMany(factory(App\Model\Comment::class, rand(0, 3))->make());
    });
```

このような感じで対応できます。
createした後はModelのCollectionが返ってくるのでそれに従って追加するという形になります。

## たまにModelFactory側で設定した設定が困る時

たまにModelPostFactoryのuser_idで指定したような記載があることで、不要にデータが生成されてしまうことがあります。
いわゆるeachでループしてしまって、Factory作ってみたらFactory内でもcreate処理があって不要なんだけどという話です。

こういうときはループ内でのmake時に対象のカラムに他の値を設定することで回避可能です。

```php
$post->xxxx()->saveMany(factory(App\XXXX::class)->make(['post_id' => null]));
```

## 中間テーブルにもデータがあって、ランダム性を出したい時

これはまだ確認していませんが、カスタムピボットモデルを使うことでModelFactoryが作れるのでこれで解決しそうです。

# 最後に

意外とModelFactoryってリレーションもうまく出来るんや！と感心しました。
リレーションが組めるのでテストには最適だと思います。しばらく使いますかねー。