はじめに

Laravel で CSV ファイルをもとに DB seed する方法を整理した。

TL;DR

  • SplFileObject で読み込んで foreach で 1行ずつ処理
    • 1行目はヘッダ行 = DB カラム名
  • ヘッダ行とデータ行を array_combine で合成
  • 合成した配列を使ってレコードの 検索/作成/更新 を行えば OK
  • Bulk insert/update には未対応なので、大量データの処理には向かないかも

目次

  1. はじめに
  2. TL;DR
  3. 環境・条件
  4. 詳細
    1. 前提条件
    2. 完成形
    3. ざっくり解説
      1. CSV の読み込み
      2. DB 用の配列作成
      3. レコードの 作成/更新
  5. まとめ
  6. その他・メモ
  7. 参考文献

環境・条件

1
2
3
4
5
6
7
8
9
10
11
12
$ grep -i pretty /etc/os-release
PRETTY_NAME="Ubuntu 16.04.3 LTS"

$ php -v
PHP 7.2.22-1+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Sep 2 2019 12:54:12) ( NTS )

$ composer -V
Composer version 1.9.0 2019-08-02 20:55:32

$ composer info laravel/framework
name : laravel/framework
versions : * v5.7.28

詳細

前提条件

CSV ファイル 1行目の内容を、対象テーブルのカラム名と一致させておくこと。

完成形

database/seeds/csvs/users.csv に以下のような CSV ファイルを配置。
(配置場所は適当なので、各自で適宜変更)

1
2
3
4
name,age,hobby
taro,10,tennis
ken,12,game
yuki,11,swimming

database/seeds/UsersTableSeeder.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Arr;

class UsersTableSeeder extends Seeder
{
public function run()
{
$file = new \SplFileObject(database_path('seeds/csvs/users.csv'));
$file->setFlags(
\SplFileObject::READ_CSV |
\SplFileObject::READ_AHEAD |
\SplFileObject::SKIP_EMPTY |
\SplFileObject::DROP_NEW_LINE
);

foreach ($file as $i => $line) {
if ($i === 0) {
$headers = $line;
continue;
}

// ['name' => 'taro'] のようにヘッダ行とデータ行を結合
$values = array_combine($headers, $line);

// 'name' で検索し、レコードが無ければ作成、有れば更新
User::updateOrCreate(
['name' => $values['name']],
Arr::except($values, ['name'])
);
}
}
}

ざっくり解説

CSV の読み込み

1
2
3
4
5
6
7
8
9
10
11
$file = new \SplFileObject(database_path('seeds/csvs/users.csv'));
$file->setFlags(
\SplFileObject::READ_CSV |
\SplFileObject::READ_AHEAD |
\SplFileObject::SKIP_EMPTY |
\SplFileObject::DROP_NEW_LINE
);

foreach ($file as $i => $line) {
// ...
}

CSV の読み込み処理は Laravelで大容量CSVファイルをSplFileObjectクラスで確実に処理する を利用しているので、そちらを見る方が良い。

foreach で 1行ずつ処理している。

DB 用の配列作成

1
2
3
4
5
6
7
8
9
foreach ($file as $i => $line) {
if ($i === 0) {
$headers = $line;
continue;
}

// ['name' => 'taro'] のようにヘッダ行とデータ行を結合
$values = array_combine($headers, $line);
}

1行目はヘッダ行として抽出し、array_combine を使って、ヘッダとデータを結合する。

2行目の処理時の $values は下記のようになる

1
2
3
4
5
[
'name' => 'taro',
'age' => '10',
'hobby' => 'tennis',
]

レコードの 作成/更新

1
2
3
4
5
// 'name' で検索し、レコードが無ければ作成、有れば更新
User::updateOrCreate(
['name' => $values['name']],
Arr::except($values, ['name'])
);

updateOrCreate を使って、検索/作成/更新 を実施。

第一引数(Array)が検索条件、第二引数(Array)が 作成/更新 に使うデータ。(たぶんやらなくても良い気がするが) Arr::except() で、検索に使った条件は除外している。

検索条件が複数ある場合は Arr::only() を使って、下記のようにするのが楽。

1
2
3
4
5
// 'name' AND 'age' で検索し、レコードが無ければ作成、有れば更新
User::updateOrCreate(
Arr::only($values, ['name', 'age']),
Arr::except($values, ['name'])
);

この部分はプロジェクトなどで条件違うはずなので、create のみ実施にする、など適宜変更。

まとめ

  • SplFileObject で読み込んで foreach で 1行ずつ処理
    • 1行目はヘッダ行 = DB カラム名
  • ヘッダ行とデータ行を array_combine で合成
  • 合成した配列を使ってレコードの 検索/作成/更新 を行えば OK
  • Bulk insert/update には未対応なので、大量データの処理には向かないかも

その他・メモ

最初は laravel-csv-seederを使ってseedデータを登録する(そしてcreate or updateしたい) - Qiita を参考に、Flynsarmy/laravel-csv-seeder を使おうかと考えていたが、最終コミットが 2017/10/31 なので自前で頑張る方式に変更した。

参考文献

関連記事