概要
以下のニーズに答えた
- 本番環境のデータをそのままローカル環境に流し込みたい
- DB構造が変わるたびにSeeder書き換えるのが嫌
- 適当に入ればいい(適当とは下記3の方)
1 ある条件・目的・要求などに、うまくあてはまること。かなっていること。ふさわしいこと。また、そのさま。「工場の建設に適当な土地」「この仕事に適当する人材」
2 程度などが、ほどよいこと。また、そのさま。「調味料を適当に加える」「一日の適当な仕事量」
3 やり方などが、いいかげんであること。また、そのさま。悪い意味で用いられる。「客を適当にあしらう」「適当な返事でごまかす」
〜 想定する流れ 〜
- データベースからcsvファイルでエクスポート
- laravelの
storage/db_csv
に設置 -
php artisan DB:seed
実行
今回は1行目(カラム名が記述される)を含むcsvファイルをエクスポートしました。
環境
laravel 5.8
MySQL 5.7
つかった技術
CSVファイルを読み込む
SplFileObject
クラスを利用して読み込む
$file = new SplFileObject("storage/db_csv/{$tableName}.csv");
// オプションをつける
$file->setFlags(
\SplFileObject::READ_CSV |
\SplFileObject::READ_AHEAD |
\SplFileObject::SKIP_EMPTY |
\SplFileObject::DROP_NEW_LINE
);
オプションについて
// CSV 列として行を読み込む
SplFileObject::READ_CSV
// 先読み/巻き戻しで読み出す
SplFileObject::READ_AHEAD
// ファイルの空行を読み飛す。READ_AHEADが有効であることが必須
SplFileObject::SKIP_EMPTY
// 行末の改行を読み飛ばす
SplFileObject::DROP_NEW_LINE
DBの情報を取得する
テーブルの一覧を取得
foreach (DB::select('SHOW TABLES') as $table) {
$dbName = config('database.connections.mysql.database');
$tableName = $table->{'Tables_in_' . $dbName};
}
カラム名とその型のリスト
$columns = Schema::connection('mysql')->getColumnListing($tableName)
注意
- json型非対応
- これを使って対応出来るとは思うのですが、とりあえず非対応です。そのうちがんばります。
コード
<?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;
}
}
おわり
とりあえず適当に入ります。
エラーが怖くて空の時はダミーデータ挿入するようにしていますが、動いていないようです。