PHP

【Laravel Excel】CSVファイルをデータベースに登録する方法

この記事ではLaravel8を使用したアプリケーションで、CSVデータをデータベースに登録する方法をまとめています。

CSVファイルのimport・export機能のPHPライブラリは

  • goodby/csv
  • FastExcel
  • Laravel Excel

など色々ありましたが、今回は記事やドキュメントが豊富だったLaravel Excelで実装してみました。

「Laravel Excel」 公式サイト

動作環境

  • Laravel 8.40.0
  • PHP 8.0.3
  • Laravel Excel 3.1
  • macOS 12.0.1

「Laravel Excel」をインストール

Composerを使ってパッケージをインストールします。

$composer require maatwebsite/excel

インストール後はサービスプロパイダーとファサードを登録します。configフォルダのapp.phpを開いて以下2つを追加します。

理由としては、Laravelはサービスコンテナに登録されているサービスを利用してアプリケーションの開発を行ないます。そのため、インストールしたパッケージをLaravelアプリケーション上で利用できるようにするために、サービスプロバイダーに登録を行います。また、ファサードを登録することで、クラスをインスタンス化しなくてもメソッドを実行できるようになります。

'providers' => [
    Maatwebsite\Excel\ExcelServiceProvider::class,
],


'aliases' => [
    'Excel' => Maatwebsite\Excel\Facades\Excel::class,
],

以下を実行

$php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"

これにより、config/excel.phpという名前の新しい構成ファイルが作成されます。

今回は特にこちらのファイルを編集しませんが、設定を変更する際はこのファイルを編集します。

Importファイルの作成

export→データをDBから取得する
import→データをDBに登録する

今回は例として旅行サイトの口コミを登録したCSVファイルをデータベースに登録する作業を行います。

まずモデルを用意して、下記のように参照先をセットします。この時参照先をセットしていないと、CSVファイルをアップロードした際にエラーが起きてデータベースに登録ができませんので忘れずに行いましょう。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Kuchikomi extends Model
{
    use HasFactory;
    protected $table = 'kuchikomis';

    protected $fillable = [
        'score',
        'review',
        'reviewed_date',
    ];
}

すでにモデルは作成した状態から、下記を実行しImportクラスを作成していきます

$ php artisan make:import KuchikomisImport --model=Kuchikomi
Import created successfully.

appフォルダ下にImports/KuchikomisImport.phpが作成されます。

<?php

namespace App\Imports;

use App\Models\Kuchikomi;
use Maatwebsite\Excel\Concerns\ToModel;

class KuchikomisImport implements ToModel
{
    /**
    * @param array $row
    *
    * @return \Illuminate\Database\Eloquent\Model|null
    */
    public function model(array $row)
    {
        return new Kuchikomi([
            //
        ]);
    }
}

ここには下記のように、アップロードするCSVファイルの列がインポートするテーブルのどの列に対応するのかを記述していきます。

<?php

namespace App\Imports;

use App\Models\Kuchikomi;
use Maatwebsite\Excel\Concerns\ToModel;

class KuchikomisImport implements ToModel
{
    /**
    * @param array $row
    *
    * @return \Illuminate\Database\Eloquent\Model|null
    */
    public function model(array $row)
    {
        return new Kuchikomi([
            'score' => $row[0],
            'review' => $row[1],
            'reviewed_date' => $row[2],
        ]);
    }
}

こんな感じで、追記する。

CSVファイルの1行目のカラムを無視したい場合は、2行目から読み込むように以下のように追記する。

<?php

namespace App\Imports;

use App\Models\Kuchikomi;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithStartRow;

class KuchikomisImport implements ToModel,WithStartRow
{

   //省略

    /**
     * @return int
     */
    public function startRow(): int
    {
        return 2;
    }
}

CSVファイルをアップロードしてデータベースへImportする

ルーティングを設定して、コントローラーに下記のように記載する。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Kuchikomi;
use Maatwebsite\Excel\Facades\Excel;
use App\Imports\KuchikomisImport;

class KuchikomisController extends Controller
{
    public function index()
    {
        $kuchikomis=Kuchikomi::all();

        return view('kuchikomis/index',['kuchikomis' => $kuchikomis,]);
    }

