【Laravel】Seederとバッチを使って初期データを揃える

# あらかじめ使うことが決まっているデータは先に作っておくと楽である
アプリケーション開発ではCRUD処理の実装が不可欠である。顧客データでも商品データでもCRUDを想定しないことは基本的にありえないはずである。使用者の利便性を考えてアプリ上でCRUD機能を搭載するが、マスターデータのようにあらかじめ使う（or現在進行系で使っている）ことが決まっている類のデータは、事前に初期値として登録しておくと色々と楽である（※）。
※データをリフレッシュした際に同じデータを再現するためにCRUDボタンをポチポチするのは、恐らく心が無になる作業である。
# データを準備する方法
今回は**Seeder**と**バッチ**を作成して初期データを作っていく。本来はSeederのみで収めたかったが、テーブルの親子関係を持った上で中身も全て決まっているデータを登録する際にSeederのみで突破することができなかった。そのため、今回は専用のバッチを作成して対応した。
ダミーデータを大量に作りたい時はFakerを使用するが、今回は目的が違うため別の機会で書こうと思う。
# 今回のケース：社内システムの構築
今回は実際に弊社内で動いている「社内システム構築」プロジェクトを元に書いていく。弊社は現在、2018年新卒プログラマの研修期間であり、その集大成として社内システム構築をLaravelで行ってもらっている。
内容を大雑把に伝えると**「社内ルーティーン作業の記録、集計、アラート」システム**である。
以下、下記のER図とともに簡単な概要を説明する。
 **Userテーブル**・・・所属する社員のテーブルである
 **Spaceテーブル**・・・社内のレイアウトを機能ごとに区切っており、そのマスターである（執務スペースや給湯室など）
 **Routineテーブル**・・・各spaceごとに個別ルーティーン作業が設定されている（ホワイトボード拭きやコーヒー淹れなど）
 **Recordテーブル**・・・各userがどのようなroutineを実行したか記録するテーブルである。今回はこれ以外の3つのテーブルにて初期値を作成していく。
