前回laravelでcsvインポートの記事を書いたのですが、今回はSplFileObjectを使って実装した記事になります。
前回の記事 - Laravel 5.3でcsvインポート機能を実装 csvの実装部分以外は前回の記事と同じ手順で導入可能です。
前回の記事はPHPExcelを使ったのですが、環境によっては以下のExceptionが発生します。
発生したException
Symfony\Component\Debug\Exception\FatalThrowableError: Class 'ZipArchive' not found in /var/www/vendor/phpoffice/phpexcel/Classes/PHPExcel/Reader/Excel2007.php:94
Stack trace:
#0 /var/www/vendor/maatwebsite/excel/src/Maatwebsite/Excel/Classes/FormatIdentifier.php(249): PHPExcel_Reader_Excel2007->canRead('/tmp/php2BSOj0')
#1 /var/www/vendor/maatwebsite/excel/src/Maatwebsite/Excel/Classes/FormatIdentifier.php(229): Maatwebsite\Excel\Classes\FormatIdentifier->canRead('Excel2007', '/tmp/php2BSOj0')
#2 /var/www/vendor/maatwebsite/excel/src/Maatwebsite/Excel/Classes/FormatIdentifier.php(55): Maatwebsite\Excel\Classes\FormatIdentifier->lastResort('/tmp/php2BSOj0', NULL, '')
#3 /var/www/vendor/maatwebsite/excel/src/Maatwebsite/Excel/Readers/LaravelExcelReader.php(1341): Maatwebsite\Excel\Classes\FormatIdentifier->getFormatByFile('/tmp/php2BSOj0')
#4 /var/www/vendor/maatwebsite/excel/src/Maatwebsite/Excel/Readers/LaravelExcelReader.php(777): Maatwebsite\Excel\Readers\LaravelExcelReader->_setFormat()
原因
ちょいちょい情報が古いかもしれませんが、
- PHPExcel使用時に「Fatal error: Class 'ZipArchive' not found」エラー
- PHPExcel を使って、PHPからExcelファイルを作成する方法
- Linux環境で「PHPExcel」を使うために必要なソフト
どうやら、zipサポートを有効にしていない設定でコンパイルしたphpだと発生する模様。 解決するには、phpを再コンパイルするか、 必要なライブラリをインストールした後、php.iniファイルにextension=zip.so
を追記して webサーバを再起動する必要がありそう。
解決方法
私の環境だとphpの再コンパイルやphp.iniの編集は難しいので、 PHPExcelを使わない方法で対応しました。
調べると、SplFileObjectという便利なものがありましたので、 早速リファクタリングを実施。
SplFileObjectはUTF-8でファイルを読み込む必要があるので、エンコードをしています。 私の環境だとSJIS(windows)を使って仕事をしている人が多いので、 csvファイルがSJISでアップロードされてくる想定で作りました。
CSV.php
<?php
namespace App\Repositories;
use Response;
use Config;
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());
Log::debug(mb_detect_encoding($csv_file));
// tmpファイル名取得
$file_tmp_name = "/var/tmp/csv_file.csv";
// 文字コードを変換してファイルを置換(csvファイルはsjis-winでくる想定)
$detect_order = 'SJIS-win,ASCII,JIS,UTF-8,CP51932';
$buffer = file_get_contents($csv_file->getRealPath());
if (!$encoding = mb_detect_encoding($buffer, $detect_order, true)) {
// 文字コードの自動判定に失敗
unset($buffer);
throw new \RuntimeException('Character set detection failed');
}
// tmpファイル作成
file_put_contents($file_tmp_name, mb_convert_encoding($buffer, 'UTF-8', $encoding));
unset($buffer);
// utf-8でファイル読み込み
$rows = new \SplFileObject($file_tmp_name);
$rows->setFlags(\SplFileObject::READ_CSV
| \SplFileObject::READ_AHEAD //先読み・巻き戻しで読み出す。
| \SplFileObject::SKIP_EMPTY //ファイルの空行を読み飛ばす。read_aheadとセットで使わないとダメ。
| \SplFileObject::DROP_NEW_LINE //行末の改行を読み飛ばす。
);
$row_cnt = 0;
$success_cnt = 0;
$error_cnt = 0;
// csvファイルのデータ数分ループ
$csv_header = array('id', 'text');
foreach ($rows as $row) {
// タイトル行以外(一行目がタイトル行の想定)
if ($row_cnt > 0) {
$row_data = [];
// データ作成
Log::debug($row);
for($i = 0; $i < count($csv_header); $i++) {
$row_data[$csv_header[$i]] = $row[$i];
}
// コールバック実行
$result = $callback($row_data);
// 成功した場合
if ($result == 1) {
$success_cnt++;
// 失敗した場合
} else {
$error_cnt++;
}
}
$row_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);
}
}
使い方は前回の記事と変わりません。 phpもLaravelも、まだまだ自分の知らない機能や実装方法がありますね。