    public function store(Request $request){

		$file = $request->file('kuchikomi');

		Excel::import(new KuchikomisImport, $file);
    return redirect()->route('kuchikomi.index')->with('flash_message', '登録が完了しました');
    }
}

ビューはこんな感じです。

<form action="{{ route('kuchikomi.store') }}" method="post" enctype="multipart/form-data">
    <input type="hidden" name="_token" value="{{ csrf_token() }}">
    <div class="grid grid-cols-1 md:grid-cols-2 gap-5 md:gap-8">
        <div class="grid grid-cols-1">
            <label class="uppercase md:text-sm text-xs text-gray-500 text-light font-semibold mb-1">CSVファイルを選択(必須)</label>
            <input type='file' name='kuchikomi' />
                @error('kuchikomi')
                    <div class="text-red-500 font-bold">{{ $message }}</div>
                @enderror
        </div>
    </div>
    <button type="submit" class='bg-gray-500 hover:bg-gray-700 text-white font-bold py-1 px-3 mt-1 rounded'>登録</button>
</form>

これで実装は完成です。

実際にCSVファイルをアップロードしてみてデータベースにデータが登録されていればOKです。


このようにでCSVファイルがデータベースに登録されていれば成功です。

【応用】アップロードしたCSVデータをLaravel上で編集してからデータベースにImportする

やりたいこと。

CSVファイルの1列目と2列目に値を追加してから、CSVデータをインポートしたい。

例えば、旅行サイトの口コミを集めたCSVファイルの1列目にホテル名、2列目に媒体名を一括で追加してからインポートしたい。

登録画面としては下記のような画面で、セレクトボックスで選択したものがそのままCSVデータの1列目と2列目に一括で登録されてからデータをインポートしたいようなとき。

CSVファイルの列1と列2に対応させるため、KuchikomisImport.phpを修正する。

<?php

namespace App\Imports;

use App\Models\Kuchikomi;
use Maatwebsite\Excel\Concerns\ToModel;

class KuchikomisImport implements ToModel
{
    /**
    * @param array $row
    *
    * @return \Illuminate\Database\Eloquent\Model|null
    */
    public function model(array $row)
    {
        return new Kuchikomi([
            'client_id' => $row[0],
            'OTA' => $row[1],
            'score' => $row[2],
            'review' => $row[3],
            'reviewed_date' => $row[4],
        ]);
    }
}

app/Models/Kuchikomi.phpもデータベースの参照先を追加

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Kuchikomi extends Model
{
    use HasFactory;
    protected $table = 'kuchikomis';

    protected $fillable = [
        'client_id',
        'OTA',
        'score',
        'review',
        'reviewed_date',
    ];
}

アップロード後にCSVデータを編集するためコントローラーは下記のように修正する。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Kuchikomi;
use Maatwebsite\Excel\Facades\Excel;
use App\Imports\KuchikomisImport;
use Illuminate\Support\Facades\DB;

class KuchikomisController extends Controller
{
    public function index()
    {
        $kuchikomis=Kuchikomi::all();
        return view('kuchikomis/index',['kuchikomis' => $kuchikomis,]);
    }

    public function store(Request $request){
        
        $validated = $request->validate([
            'client_id' => 'required',
            'OTA' => 'required',
            'kuchikomi' => 'required',
        ]);

     // csvデータかどうかを判定する
        if($request->file('kuchikomi')->getClientMimeType() ==='text/csv'){
            $file = $request->file('kuchikomi');

            // ファイル読み込み
            $input = file_get_contents($file);
    
            // 一時ファイルに書き込む
            $tmp = tmpfile();
            fwrite($tmp, $input);

            // 一時ファイルから読み込み
            $meta = stream_get_meta_data($tmp);
            $csv = new \SplFileObject($meta['uri']);
    
            // CSVとしてファイルを読み込ませる
            $csv->setFlags(
                \SplFileObject::READ_CSV |
                \SplFileObject::READ_AHEAD |
                \SplFileObject::SKIP_EMPTY |
                \SplFileObject::DROP_NEW_LINE
            );
    
            // 書き出し用のCSVファイル
            $output = new \SplFileObject($file, 'w');
    
            foreach ($csv as $i => $row)
            {
                $row[0]=$request->client_id;
                $row[1]=$request->OTA;
                // 書き出し
                $output->fputcsv($row);
            }
            // ファイルを閉じる
            $csv    = null;
            $output = null;
    
            Excel::import(new KuchikomisImport, $file);
            return redirect()->route('kuchikomi.index')->with('flash_message', '登録が完了しました');
        } else{
            return redirect()->route('kuchikomi.index')->with('flash_message_file', 'ファイルの拡張子を確認してください。');
        }
    }
}

