【CakePHP】CSVファイルからシードする方法

CakePHP4.x系。シードするときにデータをCSVファイルから読み込みたいときのやり方。

csvファイルを配置する

今回は Seeds 直下にディレクトリ csv を作って、ファイルを config/Migrations/Seeds/csv/users.csv として配置する。配置する場所はお好みで。

ubuntuなどで動かすときにはcsvファイルの文字コードは最初からUTF-8にしておけば良いが、windowsの場合は普通にSJISで保存しておいて、seederの側で文字コードを変換する。こうしないと、csvのカラムが崩れるという謎現象に苦しむことになる。

シードファイルの中身

例としてテーブル users にカラム user, email, familyname, firstname, created, modified を作成した場合を紹介する。シーダーを用意したら、run() の中身を書き換える。

public function run()
{
    $data = [];

    $file = new SplFileObject(CONFIG . DS . 'Seeds' . DS . 'csv' . DS . 'users.csv');
    $file->setFlags(SplFileObject::READ_CSV);
    foreach ($file as $index => $value) {
        if ($index == 0 || $file->eof()) {
            continue;
        }
        $str = mb_convert_encoding($value, "UTF-8", "SJIS");
        $data[] = [
            'user' => $str[0],
            'email' => $str[1],
            'familyname' => $str[2],
            'firstname' => $str[3],
            'created' => date("Y-m-d H:i:s"),
            'modified' => date("Y-m-d H:i:s"),
        ];
    }

    $table = $this->table('users');
    $table->insert($data)->save();
}

サイズが大きな csv を扱うことを想定して SplFileObject を使うと良い。定数 CONFIG にはディレクトリ config までのパスが入っている。定数 DS はパスの区切りで、環境によってパスの区切り文字が異なる場合でも対応する。windows なら / になる。

if ($index == 0 || $file->eof()) {
    continue;
}

$index == 0 によって先頭行は飛ばすようにしている。csv が見出し無しで 1 行目からデータが入っている場合はこの条件は外すと良い。

また、csvの最終行にはデータではなくファイルの終端を示す EOF が入っている。$file->eof() はポインタ位置が終端なら true、終端でなければ false を返すので、判定によって最終行も飛ばすようにする。

$str = mb_convert_encoding($value, "UTF-8", "SJIS");

文字コードを SJIS から UTF-8 に変換する。

$data[] = [
    'user' => $str[0],
    'email' => $str[1],
    'familyname' => $str[2],
    'firstname' => $str[3],
    'created' => date("Y-m-d H:i:s"),
    'modified' => date("Y-m-d H:i:s"),
];

$str はただの配列なので、ここで連想配列として $data に追加する。laravel の場合だと created と modified は自動でデータが入るが、CakePHPの場合は自前で設定しておく必要がある。

ただし、今回の書き方だとcsvファイルが巨大な場合は結局巨大な連想配列が作られることになるで、save() のタイミングを考えないといけなくなる。