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のバリデーションはホント有り難い…。