Laravelのseederでcsvのデータを挿入する方法

# はじめに

Laravelのseederで大量のcsvのデータを挿入する際に詰まったので対処方法について記述します。
csvのデータ量が10000行程度であれば以下のようなシンプルなseederでデータを挿入することが可能です。

# シンプルなSeederの作成
### Laravel Seederを作成する
```
php artisan make:seeder CsvSeeder
```
### Seederの実装
```php:CsvSeeder.pfp
<?php

namespace Database\Seeders;

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

class CsvSeeder extends Seeder
{
    public function run()
    {
        // CSVファイルのパス
        $filePath = database_path('seeders/data.csv');

        DB::transaction(function () use ($filePath) {
            // CSVファイルを開く
            if (($handle = fopen($filePath, 'r')) !== false) {
                $header = fgetcsv($handle); // ヘッダー行を取得

                while (($row = fgetcsv($handle)) !== false) {
                    $data = array_combine($header, $row);

                    // データベースに挿入
                    DB::table('users')->insert([
                        'name'  => $data['name'],
                        'email' => $data['email'],
                        'age'   => $data['age'],
                        'created_at' => now(),
                        'updated_at' => now(),
                    ]);
                }

                fclose($handle); // ファイルを閉じる
            }
        });
    }
}
```

### Seederを実行する
```
php artisan db:seed --class=CsvSeeder
```

# 大量データをSeederで挿入する方法
挿入するcsvデータが大量にある場合、メモリ不足でエラーになる場合があります。
対策としては以下が考えられます。
### PHPのメモリ制限を増やす
```php.ini
ini_set('memory_limit', '512M');
```
### バルククエリを使用し、データを分割処理をする
```php:CsvSeeder.pfp
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class CsvSeeder extends Seeder
{
    public function run()
    {
        // CSVファイルのパス
        $filePath = database_path('seeders/data.csv');

        // バッチサイズ（1回の挿入処理でまとめるレコード数）
        $batchSize = 1000;
        $batchData = [];

        // トランザクション処理
        try {
            if (($handle = fopen($filePath, 'r')) !== false) {
                $header = fgetcsv($handle); // ヘッダー行を取得

                while (($row = fgetcsv($handle)) !== false) {
                    // ヘッダーと行データを組み合わせる
                    $data = array_combine($header, $row);

                    $batchData[] = [
                        'name'       => $data['name'],
                        'email'      => $data['email'],
                        'age'        => (int)$data['age'],
                        'created_at' => now(),
                        'updated_at' => now(),
                    ];

                    // バッチサイズに達したら挿入
                    if (count($batchData) >= $batchSize) {
                        DB::transaction(function () use (&$batchData) {
                            DB::table('users')->insert($batchData);
                            $batchData = []; // 挿入後にバッチデータをリセット
                        });
                    }
                }

                // 残りのデータを挿入
                if (!empty($batchData)) {
                    DB::transaction(function () use (&$batchData) {
                        DB::table('users')->insert($batchData);
                    });
                }

                fclose($handle); // ファイルを閉じる
            }
        } catch (\Exception $e) {
            Log::error("Error occurred while processing CSV: " . $e->getMessage());
        }
    }
}
```

上記対応に加えてcsvデータのバリデーションやエラーハンドリングなどを行うことでスムーズな対応が可能になります。