Laravelでcsvファイルを簡単インポート

## はじめに
選択したCSVファイルがインポートされるまでの流れを簡単に作ってみたいと思い、必要最低限で実装。

## テーブル定義
| カラム | 型 | CSVファイル読み込み対象 |
| ---- | ---- | ---- |
| id |  bigint(20)  |  |
| name | varchar(255)   |○ |
| age | varchar(255)   | ○ |
| address | varchar(255)   | ○ |
| created_at | timestamp   |  |
| rpdated_at | timestamp   |  |


### 今回使うcsvファイル
![image](https://firebasestorage.googleapis.com/v0/b/ciita-c6ea6.appspot.com/o/user%2Fimages%2Fcvza5ChG1vGAzClo?alt=media&token=e8fe9087-a205-40c8-9693-ff6a09c96c6b)

## 実際の動き

### インポート画面
![image](https://firebasestorage.googleapis.com/v0/b/ciita-c6ea6.appspot.com/o/user%2Fimages%2F1SSgRZMj6IcZ7FSR?alt=media&token=75dfc1db-4450-451e-968a-baa435116c04)


### 「CSVファイル選択」を押下し、test.csvを開く。
![image](https://firebasestorage.googleapis.com/v0/b/ciita-c6ea6.appspot.com/o/user%2Fimages%2FNBOejr4GN5TELDkG?alt=media&token=100b0d4f-de54-4136-98ad-5fe143900969)

### 「インポート」ボタンを押下する。
※選択したファイル名を表示させたい場合はjqueryなどで実装。今回は省略

![image](https://firebasestorage.googleapis.com/v0/b/ciita-c6ea6.appspot.com/o/user%2Fimages%2Fr7k7cgR0o4XJfIUB?alt=media&token=b25fc4cc-0c88-4354-a4b9-e04f283b755a)

### csvファイルの内容がインポートされる。
![image](https://firebasestorage.googleapis.com/v0/b/ciita-c6ea6.appspot.com/o/user%2Fimages%2FkU2VbMFoXcdO9UQf?alt=media&token=62c70930-5571-4a27-b4e4-d1fbd97df1c8)

## 実際の処理
```html
<!DOCTYPE html>
<html>
    <head>
        <title>Laravel CSVインポート</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    </head>
    <body>
        <div class="container mt-5">
            <form action="{{ route('csvImport') }}" method="POST" enctype="multipart/form-data">
                @csrf
                <div class="form-group mb-4" style="max-width: 500px; margin: 0 auto;">
                    <div class="custom-file text-left">
                        <input type="file" name="csvFile" class="" id="csvFile">
                        <label class="custom-file-label" for="customFile">CSVファイル選択</label>
                    </div>
                </div>
                <button class="btn btn-primary btn-lg">インポート</button>
            </form>
        </div>
    </body>
</html>

```

ファイルを送る際は、enctype="multipart/form-data"と、<input type="file" が必要

idにcsvFileを指定する。コントローラーで使用するhasFile()では、指定したidを参照するので、ファイル有無のバリデーションに活用できる。

## コントローラーの処理
```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;


class UserController extends Controller
{
    public function import(){
        return view('csvImport');
    }
    
    public function csvImport(Request $request)
    {
        if ($request->hasFile('csvFile')) {
            // リクエストからファイルを取得
            $file = $request->file('csvFile');
            $path = $file->getRealPath();
            // ファイルを開く
            $fp = fopen($path, 'r');
            // ヘッダー行をスキップ
            fgetcsv($fp);
            // 1行ずつ読み込む
            while (($csvData = fgetcsv($fp)) !== FALSE) {
                $this->InsertCsvData($csvData);
            }
            // ファイルを閉じる
            fclose($fp);
        } else {
            throw new Exception('CSVファイルの取得に失敗しました。');
        }
    }

    public function InsertCsvData($csvData)
    {
        // csvファイル情報をインサートする
        $user = new User;
        $user->name = $csvData[0];
        $user->age = $csvData[1];
        $user->address = $csvData[2];
        $user->save();
    }
    
}

```

まずはrequestにファイルが送られてきているか判定。
requestのファイルを参照する。
fopenのmode'r'、読み込みモードでCSVをオープンにする。
fopenのmodeについては下記を参照。（よく使いそうなもの。細かく知りたい場合はPHPマニュアルを確認）
| mode | 説明 | ファイルポインタ |
| ---- | ---- | ---- |
| 'r' |  読み込みのみでオープ。  | 先頭に置く |
| 'r+' | 読み込み/書き出し用でオープン   | 先頭に置く |
| 'w' | 書き出しのみでオープン   | 先頭に置く |
| 'a' | 書き出しのみでオープン   | 終端に置く |

fgetcsv関数で、csvファイルを一行ずつ配列として取得。

csvファイルを配列として取得できたので、あとはInsertCsvDataメソッドにてインサート。

業務ではバリデーションなどで複雑だと思いますが、インポートだけに着目するとシンプルだと思いました。