【Laravel】CSVの入力、出力
CSVで出力する
コレクション型のデータをcsvに書き起こし、DL/UPできる状態に持っていく。
サンプルファイルの解説
CSV配置のためのファイルを作成する
code:php
// 置く場所指定
$file = fopen(storage_path() . '/csv_download/post.csv', 'w');
だが、これだとcsv_importがstorageファイルにない場合は使えない。
code:php
2023-01-23 22:24:10 local.ERROR: fopen(laravel_kirthread/backend/storage/csv_import/post.csv): failed to open stream: No such file or directory 特定のディレクトリ配下で作業をするときは、事前にそのディレクトリをmkdirしておく必要がある。
(権限にもよるが、コード操作でディレクトリの作成はできない)
header()の指定
code:php
// どっちでも良さそう。csvで取得させるか、任意のバイナリデータで取得させるか
header("Content-Type: application/octet-stream");
header("Content-Type: text/csv");
// 強制ダウンロードと、名前を決める
header("Content-Disposition: attachment; filename=testdata.csv");
列名$headの文字コード変換
code:php
foreach ($head as $key => $value) {
$head$key = mb_convert_encoding($value, "SJIS", "UTF-8"); }
データ行$dataのコード変換
code:php
// データ行の文字コード変換・加工
foreach ($data as $data_key => $line) {
foreach ($line as $line_key => $value) {
// 0をエクセルで表示させたい
if ($line_key == 0) {
$value = '="' . $value . '"';
}
// , があったらダブルクォーテーションで囲む
if (strpos($value, ',')) {
} else {
}
}
}
echo implode($head, ",") . "\r\n"; // echoで書き込む
Laravelのものを落としてみる
code:php
// ヘッダ行の定義
$posts = ... // 上のheadに沿うような値を、Post::select()で取得しておく
// ファイル書き込み
$f = fopen(storage_path() . '/csv/post.csv', 'w');
if ($f) {
// カラム書き込み
mb_convert_variables('SJIS', 'UTF-8', $head);
fputcsv($f, $head);
// データ書き込み
foreach ($posts as $post) {
mb_convert_variables('SJIS', 'UTF-8', $post);
fputcsv($f, $post);
}
}
fclose($f);
// HTTPヘッダの設定
header("Content-Type: application/octet-stream");
header('Content-Length: '.filesize(storage_path() . '/csv/post.csv'));
header('Content-Disposition: attachment; filename=post.csv');
readfile(storage_path() . '/csv/post.csv');
readfile
ファイルを読み込んで、標準出力に書き出す関数
post.csvを書き出して、content_typeがcsv指定されているから書き出したものがダウンロードされているイメージ
基本はこれ。
mb_convert_variables()は文字コードを「UTF-8」から「SJIS」に変換している。
UTF-8からSJISに文字コードを変換する理由は、CSVファイルは基本的にSJISで保存するため。
UTF-8のまま書き込むと文字化けの原因となる。実際に書き込みを行っているのはfputcsv()。
CSVで入力する
CSVのデータをpostsテーブルに書き込むような処理を作ってやってみる
まずはformの仕組みををざっくり振り返ろう
フォーム
基本は、@csrfでトークンを生成して送る仕組み
code:html
<form method="POST" action="{{ route('study_import_csv_store') }}">
@csrf
<div class="form-group">
<label for="importCsvFile">投稿としてインポートするファイルを選択</label>
<input type="file" class="form-control-file" id="importCsvFile" name="importCsvFile">
</div>
<button type="submit" class="btn btn-primary mb-2">取り込む</button>
</form>
バリデーション
Controller側で指定するタイプのものは、Requestクラスの持つvalidateメソッドを使う。
code:php
public function importPostByCsvStore(Request $request) {
// バリデーション定義
$validatedData = $request->validate([
// 'inputタグのnameの値' => '条件'
'importCsvFile' => 'required'
]);
バリデーションと使用可能なバリデーションルールは以下を参照する。
なんかバリデーションがおかしいな なんだ
code:php
// こうしているのに、png画像を貼ってもバリデーションで返されてしまっていた
'importCsvFile' => 'required|image'
enctypeの指定がformに無かったことが原因だった。
enctype="multipart/form-data"
ファイルを送るときにフォーム要素に記入する項目
指定しないと添付ファイルの情報を送信できておらず、サーバ側で扱えないというような現象になるらしい
バリデーションの複数定義
code:php
// 体裁を整えようとして、空白などを入れない方がいい(エラーの原因となる)。
'importCsvFile' => 'required | mimes:csv, txt | mimetypes:text/plain'
// ベタ書きする
'importCsvFile' => 'required|mimes:csv,txt|mimetypes:text/plain'
CSVインポート時のバリデーション定義について
mimes
拡張子のこと。
mimetypes
拡張子のタイプ的な?CSVはtext/plainらしい。
Requestクラスでのファイル操作
code:php
public function importPostByCsvStore(Request $request) {
$validatedData = $request->validate([
'importCsvFile' => 'required|mimes:csv,txt|mimetypes:text/plain'
]);
dd(
// input nameの値から、様々な操作ができる
$request->hasFile('importCsvFile'),
$request->importCsvFile,
$request->importCsvFile->get() // これだけでデータの取得ができる
);
https://scrapbox.io/files/63f9d3c537d9cf001b38b51a.png
$request->importCsvFile->get()で取得できるが、
データをサーバ内もしくはs3バケットなどに格納してから作業するのが基本。
ファイルの格納
$request->importCsvFile->storeAs('public/', "importCsvPosts.csv");
store()系の関数は、基本的にstorage/app下に格納されるようになっている
この場合はstorage/app/public/importCsvPosts.csvとして保存されている
ファイルの読み込み
$saved_csv_data = \Storage::disk('local')->get('public/importCsvPosts.csv');
Storageクラス
filesystem.phpに詳しいconfigがある。
code:php
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
storage_path(): storageディレクトリまでのサーバの絶対パスを出す
なのでここでは、storage/appのディレクトリのことを指している
storage/appまで行って、public/importCsvPosts.csvを取得するという形。
※csvインポートの処理を例外処理で括っている場合
DB::transactionの内部にはファイル処理は書かないようにする。
データベースの処理とは関係ないため、エラーが発生する
改行コードを\nに統一しておく
\rなどが混じるとバグの原因になるので、どちらかに統一しておくとよい
code:php
// 改行コードを合わせる
$saved_csv_data = str_replace(array("\r\n","\r"), "\n", $saved_csv_data);
統一しなかった場合の$collection_csv_data = collect(explode("\n", $saved_csv_data));
https://scrapbox.io/files/63fad2dfd06e52001ca47a66.png
統一した場合の$collection_csv_data = collect(explode("\n", $saved_csv_data));
https://scrapbox.io/files/63fad30ad5d6aa001beff239.png
ヘッダー文字と値を分ける
code:php
$csv_header = $collection_csv_data->first();
$collection_csv_data = $collection_csv_data->filter(function ($value, $key) {
return $key > 0; // インデックスが0以上のものを格納する });
1行ずつ処理する
code:php
foreach ($collection_csv_data as $c) {
$c = explode(',', $c);
}
これで体裁上は完成。
ただし、このままだとcsvの値が違ったり、ずれていたりするとズレた内容が格納されるため、
その部分についてのチェックは自分で作成して書く必要はある。