解説

->getClientMimeType()でファイルの拡張子を確認し、csvでなければアラートを出すようにする。アラートはJavascriptライブラリ「Toastr」を使用しています。

if($request->file('kuchikomi')->getClientMimeType() ==='text/csv'){

} else{

}

CSVファイルはSplFileObjectクラスで読み込んでいます。

SplFileObjectクラスはPHP固有の、ファイルのためのオブジェクト指向インターフェイスなので、Laravelでインスタンス化を行う場合は先頭に「 \ 」をつける必要があります。

「 \ 」をつけないと通常のLaravel上のクラスファイルと同じ扱いをされるので、「Error Class "App\Http\Controllers\SplFileObject" not found」とエラーが発生します。

SplFileObjectクラスでCSVを読み込む際にはオプションを追加する。
PHPマニュアル SplFileObjectクラス

オプションを追加しないと、CSVファイルの空白行なども読み取られてしまいファイルを扱う際に不都合が生じてしまう場合があります。

実際にSKIP_EMPTYなどのオプションを追加せずにCSVファイルをアップロードした際に、CSVファイルの最終行が空白だったため空白のレコードがデータベースに保存されてしまうことがありました。

そのため、CSVを扱う際にはこれらのオプションも忘れずに設定しておきたいです。

SplFileObject::DROP_NEW_LINE 行末の改行を読み飛ばします。

SplFileObject::READ_AHEAD 先読み/巻き戻しで読み出します。

SplFileObject::SKIP_EMPTY ファイルの空行を読み飛ばします。期待通りに動作させるには、READ_AHEAD フラグを有効にしないといけません。

SplFileObject::READ_CSV CSV列として行を読み込みます。

PHPマニュアル SplFileObject クラス
       file = $request->file('kuchikomi');

            // ファイル読み込み
            $input = file_get_contents($file);
    
            // 一時ファイルに書き込む
            $tmp = tmpfile();
            fwrite($tmp, $input);

            // 一時ファイルから読み込み
            $meta = stream_get_meta_data($tmp);
            $csv = new \SplFileObject($meta['uri']);
    
            // CSVとしてファイルを読み込ませる
            $csv->setFlags(
                \SplFileObject::READ_CSV |
                \SplFileObject::READ_AHEAD |
                \SplFileObject::SKIP_EMPTY |
                \SplFileObject::DROP_NEW_LINE
            );
    
            // 書き出し用のCSVファイル
            $output = new \SplFileObject($file, 'w');
    
            foreach ($csv as $i => $row)
            {
                $row[0]=$request->client_id;
                $row[1]=$request->OTA;
                // 行を書き出し
                $output->fputcsv($row);
            }
            // ファイルを閉じる
            $csv    = null;
            $output = null;
    
            Excel::import(new KuchikomisImport, $file);

1列目と2列目が空のCSVを用意して、セレクトボックスで選択したものが下記のようにデータベースに反映されたらOK。

以上です。

  • この記事を書いた人

コウダイ

都内のWeb系自社開発企業に勤務するエンジニア|33歳1児のパパ|ブログ歴4年→月間6,000PV|新卒で手取り18万のホテルマン6年→プログラミングを900時間勉強→100社以上応募しアラサー未経験から7ヶ月でフルリモートのWEB系自社開発エンジニアに転職し年収100万円UP|【人生を自由に、ノンストレスで生きる】をテーマに、30歳で文系・異業種未経験からITエンジニアに転職したノウハウの他、プログラミングやブログで稼ぐ方法など、「時間や場所に縛られずに稼ぐ」方法を発信しています。

-PHP