### 参考ER図
![output.png](https://qiita-image-store.s3.amazonaws.com/0/210985/ae0dd736-381c-b4ca-df8d-e7e8877e3c4d.png)

# プロジェクトを作成する
今回は「initial-data」というプロジェクト名で進める。
`composer create-project --prefer-dist laravel/laravel initial-data "5.5.*"`
### DBを作成する
各々の環境で今回のためのDBを作成する。今回は「initial-data」というDB名で進める。
### .envでDBとの接続の設定をする
```
   DB_CONNECTION=mysql
   DB_HOST=127.0.0.1
   DB_PORT=3306
   DB_DATABASE=initial-data
   DB_USERNAME=root
   DB_PASSWORD=
```
### app.phpでタイムゾーンを日本にする
レコード登録の際にCarbonで現在時刻を求めるために基準を日本時間に合わせる

```php:config/app.php
  'timezone' => 'UTC',
  ↓
  'timezone' => 'Asia/Tokyo',
```

# モデル/マイグレーションの準備をする
### モデル/マイグレーションファイルを作成する
「-m」オプションをつければモデルとマイグレーションの2つのファイルを同時に作れる。
`$ php artisan make:model Space -m`
`$ php artisan make:model Routine -m`
`$ php artisan make:model Record -m`
### マイグレーションファイルでカラムの定義をする
```php:usersのマイグレーションファイル.php
 public function up()
 {
     Schema::create('users', function (Blueprint $table) {
         $table->increments('id');
         $table->string('name');
         $table->string('name_kana');
         $table->string('email')->unique();
         $table->string('password');
         // 一般利用者と管理権限を持つ利用者の2タイプを想定したカラム
         $table->integer('user_type');
         $table->rememberToken();
         $table->timestamps();
     });
 }
```

```php:spacesのマイグレーションファイル.php
 public function up()
 {
     Schema::create('spaces', function (Blueprint $table) {
         $table->increments('id');
         $table->string('name');
         $table->timestamps();
     });
 }
```

```php:routinesのマイグレーションファイル.php
 public function up()
 {
     Schema::create('routines', function (Blueprint $table) {
         $table->increments('id');
         $table->string('name');
         // 当該routineを実施するともらえる社内ポイントを示すカラム
         $table->integer('point');
         // 当該routineが実施されるべき頻度を示すカラム
         $table->integer('interval');
         $table->integer('space_id')->unsigned();
         $table->timestamps();
 
 		// Spaceモデルを親に持つため外部キーの設定をする
         $table->foreign('space_id')->references('id')->on('spaces')->onDelete('cascade');
     });
 }
```

```php:recordsのマイグレーションファイル.php
 public function up()
 {
     Schema::create('records', function (Blueprint $table) {
         $table->increments('id');
         $table->integer('point');
         $table->integer('user_id')->unsigned();
         $table->integer('routine_id')->unsigned();
         $table->timestamps();
 
         // User, Routineモデルを親に持つため外部キーの設定をする
         $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
         $table->foreign('routine_id')->references('id')->on('routines')->onDelete('cascade');
     });
 }
```

### モデル間のリレーションの定義をする
必要なカラムのほか、外部キーを設定する。

```php:User.php
 protected $fillable = [
 	// 恐らく必須ではないが、name_kanaとuser_type（権限）のカラムをfillableに追加する。
     'name', 'name_kana', 'email', 'password', 'user_type',
 ];
```

```php:Space.php
 <?php
 
 namespace App;
 
 use Illuminate\Database\Eloquent\Model;
 
 class Space extends Model
 {
 	protected $table = 'spaces';
 
 	protected $guarded = array('id');
 
 	public $timestamps = true;
 
 	protected $fillable = [
 	'name', 'created_at', 'updated_at'
 	];
 
 	// Routineモデルを子に持つことを記述する
 	public function routines()
 	{
 	 return $this->hasMany('App\Routine');
 	}
 }
```

```php:Routine.php
 <?php
 
 namespace App;
 
 use Illuminate\Database\Eloquent\Model;
 
 class Routine extends Model
 {
 	protected $table = 'routines';
 
 	protected $guarded = array('id');
 
 	public $timestamps = true;
 
 	protected $fillable = [
 	'name', 'point', 'interval', 'created_at', 'updated_at'
 	];
 
 	// Spaceモデルを親に持つことを記述する
 	public function space()
 	{
 	return $this->belongsTo('App\Space');
 	}
 
 	// Recordモデルを子に持つことを記述する
 	public function records()
 	{
 	 return $this->hasMany('App\Record');
 	}
 }
```

```php:Record.php
 <?php
 
 namespace App;
 
 use Illuminate\Database\Eloquent\Model;
 
 class Record extends Model
 {
 	protected $table = 'records';
 
 	protected $guarded = array('id');
 
 	public $timestamps = true;
 
 	protected $fillable = [
 	'point', 'created_at', 'updated_at'
 	];
 
 	// Routineモデルを親に持つことを明記する
 	public function routine()
 	{
 	return $this->belongsTo('App\Routine');
 	}
 
 	// Userモデルを親に持つことを明記する
 	public function user()
 	{
 	return $this->belongsTo('App\User');
 	}
 }
```

# Seederファイルを作り、リレーションの親にあたるSpaceデータ作成の準備をする
下記コマンドを作成して各モデルに対応したSeederファイルを作成する。
`$ php artisan make:seeder UsersTableSeeder`
`$ php artisan make:seeder SpacesTableSeeder`
※Routinesの初期データはバッチ処理で作成するため、Seederファイルは作成しない

DatabaseSeeder.phpで使用するSeederファイルを指定する

```php:database/seeds/DatabaseSeeder.php
  $this->call([
     UsersTableSeeder::class,
     SpacesTableSeeder::class,
  ]);
```

UsersTableSeeder.phpには下記の通り弊社社員の情報を入力する

```php:database/seeds/UsersTableSeeder.php
 <?php
 
 use Illuminate\Database\Seeder;
 // created_at(作成時刻), updated_at(更新時刻)を登録するためのDateTimeクラス
 use Carbon\Carbon;
 
 class UsersTableSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
     	// usersテーブルに登録するレコードを配列で定義する。何人登録するかは任意。弊社は15名前後なので全員ここに記述した。
     	DB::table('users')->insert([
     		[
     			'name'           => '若林正恭',
     			'name_kana'      => 'ワカバヤシマサヤス',
     			'email'          => 'wakabayashi@example.com',
     			'password'       => Hash::make('password'),
     			'user_type'      => 2,
     			'remember_token' => str_random(10),
     		],
     		[
     			'name'           => '春日俊彰',
     			'name_kana'      => 'カスガトシアキ',
     			'email'          => 'kasuga@example.com',
     			'password'       => Hash::make('password'),
     			'user_type'      => 2,
     			'remember_token' => str_random(10),
     		],
     	]);
     }
 }
```

SpacesTableSeeder.phpには下記の通りの、弊社オフィスのエリアを登録する

```php:database/seeds/SpacesTableSeeder.php
 <?php
 
 use Illuminate\Database\Seeder;
 
 class SpacesTableSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      *
      * @return void
      */
     public function run()
     {
     	// UsersTableSeeder.phpと考え方はほぼ同様
     	DB::table('spaces')->insert([
     		['name' => '全体共通'],
 			['name' => 'ベランダ'],
 			['name' => 'カフェスペース'],
 			['name' => '業務スペース'],
 			['name' => '会議室'],
 			['name' => 'トイレ'],
 			['name' => '給湯室'],
 			['name' => '受付裏スペース'],
 			['name' => '受付'],
     	]);
     }
 }
```

一通り定義が完了したので--seedオプションを指定して実行する。
`$ php artisan migrate:refresh --seed`
コマンド実行後にデータを確認する。
### Userデータ
2名のUserデータが登録されていることが確認できた
![スクリーンショット 2018-07-13 10.13.58.png](https://qiita-image-store.s3.amazonaws.com/0/210985/72950cd1-cba2-7e5d-30f6-d6e30d52b459.png)

### Spaceデータ
9つのSpaceデータが登録されていることが確認できた。次はバッチ処理でRoutineデータを作成する。
![スクリーンショット 2018-07-13 10.04.34.png](https://qiita-image-store.s3.amazonaws.com/0/210985/206439a2-2116-5dcc-5ec4-7053b1962b50.png)


# ER図を作成してリレーションが想定どおりになっているか確認する
[Laravelでリレーションが正しくモデルファイルに記述できているか、なる早でビジュアルで確かめる方法](https://qiita.com/hrtk_ymzk/items/d75af56ae026eef834a0)に記載があるが、この時点で想定通りのリレーションが構築できているか確認する記事を書いた。上記のER図もこのパッケーで作成した。

# Spaceと親子関係にあるRoutineデータ登録するバッチを作成する
大きく分けて下記の手順で進めていく。
・バッチスクリプトの詳細を記述するphpファイルを作る
・バッチスクリプトを編集する
・バッチスクリプトを登録する
・登録したバッチスクリプトを実行する

### バッチスクリプトの詳細を記述するphpファイルを作る
Laravelではバッチのプログラムのひな型をphp artisan make:commandコマンドで作成する。今回は、CreateRoutinesという名前のバッチを作成する。
`$ php artisan make:command CreateRoutines`
コマンドの実行に成功すると、バッチスクリプトのひな型ファイル
app\Console\Commands\CreateRoutines.phpが作成される。

### バッチスクリプトを編集する
ファイル内のhandle()メソッドに詳細の処理を記述する。今回はSpaceとリレーションを持つRoutineの初期データを記述する。

```php:app\Console\Commands\CreateRoutines.php
 <?php
 
 namespace App\Console\Commands;
 
 use Illuminate\Console\Command;
 // DBファサードを追記してSQLクエリを書けるようにする
 use Illuminate\Support\Facades\DB;
 // 日付/時刻処理をするためにCarbonを追記する
 use Carbon\Carbon;
 
 class CreateRoutines extends Command
 {
     /**
      * The name and signature of the console command.
      *
      * @var string
      */
     // protected $signature = 'command:name';を下記に修正。コマンド名を「createroutines」で実行できるようにする
     protected $signature = 'command:createroutines';
 
     /**
      * The console command description.
      *
      * @var string
      */
     // protected $description = 'Command description';を下記に修正。コマンドの説明を加える。
     protected $description = 'Create Default Routines';
 
     /**
      * Create a new command instance.
      *
      * @return void
      */
     public function __construct()
     {
         parent::__construct();
     }
 
     /**
      * Execute the console command.
      *
      * @return mixed
      */
     public function handle()
     {
         // Carbonを使ってcreated_atとupdated_atにinsert処理時の時間を入力する
         $now = Carbon::now();
         // 複数データを登録するため$dataを配列で定義する
         $data = [
             // 全体共通:1
             // 外部キー「space_id」に各routineデータが所属するspaceのidを記述する
             ['name' => '音楽をつける', 'point' => 1, 'interval' => 0, 'space_id' => 1, 'created_at' => $now, 'updated_at' => $now],
             ['name' => '蛍光灯　補充依頼', 'point' => 1, 'interval' => 0, 'space_id' => 1, 'created_at' => $now, 'updated_at' => $now],
             // ベランダ:2
             ['name' => 'ベランダの机と椅子の掃除', 'point' => 2, 'interval' => 30, 'space_id' => 2, 'created_at' => $now, 'updated_at' => $now],
             ['name' => 'ベランダの吸殻処理', 'point' => 2, 'interval' => 0, 'space_id' => 2, 'created_at' => $now, 'updated_at' => $now],
             // カフェスペース:3
             ['name' => 'テーブル(2台)の除菌拭き', 'point' => 2, 'interval' => 0, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
             ['name' => 'イスの水拭き', 'point' => 2, 'interval' => 0, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
             ['name' => 'コーヒーを淹れる', 'point' => 1, 'interval' => 0, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
             ['name' => 'コーヒーメーカー　ポットの洗浄', 'point' => 2, 'interval' => 1, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
             ['name' => 'ミネラルウォーター　開梱格納', 'point' => 3, 'interval' => 0, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
             ['name' => '冷凍庫の霜取り', 'point' => 4, 'interval' => 0, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
             ['name' => '砂糖の補充', 'point' => 2, 'interval' => 0, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
             ['name' => 'コーヒーフレッシュの補充', 'point' => 2, 'interval' => 0, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
             ['name' => 'ティッシュペーパーの補充', 'point' => 2, 'interval' => 0, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
             ['name' => 'コーヒーの補充依頼', 'point' => 1, 'interval' => 0, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
             ['name' => '飲料水の補充依頼', 'point' => 1, 'interval' => 0, 'space_id' => 3, 'created_at' => $now, 'updated_at' => $now],
         ];
 
 		// DBに保存するために上部でDBファサードを登録する
         DB::table('routines')->insert($data);
     }
 }
```

### バッチスクリプトを登録する
上記で編集したバッチ処理を実際に使えるように登録する必要がある。対象はapp/Console/Kernel.phpファイルである。$commandsという配列があるので、ここにクラス名を追加する。

```php:app/Console/Kernel.php
 protected $commands = [
     Commands\CreateRoutines::class,
 ];
```

バッチを登録したら、`$ php artisan list command`コマンドで、登録内容が正しく認識されたかを確認する。
### 登録したバッチスクリプトを実行する
下記のコマンドを実行する
`$ php artisan command:createroutines`
先ほどapp\Console\Commands\CreateRoutines.phpファイル内で「protected $signature = 'command:createroutines';」と登録した部分がcommand名となる。実行してエラーが発生しなければデータベースに初期値が登録されているか確認する。
![スクリーンショット 2018-07-13 10.01.57.png](https://qiita-image-store.s3.amazonaws.com/0/210985/f1824207-edad-7712-03cf-51490d744e23.png)

外部キーであるspace_idも含めてデータが作成されていることが確認できる。
これで初期値として揃っていてほしいデータを準備することができた。

### 参考URL
[公式のartisanページ](https://readouble.com/laravel/5.5/ja/artisan.html)→artisanコマンドの作成の参考
[公式のSeedingページ](https://readouble.com/laravel/5.5/ja/seeding.html)→シーディング利用の際の大まかな流れの参考
[複数レコードをDBに一気に保存する方法](http://kengotakimoto.com/post-2562/)→レコードを追加する際にどの方法を用いればいいかの参考
[公式のクエリビルダページ](https://readouble.com/laravel/5.5/ja/queries.html)→DBファサード利用の参考
[バッチ処理について一番参考になったページ](https://akamist.com/blog/archives/1010)→バッチ処理について非常に参考になったページ。この記事のようにステップバイステップで進めると初学者に優しい。
[Laravelでリレーションが正しくモデルファイルに記述できているか、なる早でビジュアルで確かめる方法](https://qiita.com/hrtk_ymzk/items/d75af56ae026eef834a0)→モデルファイルとマイグレーションファイルの定義を読み取ってER図として画像出力してくれるLaravelパッケージの紹介