開発室ブログ

Laravel PHP

CSVインポート機能をLaravelで書く

最近、開発室なのに技術ネタが少ない…ので、今回はLaravel(5.5)で。

CSVインポート時のバリデーション

CSVをインポートしてDBに投入する、というよくある処理ですが、意外と大変だったりしますよね。いやいや、Laravelだったら楽できるのかな? ということで、調べつつ実装してみました。

希望すること

Laravel標準 + 自作のカスタムバリデータ一式ぜんぶ使いたい

CSVはバリデーションが大変ですよね…あと、CSVにエラーあったら表示どうする? とか考えると、いろいろややこしい。カスタムバリデータもあるし。どうせなら、Laravelの仕組みを使ってチェックしたい!

すでに管理画面用に作成済みの rules とか attributes 一式を流用したい

管理画面のマスタメンテ的な機能、ありますよね。そこの FormRequest で利用しているバリデーション定義を、CSVのバリデーションにも流用したい!

どうにかできそう?

勉強します。

コントローラでも バリデーションできるんだー。逆に、FormRequest に書くのは難しそう…? withValidator(Validator $validator) からの $validator->after()でゴリゴリっとできないだろうか…ひとまず、コントローラに実装する感じでやってみる。ちょっと簡略化もする。

ってことは、作成済みの Rules や Attributes もひとまずコピペかな…。

方針

  • POSTのチェックはFormRequest。CSVの明細チェックはコントローラ内のバリデータ。

  • 細かい処理はコントローラから分けたい。

CSVフォーマットや文字コード等はそれぞれかと思うので、ここでは省略します。

Controller

ざっくりこんな雰囲気でしょうか。

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;

use Illuminate\Http\Request;
use App\Http\Requests\Admin\CsvImportFormRequest;

// CSV用クラス
use App\Services\FileImport\ImportCsvService;

// バリデータ
use Illuminate\Support\Facades\Validator;

class CsvImportController extends Controller
{
    protected $csv_service;

    // CSV配列
    protected $csv_array;

    // 戻り
    protected $return_top = '/admin/modori';

    /**
     * コンストラクタ
     */
    public function __construct(Request $request)
    {
        // CSVサービス
        $this->csv_service = new ImportCsvService();
    }


    /**
     * アップロードフォーム
     */
    public function form(Request $request)
    {
        return response(
            view('アップロードページのBladeテンプレート')
        );
    }


    /**
     * 実行
     */
    public function execute(CsvImportFormRequest $request)
    {

        // CSV読み込み→連想配列は別に記述
        $this->csv_array = $this->csv_service->getCsvArray($request);

        /**
         * こっからCSV中身のバリデーション
         */
        $error_list = [];
        $count = 1;

        foreach($this->csv_array as $row) {

            $validator = Validator::make(
                $row,
                $this->csv_service->validationRules(),
                $this->csv_service->validationMessages(),
                $this->csv_service->validationAttributes()
            );

            if ($validator->fails() === true) {
                $error_list[$count] = $validator->errors()->all();
            }

            $count++;
        }

        // エラーがあれば終わり
        if ( count($error_list) > 0 ) {
            return response(
                view('アップロードページのBladeテンプレート')
                    ->with('format_errors', $error_list)
            );
        }

        // データベースへ書き込み
        foreach($this->csv_array as $row) {
            /**
             * 省略(モデルにブン投げるなど…)
             */
        }

        return redirect('飛び先', 303);
    }

}

FormRequestを通過したらCSVを配列化し、Validator::make() でチェックします。 ダメならエラーを持ってフォーム表示に戻り、バリデーションが通れば書き込み処理等を行う。

FormRequest

ここではPOSTされたリクエスト自体のチェックを行います。

<?php
………

class CsvImportFormRequest extends FormRequest
{

………

    public function rules()
    {
        $rules = [
            // アップロードファイル必須、アップロード成功、容量制限
            'csv_file' => 'required|file|max:40960',
        ];

        return $rules;
    }

………

このようなチェックが行えます。 CSVの中身チェックはコントローラ内のバリデータで!

CSV処理用のクラス

CSV→連想配列への処理と、CSVバリデーション設定の格納に使います。

<?php
/**
 * CSVインポートクラス
 */
namespace App\Services\FileImport;

class ImportCsvService
{

    // パースしたCSVを入れる配列
    protected $csv_array = [];

    /**
     * CSV配列を返す
     */
    public function getCsvArray($request)
    {
        $this->uploadCsvToArray($request);
        return $this->csv_array;
    }

    /**
     * リクエストからCSVファイルをもらってパースして配列へ
     */
    private function uploadCsvToArray($request)
    {
        // リクエストより
        $file = $request->file('csv_file');

        // 読み込みます
        $csv = new \SplFileObject($file->getRealPath());
        $csv->setFlags(
            \SplFileObject::READ_CSV     |
            \SplFileObject::READ_AHEAD   |
            \SplFileObject::SKIP_EMPTY   |
            \SplFileObject::DROP_NEW_LINE
        );

        foreach($csv as $record) {
            // ループして $csv_array へ入れる
            // $csv_array[]['tel'] = '値' のような形へ
        }
    }

    /**
     * 以下、バリデーションの設定
     */
    public function validationRules()
    {
        return [
            'name'     => 'required|max:200',
            'address'  => 'max:500',
            'tel'      => 'max:20',
            ………
            …
        ];
    }

    public function validationMessages()
    {
        return [

        ];
    }

    public function validationAttributes()
    {
        return [
            'name'     => '名前',
            'address'  => '住所',
            'tel'      => '電話番号',
            ………
            …
        ];
    }

}

まとめておく。こっちでバリデーションしても良かったか…。

テンプレートでのエラー対応

エラーはこんな感じで展開する。
($validator->errors()->add()… とかでもいいんだろうか)

@foreach ($format_errors as $key => $val)
   @foreach($val as $msg) 
       <span class="error">データ{{ $key }}件目 : {{ $msg }}</span>
   @endforeach
@endforeach

やっぱり Laravel 便利

細かいところはまだまだありますが、こんな感じで書けるということで。
しかし、Laravelのバリデーションはホント有り難い…。

RecentPost