とりあえず適当に本番データをローカル環境で入れることが出来るDB:seedを考えた

## 概要
以下のニーズに答えた

- 本番環境のデータをそのままローカル環境に流し込みたい
- DB構造が変わるたびにSeeder書き換えるのが嫌
- 適当に入ればいい(適当とは下記3の方)

> １ ある条件・目的・要求などに、うまくあてはまること。かなっていること。ふさわしいこと。また、そのさま。「工場の建設に適当な土地」「この仕事に適当する人材」
> ２ 程度などが、ほどよいこと。また、そのさま。「調味料を適当に加える」「一日の適当な仕事量」
> ３ やり方などが、いいかげんであること。また、そのさま。悪い意味で用いられる。「客を適当にあしらう」「適当な返事でごまかす」

<a href="https://kotobank.jp/word/%E9%81%A9%E5%BD%93-575436">適当(テキトウ)とは - コトバンク</a>


**〜 想定する流れ 〜**
1. データベースからcsvファイルでエクスポート
2. laravelの `storage/db_csv` に設置
3. `php artisan DB:seed` 実行

今回は1行目(カラム名が記述される)を含むcsvファイルをエクスポートしました。
## 環境
laravel 5.8
MySQL 5.7
## つかった技術
### CSVファイルを読み込む

#### `SplFileObject` クラスを利用して読み込む

```php
$file = new SplFileObject("storage/db_csv/{$tableName}.csv");
// オプションをつける
$file->setFlags(
	\SplFileObject::READ_CSV |
	\SplFileObject::READ_AHEAD |
	\SplFileObject::SKIP_EMPTY |
	\SplFileObject::DROP_NEW_LINE
);
```

#### オプションについて

```php
// CSV 列として行を読み込む
SplFileObject::READ_CSV

// 先読み/巻き戻しで読み出す
SplFileObject::READ_AHEAD

// ファイルの空行を読み飛す。READ_AHEADが有効であることが必須
SplFileObject::SKIP_EMPTY

// 行末の改行を読み飛ばす
SplFileObject::DROP_NEW_LINE
```

### DBの情報を取得する

#### テーブルの一覧を取得

```php
foreach (DB::select('SHOW TABLES') as $table) {
	$dbName = config('database.connections.mysql.database');
	$tableName = $table->{'Tables_in_' . $dbName};
}
```

#### カラム名とその型のリスト

```php
$columns = Schema::connection('mysql')->getColumnListing($tableName)
```

## 注意
- json型非対応
	- [これ](https://qiita.com/_mmmm_/items/c46fa4e6169878da554c#json%E5%9E%8B%E3%81%A7%E3%82%A8%E3%83%A9%E3%83%BC)を使って対応出来るとは思うのですが、とりあえず非対応です。そのうちがんばります。

## コード

```php
<?php

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;

class DatabaseSeeder extends Seeder
{

    /**
     * storage/db_csv に{テーブル名}.csvを設置する
     */
    public function run()
    {
        foreach (DB::select('SHOW TABLES') as $table) {
            $dbName = config('database.connections.mysql.database');
            $tableName = $table->{'Tables_in_' . $dbName};

            // migrationsは除外
            if ($tableName === 'migrations') continue;

            // 存在しなければ除外
            if (!File::exists("storage/db_csv/{$tableName}.csv")) continue;

            // もう何か入っていれば除外
            if (DB::table($tableName)->count() > 0) continue;

            $this->saveByCsv($tableName);
        }

    }

    /**
     * csvからdbへ保存
     * @param $tableName
     */
    private function saveByCsv($tableName)
    {
        $file = new SplFileObject("storage/db_csv/{$tableName}.csv");
        $file->setFlags(
            \SplFileObject::READ_CSV |
            \SplFileObject::READ_AHEAD |
            \SplFileObject::SKIP_EMPTY |
            \SplFileObject::DROP_NEW_LINE
        );

        // 存在するカラムを取得
        $columns = Schema::connection('mysql')->getColumnListing($tableName);

        $columnTypes = [];
        foreach ($columns as $column) {
            $columnTypes[$column] = Schema::connection('mysql')->getConnection()->getDoctrineColumn($tableName, $column)->toArray()['type'];
        }

        $records = [];
        foreach($file as $key => $line) {
            // 1行目はカラム名なので除外
            if ($key === 0) continue;

            $record = [];
            foreach ($columns as $columnKey => $column) {

                // 存在しない場合は適当に値を入れる
                if (empty($line[$columnKey]))
                    $line[$columnKey] = $this->columnTypeToDummyValues($columnTypes[$column]);

                // nullが文字列になっているので修正
                if ($line[$columnKey] === "NULL")
                    $line[$columnKey] = null;

                $record += [$column => $line[$columnKey]];
            }
            $records[] = $record;

        }

        DB::table($tableName)->insert($records);

    }

    /**
     * カラムのタイプ毎にダミーの値を返す
     *
     * @param $type
     * @return mixed|string
     */
    private function columnTypeToDummyValues($type)
    {
        $ColumnTypes = [
            'Doctrine\DBAL\Types\StringType' => 'ダミーテキスト',
            'Doctrine\DBAL\Types\IntegerType' => 1,
            'Doctrine\DBAL\Types\SmallIntType' => 10,
            'Doctrine\DBAL\Types\DateTimeType' => \Carbon\Carbon::now(),
            'Doctrine\DBAL\Types\TextType' => 'ダミーテキスト',
            'Doctrine\DBAL\Types\BooleanType' => true,
            'Doctrine\DBAL\Types\DecimalType' => 0.1,
        ];
        $text = '';
        foreach ($ColumnTypes as $ColumnType => $value) {
            if ($type instanceof $ColumnType) {
                $text = $value;
            }
        }
        return $text;
    }
}

```

## おわり
とりあえず適当に入ります。
エラーが怖くて空の時はダミーデータ挿入するようにしていますが、動いていないようです。