Laravel5.3で、jQueryでPOST(ajax)されたcsvファイルを受け取り、 一行ずつ読み込んでインポートをする処理を実装したので、手順をまとめます。
csvファイルの文字コードはSJIS-winの想定です。 UTF-8で送られてくるとエラーとなります。
UTF-8で送られた場合でも上手く処理できるようにしたかったのですが、 エンコード周りが上手くできませんでした・・・ 誰か詳しい方がいらっしゃいましたら、コメントをお願いします。
csvの処理周りは、今回はこちらのLaravel Excelを使いました。 Laravel Excel
こちらの記事のファサードやサービスプロバイダの登録部分を行わないと 上手く動きませんので、合わせて読んでいただければと思います。 Laravel 5.3でcsvのダウンロード機能を実装
では早速インストール手順から。
インストール
コマンド実行
$ composer require maatwebsite/excel:~2.1.0
config/app.phpの編集
'providers' => [
~~ 省略 ~~
Maatwebsite\Excel\ExcelServiceProvider::class,
],
'aliases' => [
~~ 省略 ~~
'Excel' => Maatwebsite\Excel\Facades\Excel::class,
],
設定のロード、キャッシュ削除
必要であれば行ってください。
$ composer update
$ APP_ENV=local php artisan config:cache
$ composer dump-autoload
これで設定は完了です。 続いてソースコードを作成していきます。
クライアント側
html
<form id="csv_import_form"
method="POST"
action="/csv_import"
enctype="multipart/form-data">
<fieldset>
<legend>ファイル選択</legend>
<p>
csvファイル:<input type="file" id="csv_file" name="csv_file">
</p>
</fieldset>
<p>
<input type="submit" id="submit_button" name="submit_button" value="インポート" disabled>
</p>
{{ csrf_field() }}
</form>
jQuery
二重送信防止、 ファイルを選択していない場合の「インポート」ボタンのdisabled、 送信後にformの値をリセット
などの処理を入れています。
/**
* csvファイルの選択が変更された時に動作。
* インポートボタンのdisabledの切り替えを行う。
*/
$('#csv_file').change(function(event) {
var file_obj = $(this)[0].files[0];
if (typeof(file_obj) === "undefined") {
$('#submit_button').prop("disabled", true);
} else {
$('#submit_button').prop("disabled", false);
}
});
/**
* インポートボタンが押された時に動作。
* サーバへcsvファイルを送り、結果を受け取り処理を行う。
*/
$('#csv_import_form').submit(function(event) {
// HTMLでの送信をキャンセル
event.preventDefault();
// 操作対象のフォーム要素を取得
var $form = $(this);
// 送信ボタンを取得
var $button = $form.find('submit_button');
// 送信データ
// $form.serialize()
var formData = new FormData($(this).get(0));
// 送信
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
processData: false,
contentType: false,
data: formData,
timeout: 10000, // 単位はミリ秒
// 送信前
beforeSend: function(xhr, settings) {
// ボタンを無効化し、二重送信を防止
$button.attr('disabled', true);
},
// 応答後
complete: function(xhr, textStatus) {
// ボタンを有効化し、再送信を許可
$button.attr('disabled', false);
},
// 通信成功時の処理
success: function(result, textStatus, xhr) {
console.log("success");
console.log(result);
console.log(textStatus);
// csvファイルのファイル名を取得
// $('#csv_file')[0].files[0].name
// 入力値を初期化
$form[0].reset();
$('#submit_button').prop("disabled", true);
},
// 通信失敗時の処理
error: function(xhr, textStatus, error) {
console.log("error");
console.log(error);
}
});
});
サーバ側
router
Route::post('/csv_import', 'Controller@csv_import');
controller
validateにエラーがあった場合は-1を返して、それ以外は1。 それによって読み込んだcsvファイルに何件エラーがあったのかカウントできるようにしてます。
use Illuminate\Support\Facades\Log;
use CSV;
use Validator;
class Controller extends Controller {
~~省略~~
/**
* faqのcsvインポートを行う。
* @param Requrest $request - リクエスト
*/
public function csv_import(Request $request) {
Log::debug($request);
// validate
if (!isset($request["csv_file"])) {
return response()->json("csv_file not found.", 400);
}
// csvファイルを取得
$file = $request->file('csv_file');
/**
* 一行ずつ、csvの取り込みを行う
* @param array $row - 取り込みデータ
* @return number - 1 = 成功、 -1 = 失敗
*/
$result = CSV::read($file, function($row) {
Log::debug("csv_import read()");
log::debug($row);
// validation
$validator = Validator::make($row, [
'id' => 'required',
'text' => 'required'
]);
if ($validator->fails()) {
return -1;
}
return 1;
});
Log::debug($result);
return response()->json($result, 200);
}
}
CSV
<?php
namespace App\Repositories;
use Response;
use Config;
use Excel;
use Illuminate\Support\Facades\Log;
class CSV {
public function __construct() {
}
/**
* CSVファイルの読み込みを行う
* @param Illuminate\Http\UploadedFile $csv_file - csvファイル
* @param function $callback - 一行読み込み毎に呼び出されるコールバック関数
* @return Number 読み込んだ行数
*/
public function read($csv_file, $callback) {
Log::debug($csv_file->getRealPath());
//$enc = ['UTF-8','ASCII', 'SJIS-win', 'SJIS', 'EUC-JP'];
//mb_detect_order($enc);
Log::debug(mb_detect_encoding($csv_file));
/*
if (mb_detect_encoding($csv_file) == "UTF-8") {
Log::debug("encoding");
// "auto" は、"ASCII,JIS,UTF-8,EUC-JP,SJIS" に展開される
$csv_file = mb_convert_encoding($csv_file, "SJIS-win", "UTF-8");
}
*/
// "auto" は、"ASCII,JIS,UTF-8,EUC-JP,SJIS" に展開される
$csv_file = mb_convert_encoding($csv_file, "SJIS-win", "UTF-8, ASCII, SJIS-win");
$reader = Excel::load($csv_file, 'SJIS-win');
$rows = $reader->toArray();
$success_cnt = 0;
$error_cnt = 0;
foreach ($rows as $row){
$result = $callback($row);
if ($result == 1) {
$success_cnt++;
} else {
$error_cnt++;
}
}
return ["success_count" => $success_cnt, "error_count" => $error_cnt];
}
/**
* CSVダウンロード
* @param array $list
* @param array $header
* @param string $filename
* @return \Illuminate\Http\Response
*/
public function download($list, $header, $filename) {
if (count($header) > 0) {
array_unshift($list, $header);
}
$stream = fopen('php://temp', 'r+b');
foreach ($list as $row) {
fputcsv($stream, $row);
}
rewind($stream);
$csv = str_replace(PHP_EOL, "\r\n", stream_get_contents($stream));
$csv = mb_convert_encoding($csv, 'SJIS-win', 'UTF-8');
$headers = array(
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=$filename",
);
return \Response::make($csv, 200, $headers);
}
}
あとは、ControllerのCSV::readの関数内に処理を記述していくだけです。 このサンプルだと、送られてくるcsvファイルは以下のようなフォーマットを想定。
id, text
1, test1,
2, テストデータ,
3,,
これをSJISで保存して送ります。 レスポンス結果は、success_count = 2件、error_count = 1件になります。
実装中に起こった問題
日本語がfalseとなる
Log::debug();でデータを表示してみたところ、日本語の部分がfalseになってました。 要はマルチバイト文字の読み込みが上手くできていなかった。
local.DEBUG: array (
'id' => 2.0,
'text' => false,
)
原因は、ExcelのファイルがUTF-8で保存されていたからでした。 SJISで保存し直したら読み込めました。
local.DEBUG: array (
'id' => 2.0,
'text' => 'テストデータ',
)
windowsで作られることが多いことを想定して、 SJIS-winでloadを指定していたので、当然ですね。
$reader = Excel::load($csv_file->getRealPath(), 'SJIS-win');
参考
以下のサイトを参考にさせていただきました、ありがとうございました。