PHP_XLSXWriterで10万行以上のCSVファイルをXLSXに変換してみた
当社のWeb関連技術の公開と採用活動のために掲載しています。
(2018年1月29日更新)
皆さんこんにちは。新人エンジニアのKと申します。
こちらでは、PHP上でCSVからXLSXワークシートへの変換を
行った際の記録を日誌としてまとめております。
長文になりますが、ご一読いただければ幸いです。
- 実行環境
- Windows 10 Home 64bit … ホストOS
- Oracle VM VirtualBox … 仮想化ソフト
- CentOS 7.2 … ゲストOS
- PHP 7.1
- Laravel 5.5 … PHPフレームワーク
- httpd … Apache HTTPサーバ -
使用ライブラリ
- PHPExcel(https://github.com/PHPOffice/PHPExcel)
- Laravel-Excel(https://github.com/Maatwebsite/Laravel-Excel)
- PHP_XLSXWriter(https://github.com/mk-j/PHP_XLSXWriter)
この記事の目次
- 2017年10月27日
- 2017年10月30日
- 2017年10月31日
- 2017年11月1日
- 2017年11月2日
- 2017年11月6日
- 2017年11月7日
- 2017年11月8日
- 2017年11月10日
- 2017年11月13日
- 2017年11月14日
- 2017年11月15日
- 2017年11月16日
- 2017年11月17日
- 2017年11月20日
- 2017年11月21日
- 2017年11月22日
- 2017年11月24日
- 2017年11月27日
- 2017年11月28日
- 2017年11月29日
- 2017年11月30日
- 2017年12月1日
- 2017年12月4日
- 2017年12月5日
- 2017年12月6日
- 2017年12月7日
- 2017年12月8日
- 2017年12月11日
2017年10月27日
-
作業内容
- laravel-debugbar, PHPExcelの導入
- PHPExcel, SplFileObjectでのCSV -> Excel変換 -
学習内容
- SplFileObjectクラスを使ったCSVの読み込み
- requireによる外部ファイルの読み込み -
作業メモ
- SplFileObject::READ_CSV
-> 現状CSVの読み込みが最も高速
- autoload
-> 自動で外部クラスを読み込む
- sql_autoload_register
-> インスタンス生成時に、読み込めないクラスがあった場合に呼び出される
=> 同様のメソッドに __autoload があるが、1つずつしか定義できない。
一方、sql_autoload_registerは複数同時に定義できるため、こちらの使用が
推奨されている。
2017年10月30日
-
作業内容
- PHPExcelでのCSV -> Excel変換
- O’Reilly 『Web API The Good Parts』 -
学習内容
- 文字列を囲む’ (クォーテーション) の削除
- mb_convert_encodingによるエンコード変換 -
作業メモ
- 要素1つごとにエンコード -> 時間がかかる
mb_convert_encodingでは配列で変換をかけられない
=> エンコードした一時ファイルを作成して読み込む// 元ファイルの内容を取得 $csvSrcData = file_get_contents([CSVのパス]); // 取得した内容のエンコーディング変更 $csvSrcData = mb_convert_encoding($csvSrcData, 'UTF-8', 'Shift-JIS'); // 一時ファイル作成 $csvTmpFile = tmpfile(); // 一時ファイルのメタ情報取得 $metaTmp = stream_get_meta_data($csvTmpFile); // 一時ファイルへの書き込み fwrite($csvTmpFile, $csvSrcData); // ファイルポインタを一時ファイルの先頭に移動 rewind($csvTmpFile);
- デリミタ(区切り文字),エンクロージャ(囲い文字),エスケープ記号の設定
// 一時ファイルの内容を取得 $csvTmpObj = new SplFileObject($metaTmp['uri']); $csvTmpObj->setFlags(SplFileObject::READ_CSV); // デリミタ等の設定 $csvTmpObj->setCsvControl([デリミタ], [エンクロージャ], [エスケープ記号]);
- 感想
- CSVをワークシートに出力する際に、エンクロージャまで文字列として出力されていたため、
公式のマニュアルを調べてコードを修正した。
また、エンコーディングの差異に起因する脆弱性についても考慮し、エンコーディングした
一時ファイルを作成することで対応した。- エンコーディングの問題を修正する際に、最初は取得した文字列を逐一エンコーディングしていたが、
他に方法がないか調べていたところ、エンコーディングした一時ファイルを読み込むという方法があった。
自分で考えていてもなかなかいいアプローチが見つからず、調べてみたら今回のように思いがけず簡単に
解決できてしまうことが度々あるため、分からなくなったら調べる、というのを今後も徹底していきたい。
2017年10月31日
-
作業内容
- PHPExcelでのCSV->Excel変換
- O’O’Reilly 『Web API The Good Parts』 -
学習内容
- Laravel-Excelの導入
- viewの設定 -
作業メモ
- view(‘welcome’)
-> [プロジェクトルート]/resources/views/welcome.blade.php- Laravel-Excel
-> composer.json"require":{ ~ "maatwebsite/excel": "~2.1.0" }
-> ‘composer update’
-> [プロジェクトルート]/config/app.php
“`app.php
‘providers’ => [
~
Maatwebsite\Excel\ExcelServiceProvider::class,
]
~
‘aliases’ => [
~
‘Excel’ => ‘Maatwebsite\Excel\Facades\Excel::class,
]
\- ビュー設定  -> web.phpRoute::get(‘CSVToWorkSheet’, function(){
return view(‘csvToWorksheet_XX’);
});
“` -
感想
- Laravel-Excelを導入してみたが、PHPExcelよりもLaravelに依存している部分が多く、
かなり構文が複雑だった。
また、PHPExcelで取得したCSVの配列をLaravel-Excelで利用しようと試みたが失敗した。CSVを読み込んでシートに出力するだけであればPHPExcelでも実行可能なので、当分は
PHPExcelを使用して変換を行うことにする。
2017年11月1日
-
作業内容
- PHPExcelを用いたCSV->Excel変換
-> コントローラの作成
=> [プロジェクトルート]/app/Http/Controllers/CSVToWSController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Requests; use App\Http\Controllers\Controller; use Illuminate\Foundation\Http\FormRequest; class CSVToWSController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function csvRecieve(Request $request) { // ファイルのアップロードを確認 if (!is_uploaded_file($request->file('csvfile'))){ return view('csvToWorksheet_XX', ['error' => 1]); } $csv = $request->file('csvfile')->getRealPath(); $csvSrcName = base_path()."/exceloutput/"."temp_".time().".csv"; move_uploaded_file($csv, $csvSrcName); // エラーなし return view('csvToWorksheet_XX', ['error' => '0' , 'csvSrcPath' => $csvSrcName]); } } // CSVファイルの検証 class ValidateCSVRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { // CSVファイルでない場合トップに戻る? return [ 'csvfile' => 'mimes:csv,txt', ]; } // 以下不要 /* public function messages() { return [ 'csvfile.mimes' => 'CSVではありません', ]; } */ }
-> web.php
=> Route::postで指定しないとviewに対してPOSTできない// CSVToWSController.blade.phpの呼び出し Route::get('/csvToWorksheet','CSVToWSController@csvRecieve'); Route::post('/csvToWorksheet','CSVToWSController@csvRecieve'); Route::get('errorPage', function(){ return view('csvErrorPage'); });
-> welcome.blade.php
~ <form action="csvToWorksheet" method="POST" enctype="multipart/form-data"> {{ csrf_field() }} <!-- これを入力しないとCSRFトークン関連で419エラーが出る --> ~
- 感想
- まだLaravelのViewについてよく理解できておらず、コードを手直ししてブラウザ側で
再読み込み、を繰り返して修正した。
最初はphpファイルをpublicフォルダ内に配置して実行していたが、Viewでファイル名を
隠せることがわかり、コントローラを自作してPOSTメソッドでのやり取りができるように
した。また、CSVファイルのバリデーションをする際に、バリデーションに失敗するとなぜかトップに
戻されるため、原因を調べたところ、自動的に前ページへ戻されるようになっており、その
過程でエラーの内容を取得できることも分かったため、エラーを取得してトップページに
エラー内容を表示させるようにしようと考えた。しかし、エラー内容を取得する前にトップページに戻ってしまうため、エラー内容を取得して
独自にメッセージを出せないか次回以降調べていきたい。
2017年11月2日
-
作業内容
- PHPExcelによるCSV->Excel変換
-> コントローラ設定
=> [プロジェクトルート]/app/Http/Controllers/CSVToWSController.php$this->validate( $request, ['csvfile' => 'required|mimes:csv,txt'], [ 'csvfile.required' => view('csvErrorPage',['errorNum' => 0]), 'csvfile.mimes' => view('csvErrorPage',['errorNum' => 1]) ] );
‘required’ はファイルの有無, ‘mimes’ではファイル形式を検証可能
エラーページ ‘csvErrorPage’ に数値を渡し、トップページにエラーメッセージを
View経由で表示させることが可能
当初はエラーページに飛んだあとトップページにリダイレクトさせる予定であったが、
ビューでエラーメッセージを表示させることで、CSVの再入力も容易になった。 -
書籍
- 上田 勲『プリンシプルオブプログラミング』P.10 ~ P.47
プログラマとして身に付けるべき原則が書かれている書籍である。代表から「是非読んでほしい」とのことで貸していただいている本だが、
内容を見てみると私が普段ついやってしまいがちなコードの書き方が
良くない例として載っており、前半からいきなり反省すべき点を見つけて
しまった。癖になってしまっている書き方がよくない例としてしっかり上がって
しまっているので、本書の内容を実践して癖を一つ一つ直していきたい。
2017年11月6日
-
作業内容
- PHPExcelを用いたCSV->Excel変換
前回、膨大なサイズのCSV処理を想定し、10000レコード,50カラムのCSVを作成し
処理させてみたところ、Allowed memory size of 134217728 bytes exhausted (tried to allocate 4096 bytes)
メモリ不足のエラーメッセージが出力され処理が中断された。
生成されるはずのシートも破損して内容を確認することができなかった。1シートに全てまとめて出力しようとしたことがこの問題の原因と推測し、
今回は以下のような方法でシートの出力を試みた。
➀ 100レコードにつき1シートとして保存
➁ 100レコードにつき1シート、500レコードにつき1ブックとして保存レコード数を変えて複数回実験してみたが、結果はいずれも
Allowed memory size of 134217728 bytes exhausted (tried to allocate 589824 bytes) ^^^^^^
のように、多少数値に違いがあった(^部分)ものの同じようなエラーメッセージが
出力され処理が中断。
ただし今回は途中に保存を数度行っていたため、何レコード目で中断したかを
調べることができた。結果としては、最大で7000レコード弱まで記録できていたが、それ以降が
データとして読み取ることができておらず、Excelで開いたところ破損シートと
して扱われたシートがあることが分かった。
また、分割の仕方によっては極めて低速になってしまうパターンがあったため、
仮に実行できたとしても実用性がないと感じた。次回はもう少し構文を簡略化して少しでもメモリの使用量を減らしたうえで
同様の手順を実行してみることにする。Laravel経由での実行はもう少し後になりそうな気がする…
-
書籍
- 上田 勲『プリンシプルオブプログラミング』 P.64 ~ P.76
自分の癖として、変数名がやたら短縮されていたり、逆に長くなったりすること、
同じ処理でも変数の受け渡し方が異なる、というようにコードの書き方がかなり
バラバラになってしまうことがある。
同じ処理であれば同じく、またどこかにまとめておくことでコード自体の短縮や
簡略化が図れることは分かってはいるが、気を抜くとつい乱雑な書き方をしてしまう
ことがあるので、日々こういった点に気を付けながらコードを書くように心がけたい。
2017年11月7日
-
作業内容
- PHPExcelを用いたCSV->Excel変換1セルずつ書き込んで列幅の調整等を行う方法から、読み込んだ配列を
fromArray()メソッドでそのままシートに格納する方法に変更// 列 $excelRow = 1; foreach ($csvTmpObj as $csvLine){ // 最終行が空白だった場合、最終行の内容を配列に入れない if (!is_null($csvLine[0])){ // セル'A1'から順番に格納する $sheet->fromArray($csvLine, null, 'A'.$excelRow++); } }
列幅の調整を行わないことで処理を簡略化した。
実行すると前回と同様、メモリ関連のエラーが発生した。
記録レコード数を変えてもエラーが続いたため、調べたところやはりメモリ不足に
起因しているようであったため、php.iniで使用メモリ自体を増やすことにした。// memory_limit = 128M memory_limit = 512M
結果、これまで失敗していた全パターンが問題なく動作、出力でき、
再度1シートに出力するように処理を変えたところ、エラーが出ることなく
出力することができた。
ただ、動作自体は重く、実用性には欠けると感じた。- Laravel5.5 経由でのCSV->Excel変換
前述の処理が成功したことに伴い、Laravel上で動かしているコードも
変更を行った。今度はLaravel経由で動かしたが、いざCSVファイルを読み込むと
「failed to upload」の文言が。
調べたところphp.iniのアップロードファイルサイズの上限が小さいらしく、
CSVのサイズよりも大きい値に変更した。// upload_max_filesize = 2M upload_max_filesize = 20M
再度CSVファイルを読み込むとCSVダウンロード画面へ移行し、
ダウンロードしてみるとファイルの破損等もなくダウンロードできた。
ただ処理がかなり重いのが難点であるため、もう少し早く処理できる
方法を探したい。参考サイト:
http://www.larajapan.com/2016/03/26/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%EF%BC%88%EF%BC%91%EF%BC%89%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA/ -
書籍
- 上田 勲『プリンシプルオブプログラミング』 P.77~86カプセル化や抽象化についてはこれまでも何度か書籍で読んで学習してきたが、
重要な概念であると同時に、概念についての話であるため非常に難解な分野
であると感じている。C#やJavaでもprivate句などを使ってカプセル化を実装するが、私はどの変数を
カプセルするかしないかをよく考えずに実装した結果、必要な変数にアクセス
できなかったり、変えてはいけない変数を変更できてしまうなど、支離滅裂
なコードを作ってしまったという経験がある。非常に重要な部分なので、間違った覚え方をしないようにもう一度しっかりと
学習したい。
2017年11月8日
- 作業内容
- PHPExcelによるCSV->Excel変換
-
重複部分修正
[Proj.Root]/resources/views/ : view対象フォルダ
修正前csvToWorkSheet.blade.php : シート作成用view
// 出力先フォルダ $sheetFolder = "[シート出力先フォルダ]/"; // シート名 $sheetName = '[タイトル]'.'.xlsx'; // 出力 $sheetOutput = $sheetFolder.$sheetName; // Excelワークシートへの書き込み $writer = PHPExcel_IOFactory::createWriter($book, 'Excel2007'); $writer->save($sheetOutput); ~ <form action = "deleteSheet" method="POST"> {{ csrf_field() }} <input type="hidden" name="del_name" value="<?=$sheetName?>" /> ^^^^^^^^^^ シート名だけ <input class="top" type="submit" value="トップに戻る" /> </form>
=> コントローラ経由でシート名のみ渡していた
deleteSheet.blade.php : ファイル消去用view
if (file_exists("[シート出力先フォルダ]/".$deleteName)){ ^^^^^^^^^^^^^^^^^^^^ 重複 unlink("[シート出力先フォルダ]/".$deleteName); ^^^^^^^^^^^^^^^^^^^^^ 重複 }
=> フォルダ名を含めずにシート名を渡していたためフォルダ名までつける必要があった
修正後
csvToWorkSheet.blade.php
<form action = "deleteSheet" method="POST"> {{ csrf_field() }} <input type="hidden" name="del_name" value="<?=$sheetOutput?>" /> ^^^^^^^^^^^^ シート名+フォルダ名 <input class="top" type="submit" value="トップに戻る" /> </form>
deleteSheet.blade.php
if (file_exists($deleteName)){ unlink($deleteName); }
=> 重複していた部分を削除
このように重複して書かれている部分がまだ点在しているため、逐一修正していきたい。
-
大サイズCSVの変換
対象CSV : 100,000レコード , 50カラム (レコード数10倍)phpファイル単体で変換を実施
➀使用メモリサイズ : 512M
1レコードずつ追加結果 :
Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 4096 bytes)➁使用メモリサイズ : 無制限( -1 )
1レコードずつ追加結果 :
CPU使用率が100%到達
メモリ使用率が常時75%以上
20分以上経過したためCtrl+Cで中止
2017年11月10日
-
作業内容
- PHPExcelによるCSV->Excel変換
ファイル削除後にトップページにリダイレクトする処理を誤って
コメントアウトしていたのを修正
-> [Proj.Root]/resources/views/deleteSheet.php<?php $url = '/'; if (!is_null(@$deleteName)){ if (file_exists($deleteName)){ unlink($deleteName); } } /* <- ここと echo <<<RDT <script type="text/javascript"> location.replace('$url'); </script> RDT; */ <- ここを削除 ?>
- ブログ記事の作成
1. 記事内の誤字訂正
2. 脆弱性診断報告書の誤字訂正・アップロード
アップロードする際にファイルサイズ制限に引っかかる
-> 設定を変更してもらいアップロードできた=> 現在、小笠原代表に記事の内容のチェックを依頼中
-
感想
- 先日のセキュリティ研修の続きのような形で記事の作成をしていたが、
今日の段階でひとまず形にできた。文章を読むのは全く苦手ではないが、反面文章を書くとなると極端に
遅筆になり、かつ稚拙な文章になってしまうのでつい吹き出しそうになる。
今この感想を書いている段階でも何度かキーボードを打つ手が止まったり、
内容を打っては消しの繰り返しになっている。ブログ記事は現時点では定期的に出すかどうかは分からないが、少なくとも
記事を見た人に役立ててもらえるような文章を書くにはまだ至っていないと思う。
自分が今何を学習しているかについて常に把握し、ブログを見ている方にその内容を
できる限り分かりやすく伝えていけるよう努力したい。
2017年11月13日
-
作業内容
- PHPExcelによるCSV->Excel変換
機能追加1.範囲指定して整列
ex.) $sheet->getStyleByColumnAndRow(0,1,4,5) ->getAlignment() ->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_LEFT); -> 'A1' セルから 'E5' セルまで '左揃え' 01 45 HORIZONTAL_LEFT
2.ヘッダ行の列数調整
// ヘッダ行末尾が空白の場合削除し、列数を減らす if (empty($sheet->getCellByColumnAndRow($excelCol,1)->getValue())){ $excelCol--; }
->行の末尾にコンマがついている場合、必ず空白の列として読み込むため
(読み込み時に列数を確認した際に判明)3.列幅の調整
ex.) // 列番号 $colomn = 0; // 列幅の自動調整を止める $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); // 列幅の再計算 $sheet->calculateColumnWidths(); // 現在の列幅を取得 $width = $sheet->getColumnDimensionByColumn($column)->getWidth(); // 適当な幅に調整(ここでは 元の列端 * 1.2 に調整) $sheet->getColumnDimensionByColumn($column)->setWidth($width * 1.2);
-> 列幅の自動調整機能はあるが、正常に動作しない(列幅が乱れる)
4.アクティブセルを先頭(A1)セルに移動
$sheet->setSelectedCells('A1');
=>追加後、10000レコード50カラムのCSVファイルで動作検証済み
- Laravel-ExcelによるCSV->Excel変換
大容量のCSVサイズを読み込む際に30秒以上かかり自動でタイムアウトに
なるため、対策を考えている- ブログ記事公開
代表の確認が終了し、数か所の修正を行ってから公開した
2017年11月14日
-
作業内容
- PHPExcelによるCSV->Excel変換
BOM削除
->世界銀行のサイトからデータ(世界開発指標)をCSVでダウンロード
変換しようとしたが、先頭が文字化けした
=>バイナリエディタにかけたところBOMを発見UTF-8のBOMは3バイトであり、16進数に直すと
「EF BB BF」となる。
このことから、読み込んだCSVの先頭3バイトを検査し、合致した場合は
3バイト分除去する処理を追加した。// 文字列が空でない if (!is_null($csvSrcData)){ // 先頭3バイトを結合し16進数に変換した結果が // "EF BB FF"であった場合 if (preg_match("/^efbbbf/", bin2hex( $csvSrcData[0]. $csvSrcData[1]. $csvSrcData[2]) ) === 1) { // 先頭3バイトを除去 $csvSrcData = substr($csvSrcData,3); } }
->3バイト除去されるはずだが再び先頭が文字化け
=>CSVファイルを読み込む際にエンコード変換を行っているが、
SJIS->UTF-8への変換しか設定していなかったため、余計な
エンコードが行われてしまったEF BB BF (UTF-8) v 3F EF BD (SJIS->UTF-8)
コード
// CSV読み込み $csvSrcData = file_get_contents($csvSrcPath); // エンコード変換 $csvSrcData = mb_convert_encoding( $csvSrcData, "UTF-8", "SJIS"); | | 以下のように修正 v $csvSrcData = mb_convert_encoding( $csvSrcData, "UTF-8", "SJIS,SJIS-win,UTF-8"); ^^^^^ UTF-8を追加
想定されるエンコーディングにUTF-8を追加し、適切にエンコードの
変換が行われるようにした
->BOMの不正なエンコードが行われず、BOMを除去できるようになったphp.ini側の設定
;post_max_size = 8M post_max_size = 50M ; upload_max_filesize = 20M upload_max_filesize = 50M ;max_execution_time = 30 max_execution_time = 60 ;memory_limit = 512M memory_limit = 1024M
- SQLite3のインストール
cd /usr/local/src sudo wget http://www.sqlite.org/2017/sqlite-autoconf-3210000.tar.gz sudo tar -zxvf sqlite-autoconf-3210000.tar.gz cd sqlite-autoconf-3210000 sudo ./configure sudo make sudo make install
- SQLite3データベースの作成(既存ファイルへの追加)
トランザクション<?php $pdo = new PDO('sqlite:[データベースファイル名]'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $pdo->beginTransaction(); try{ $stmt = $pdo->prepare("insert into [テーブル名] values(~中略~)); $stmt->execute(); $pdo->commit(); } catch(Exception $e){ echo $e->getMessage().PHP_EOL; $pdo->rollBack(); } ?>
2017年11月15日
- 作業内容
- SQLiteデータベースファイルからCSVへのデータ出力<?php // データベースに接続 $pdo = new PDO('sqlite:[DB.File]'); // エラーレポートのモードを「例外を投げる」モードに設定 // (例外の場合、自動でロールバックされる) $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // デフォルトのフェッチ(データ取得)モードの形式を // カラム名で添え字を付けた配列に設定 $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); try{ // DBのテーブル名 $tblName = "maillog"; // 出力先ファイルパス $csvPath = [OUTPUT.File]; // 空ファイルを作成 touch($csvPath); // ファイルを書き込みモードで開く $file = new SplFileObject($csvPath, "w"); // CSVのデリミタなどを設定 $file->setCSVControl(',','"','\\'); // プリペアドステートメントを使用してアクセス $stmt = $pdo->prepare("select * from ".$tblName); // SQL文実行 $stmt->execute(); // カラム数をカウント $colCount = $stmt->columnCount(); // CSVヘッダの設定 for($cols = 0; $cols < $colCount; $cols++){ // カラム名取得 $meta = $stmt->getColumnMeta($cols); // CSVヘッダ名用配列 // UTF-8からSJISにエンコードする $csvHeadEnc[] = mb_convert_encoding($meta['name'],'SJIS','UTF-8'); } // ヘッダを書き込む $file->fputcsv($csvHeadEnc); while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ foreach($row as $key => $rowElem){ // UTF-8からSJISにエンコードする $rowEnc[] = mb_convert_encoding($rowElem,'SJIS','UTF-8'); } // レコードを書き込む $file->fputcsv($rowEnc); } // DBから切断する $pdo = null; } catch (Exception $e){ // エラーメッセージをコンソールに出力 echo %e->getMessage().PHP_EOL; } ?>
- 書籍
- 『プリンシプルオブプログラミング』P.100~P.124
今回読んだところについて端的に述べると、ことごとく今の自分に
足りない考え方だと感じた。
動作することを前提にして書くこともあれば同じ内容を全然別の書き方で
書いたりすることも多々あり、とにかく安全で保守可能なソースとは
言えないものばかりを書いている。
改めて「仕事」であるという意識をもってソースをかけるようにしたい。
2017年11月16日
-
作業内容
- PHPExcelによるCSV->Excel変換// デフォルト // $cacheStorage = PHPExcel_CachedObjectStorageFactory::cache_in_memory; // 一時ファイル作成 $cacheStorage = PHPExcel_CachedObjectStorageFactory::cache_to_discISAM; // php://tempを準備 $cacheStorage = PHPExcel_CachedObjectStorageFactory::cache_to_phpTemp; // キャッシュの一時保存先(discISAM用) $cacheSetting = array('dir' => [Temp.Dest.Dir]); // php://tempのサイズ $cacheSetting = array('memoryCacheSize' => '512MB'); // キャッシュ保存方法を設定 if (!PHPExcel_Settings::setCacheStorageMethod($cacheStorage, $cacheSetting)){ die('Cannot set PHPExcel Cache Storage.'); }
-> 実行するもメモリ使用量過多は改善されず
=> 別アプローチを模索中 -
書籍
- 『プリンシプルオブプログラミング』P.125~P.152
コードを単純かつ透明なまま機能を追加していくのは、非常に
高度なテクニックだと思う。
機能は追加すればするほど根本となる部分がコードの中に埋もれていき、
見つけるのが困難になってしまう。
ただあれを足して、これも足して、というのではなく、ある程度共通性の
ある処理ごとに区分けして、関連性を見やすくしていくことが
透明性や単純性に寄与すると思う。 -
感想
- メモリ管理の問題が全くと言っていいほど進展せず少し参っている。
PHPSpreadSheet等も使いたいが同様にメモリ消費の問題があるとの
情報もあり、現状ではこちらのライブラリを使わざるを得ない。
キャッシュに書き出す段階で莫大なデータ量になっており、
かつ回避の手立てがないため、ほぼ手詰まりになってしまっている。
数日中には解決できるようにしたい。
2017年11月17日
-
トラブル
- Windows10アップデート後からキーボードのテンキーがVimでうまく動作しない
=> Numlockキーでvimのhelp.txtが表示されるように
-> TeraTermの設定->端末->キーボード
->無効化するモードでアプリケーションキーパッドを無効化
=>Numlockキーの動作に変化はないものの数字キーは正常に動作
ただし記号のキーは等となり入力不能
現在も原因を調査中 -
作業内容
- PHPExcelによるCSV->Excel変換
先日からのメモリ使用量過多の問題が解決できず停滞中- 6階への引っ越し作業
2階にあった書類やオフィス用品の一部を運搬した
また、自分のデスクやPC等も移動し、以降は6階での作業となった
2017年11月20日
-
作業内容
- オフィス移転
事務用品やプリンタ、LAN等オフィス機能の大半を6階に移動した。
今日から本格的に6階での作業となるため、心機一転して業務に
取り組んでいきたい。- PHPExcelによるCSV->Excel変換
デリミタの調整がうまくいかず、クォーテーションまで出力される
不具合が発生
エンコードと同時にデリミタを置換、統一することで対処できるか検証中
2017年11月21日
-
作業内容
- PHPExcelによるCSV->Excel変換// 一時ファイル作成 $csvTmpFile = tmpfile(); // 一時ファイルの場所を取得 // $csvTmpFileMeta = stream_get_meta_data($csvTmpFile); // $csvEncoded ... エンコード済みCSV文字列 // CSV文字列を一時ファイルへ書き込む fwrite($csvTmpFile, $csvEncoded); rewind($csvTmpFile); // 下記の処理の変更に伴い削除 // $csvObj = new SplFileObject($csvTmpFileMeta['uri']); // $csvObj->setFlags(SplFileObject::READ_CSV); // $csvObj->setCsvControl(',',"'", '\\'); // fclose($csvTmpFile); // オブジェクトを保持していると余分にメモリを消費するため // ファイルポインタを使用して読み込むように変更 // foreach($csvObj as $csvLine){ while($csvLine = fgetcsv($csvTmpFile, 0, ',', '"', '\\')){ ~中略~ } fclose($csvTmpFile);
->実行するもメモリ使用量ほとんど改善なし
=> シートを出力する際にメモリ不足エラー発生
また、キャッシュを使用した場合、25000行あたりでキャッシュデータのサイズが
ディスクを逼迫するレベルに膨れ上がるため、仮想ハードディスクの容量を増加させる
必要あり?
2017年11月22日
-
作業内容
- PHPExcelによるCSV->Excel変換Vagrantfileの設定を変更
config.vm.provider "virtualbox" do |vb| # メモリを2048MBに vb.customize ["modifyvm", :id, "--memory", "2048"] end
-> php上でのメモリ使用量、キャッシュ容量を変更
// 2048MBまで使用可能 ini_set('memory_limit', '2048M'); ~ 中略 ~ // キャッシュのストレージを php://temp に $cacheStorage = PHPExcel_CachedObjectStorageFactory::cache_to_phpTemp; // キャッシュのサイズを1024MBに $cacheSetting = array('cacheSize' => '1024MB');
=>30000レコード50カラム程度であれば正常に動作したが、
40000レコード50カラム程度になると再度メモリ関連エラーが発生
->対策を検討=> PHP_XLSXWriterというライブラリを発見
使用したところメモリ関連のエラーが発生しない+
50000レコード50カラムのCSVを十数秒で処理(超高速)
-> ただし更新が5ヶ月前で止まっているインストール
cd [Proj.root] composer require mk-j/PHP_XLSXWriter --dev composer update
使用例
$excelWriter = new XLSXWriter(); $excelSheetName = [Sheet.Name]; // csvObjはSqlFileObjectで読み込んだCSVの内容 foreach($csvObj as $csvLine){ if (!is_null($csvLine[0])){ if ($excelRow === 0){ // ヘッダ行の記述 $excelWriter->writeSheetHeader($excelSheetName, $csvLine); } else{ // 以降の行の記述 $excelWriter->writeSheetRow($excelSheetName, $csvLine); } } // 一応配列を空にする unset($csvLine); } $excelWriter->writeToFile([File.Name]); $csvObj = null;
-> 100000レコード50カラムのCSVは二十数秒で処理可能
- ネームオブジェ塗装
オフィス入り口に飾るネームオブジェに色を塗った。
ベージュでなるべく濃いめに塗ったつもりだがまだ乾いていないため
後で仕上がりを確認する予定。 -
感想
- ややしばらくメモリ関連のエラーで苦しんでいたが、あっさり解決できて
しまう方法を見つけてしまい、少しガクッときている(笑)。
しかし、更新が止まっていること等を鑑みると、やはりPHPExcelが
必要なところはあると思う。
両方試しつつ、どちらを利用するかを決めていきたい。
2017年11月24日
-
作業内容
- XLSXWriterを用いたCSV->XLSX変換// 出力先フォルダ $excelFolder = base_path()."/exceloutput/"; // ファイル名 $excelName = 'csvToSheet_'.time(); $excelExt = '.xlsx'; // 出力先 $excelOutput = $excelFolder.$excelName.$excelExt; // シート名 $excelSheetName = 'Data'; // XLSXWriterオブジェクト $excelWriter = new XLSXWriter(); // ヘッダ行整列(左揃え) $excelHeaderAlign = ['font'=>'meiryo', 'halign'=>'left']; // 以降の行を整列(右揃え) $excelRowsAlign = ['font'=>'meiryo', 'halign'=>'right']; // ヘッダ行判別用カウンタ $excelRowsCount = 0; // $csvObjはSplFileObject::READ_CSVで読み込んだCSVの内容 foreach($csvObj as $csvLine){ if (!is_null($csvLine[0])){ // ヘッダ行 if ($excelRowsCount++ === 0){ // ヘッダ行の設定 $excelHeader = array(); foreach($csvLine as $csvHeaderElem){ // カラム内の文字列の形式を「文字列」に設定 $excelHeader[$csvHeaderElem] = 'string'; } // ヘッダ行の書き込み $excelWriter->writeSheetHeader( $excelSheetName, $excelHeader, $excelHeaderAlign); unset($excelHeader); } // 以降の行 else{ // 各行の内容を書き込む $excelWriter->writeSheetRow( $excelSheetName, $csvLine, $excelRowsAlign); } } unset($csvLine); } // 出力 $excelWriter->writeToFile($excelOutput);
- ネームオブジェの塗装
- 棚の組み立て
-
感想
- 先日社内で自作したネームオブジェの色塗りをしたが、乾くと角の
部分に塗料の塊ができてしまうため、それを削っては塗りを
繰り返している。
また、乾いたときに下地の色が若干見えてしまうため、重ね塗り
しつつ角にあまりのらないように調整するのが難しい。
2017年11月27日
-
作業内容
- XLSXWriterによるCSV->Excel変換
項目の追加
・文字色
・フォント(3種類)
・作成者氏名(20字まで)
・シート名(10字まで)
・水平方向の配置(ヘッダ、その他行別)Controller設定
// 文字色 // <input type="color" name="ch_color"> $charColor = $request->ch_color; // OK // フォントの種類 // <select name="font"> $font = $request->font; // OK // 作成者氏名 // <input type="text" name="si_name"> $clientName = htmlspecialchars($request->si_name,ENT_QUOTES); // シート名 // <input type="text" name="st_name"> $sheetName = htmlspecialchars($request->st_name,ENT_QUOTES); // ヘッダ行の水平方向配置 // <select name="hdr_align"> $headerAlign = $request->hdr_align; // その他の行の水平方向配置 // <select name="otr_align"> $otherAlign = $request->otr_align;
2017年11月28日
- 作業内容
- XLSXによるCSV->Excel変換
xlsxの中身
officeopenxml.comによると、XLSXを解凍するとファイル構造が
見られるとの記述があり、ファイルの形式をzipファイルに変更し解凍してみた(XLSX-File).zip | |-- _rels | |-- .rels 関係性定義ファイル | |-- docProps | |-- app.xml | |-- core.xml | |-- xl | |-- _rels | | |-- workbook.xml.rels ワークシート内ファイルの関連性 | | | |-- worksheets | | |-- sheet1.xml | | | |-- styles.xml スタイルの定義 | |-- workbook.xml メイン部分 | |-- [Content_Types].xml ドキュメントタイプ定義ファイル
プルダウンメニュー
HTMLだけでなくLaravelのviewでも表示できるとのこと
(qiita.com/sakuraya/items/42fffe0f171d49ee74a0)なので、
viewを使用して表示してみたconfigフォルダに以下の内容のファイル(font.php)を作成
<?php return [ 'MSゴシック'=>'MS Gothic', 'Arial'=>'Arial', 'メイリオ'=>'Meiryo', 'HGゴシックM'=>'HGGothicM', 'Lucida Sans'=>'Lucida sans Unicode', 'Consolas'=>'Consolas', ] ?>
トップページのviewの一部を以下のように変更
フォント:  <select name="font"> <option value="ms gothic">MS ゴシック</option> <option value="arial">Arial</option> <option value="meiryo">メイリオ</option> </select> | v フォント:  <select name="font"> @foreach(config('font') as $name => $font) <option value="{{ $font }}">$name</option> @endforeach </select>
=> 読み込めずエラーに
-> web.phpに直で記入Route::get('/', function () { $fonts = [ 'Arial' => 'Arial', 'Lucida Sans' => 'Lucida Sans Unicode', 'Consolas' => 'Consolas', 'メイリオ' => 'Meiryo', '游ゴシック' => 'Yu Gothic', 'MSゴシック' => 'MS Gothic', 'HGゴシックM' => 'HGGothicM', ]; return view('welcome')->with(['fonts' => $fonts]); });
-> トップページのviewも以下のように変更
フォント:  <select name="font"> @foreach(config('font') as $name => $font) <option value="{{ $font }}">{{ $name }}</option> @endforeach </select> | v フォント:  <select name="font"> @foreach($fonts as $name => $font) <option value="{{ $font }}">{{ $name }}</option> @endforeach </select>
=> 一応正常に動作したが、これらの配列は別ファイルに書きたいため、
読み込む方法を検討中 -
感想
- インターンシップとして専門学校生の方の訪問があった。
自分もインターンシップで数社を見て回ったことがあり、それをふと
思い出していた。
自分の場合は卒業年度の前半ですでに前職の会社で内定が決定しており、同級生の
中では一番早く決まっていた。
早く決まって当時は嬉しかったが、今考えるともっとじっくり考えてみるべきでは
なかっただろうかという後悔もある。
今回訪問してくださった学生の方には、是非とも慎重に就職先を選び、後悔のない
就職をしてもらいたいと願うばかりである。
2017年11月29日
-
作業内容
- XLSXWriterによるCSV->XLSX変換
項目の追加
・セルの着色(無色も別途選択可能)
・セルプロパティの区別(ヘッダ行とそれ以外の行)
・各項目の配置を一部変更
・フォントの追加
・セル内文字列の配置方法に「両端揃え」=> 列や行の固定も追加しようとしたが実装されていないため
追加を見送った空白列,はみ出た列の削除
$excelRowsCount = 0; $headerCols = 0; foreach($csvObj as $csvLine){ if (!is_null($csvLine[0])){ // ヘッダ行 if ($excelRowsCount++ === 0){ $excelHeader = array(); foreach($csvLine as $csvHeaderElem){ // ヘッダ列が空白でない場合 if (!empty($csvHeaderElem)){ // 列の書式を設定 $excelHeader[$csvHeaderElem] = 'string'; $headerCols++; } // 空白であった場合 else{ break; } } // シートへの書き込み $excelWriter->writeSheetHeader( $excelSheetName, $excelHeader, $excelHeaderAlign); unset($excelHeader); } // 以降の行 else{ // 現在取得している行の列数 $tmpCols = count($csvLine); // 列数がヘッダの列数を上回っている場合 if ($tmpCols > $headerCols){ // はみ出た列の削除 array_splice( $csvLine, $headerCols-1, $tmpCols-$headerCols); } // シートへの書き込み $excelWriter->writeSheetRow( $excelSheetName, $csvLine, $excelRowsAlign); } } unset($csvLine); }
array_splice
参考:https://qiita.com/Quantum/items/767dba44af81d1825248
http://php.net/manual/ja/function.array-splice.php -
感想
- 当面の課題であったメモリ管理の問題が解決したこともあり、
ここ数日でかなり機能を追加することができた。
ただ、今使っているライブラリ(XLSXWriter)が数か月前から
更新されていない点や、列固定などがまだ実装されていない点
などが少し気になっている。
最悪自分でコードを追加するかどうかも検討する必要があるかもしれない。
2017年11月30日
-
作業内容
- XLSXWriterによるCSV->Excel変換
発生中の問題
メモリ使用率は10000レコードでも10%に到達しないが、
CPU使用率が100%近くになっている方針の変更
現在作成しているアプリはCSVをユーザ側でアップロードし、
それをXLSX形式に変換するというものだが、代表に見ていただいた
ところ、「サーバ側のストレージにCSVファイルを置いてそれを
サーバ側で変換かけてユーザにダウンロードさせてはどうか」との
意見であったため、その方針で再度コードを組みなおすことにした。また、代表からLambdaやAzureを使用してCPU使用率を
考慮しないようにすればいいとの提案があったため、それらの
利用も視野に入れて準備したいと思う。経路
CSVファイルを要求 | | <---------------- | | | SV1 | ----------------> | SV2 | CSV->XLSX変換 CSVファイルを渡す ^ / / / 変換リクエスト / / XLSXファイルを渡す / v | Browser |
=> SV2をAzure等にすることでCPU使用率を抑えられる(?)
2017年12月1日
-
作業内容
- PHP_XLSXWriterによるCSV->XLSX変換CSVファイルをストレージに保存し、一覧を取得
-> ファイル名を取得するたびにテーブルに列を追加、
\<form>タグを用いて各列ごとにダウンロードボタンも付ける
=> 各列ごとにformを設けないと、ボタンを押した際にテーブル
末尾のCSVファイルが変換・DLされてしまうため- Fall Creators Updateに伴うvagrant起動不具合
出社時にいつもの通りvagrantを起動しようとしたところエラーで起動せず
-> 昨日のシャットダウン時に更新がかかっていたのを思い出す
調べたところ「Fall Creaters Update」更新プログラムの影響でvagrantの
起動不具合が発生しているという記事(Qiita)を発見
バージョンアップすればいいとのことでvirtualboxを更新するも起動せず
=> バージョン5.2は対応していないらしく5.1の最新版にダウングレード
したところようやく起動参考:
Qiita https://qiita.com/VTRyo/items/1b7a439844f93fd3b033 -
感想
- CSV一覧表示については、ベース自体できていたので特段困った点は無かった。
ただ、仮想サーバを増設した際にどのくらい処理の仕方に変化があるか分からないため、
コードをできる限り簡潔にしておいた。
virtualbox周りの更新については4月のCreatorsUpdate以来のことであったため、
正直に言って「またか」という感じになった(苦笑)。
ソフトウェアの更新は必要だが、それに伴ってこのような不具合も起こりうるため、
とくにサーバのソフトウェアを更新する際は気を付ける必要があると感じた。
2017年12月4日
-
作業内容
- PHP_XLSXWriterによるCSV->XLSX変換機能追加
・昇順、降順での並び替え(作成日時、ファイル名)スループットの改善
CPU使用率の増加に伴う処理の遅延は避けられないため、
コードの見直しを行うことで少しでも処理時間を削れるか
試してみた。・エンコーディング変換
CSVのエンコーディングを変換する際に、UTF-8であるかどうかに拘らず
変換を実施していたため、文字列のサンプルを採りエンコーディングの
種類を判定してから変換を実施するように処理を変更した。結果:約60ms減
・配列末尾の削除
ヘッダ行の個数に合わせてxlsxに格納するためはみ出した列を削除していたが、
その処理を省略した。結果:約30ms減
結果として100ms近く減らすことはできたが、やはりCPUの使用率が大きく
処理速度に影響していることが分かった。
他にも文字列の整列処理を削るなどいろいろ試したものの全く改善が見られず、
大きく結果が出たのは上記の2つであった。 -
感想
- 機能の見直しを行うことで、ある程度処理を軽くすることはできたものの、
それでも目に見えて高速になったわけではないように感じた。
また、不用意に機能を削っても、必ず高速化するわけではない上に、
アウトプットが満足のいくものでなくなってしまう可能性もあるため、
高速化とアウトプットの品質のバランスをとることが重要だと感じた。
2017年12月5日
-
作業内容
- PHP_XLSXWriterによるCSV->XLSX変換仮想サーバを2つ用意
config.vm.define "vm1" do | multi | multi.vm.network "private_network", ip: "192.168.33.10", virtualbox__intnet: "vmintnet" multi.vm.network "forwarded_port", host_ip: "127.0.0.1", guest: 80, host: 8080 end config.vm.define "vm2" do | multi | multi.vm.network "private_network", ip: "192.168.33.11", virtualbox__intnet: "vmintnet" multi.vm.network "forwarded_port", host_ip: "127.0.0.1", guest: 80, host: 8100 end
=> 起動するもののTeraTermでのSSH接続ができず
-> SSHポート関連のエラーが出ているため調査中==> 別個にフォルダを作成し完全に別マシンとして動かすことに
-
感想
- 仮想マシンのクローンの仕方をよく理解できておらず結局力任せの
ような状態になってしまった。
公式のドキュメントやその他専門のサイトを再度見直し、より時間を
短縮できるようにしたい。
2017年12月6日
-
作業内容
- 仮想サーバの作成
Vagrantファイルの入っているフォルダ(元フォルダ)でvagrant package
を実行し、package.boxを作成したあと、別フォルダに
Vagrantfileとpackage.boxをコピーする
コピーしたら、別フォルダ側のVagrantfileの設定を以下の
ように変更するconfig.vm.network "forwarded_port", host_ip: "127.0.0.1" , guest: 80, host: 8080 -> ~略~ host: 8100 config.vm.network "private_network", ip: "192.168.33.10" -> ~略~ ip: "192.168.33.11"
変更した後、別フォルダで
vagrant box add [新しいBOX名] ./package.box vagrant up
を順に実行し、virtualboxが立ち上がること、TeraTerm
でのSSH接続が可能であることを確認する。- 議事録の作成
コンテンツマーケティングに関しての会議の議事録を
作成することになった。
議事録の作成は全くやったことがないが、一応メモできる内容は
メモをとり、不要な部分を削るような形で議事録の原文を作成した。
内容は後ほど代表に確認していただく予定。
2017年12月7日
-
作業内容
- 仮想サーバ間でのCSVのやり取りCSV一覧の取得
viewを用いて別サーバからCSVの一覧を取得しようとしたが、
そもそもviewでは内部のフォルダにしかアクセスできない
らしく、頓挫した。
現在別の方法を模索中 -
Tips
VS Codeのウィンドウのフルパス表示について
仮想マシンをコピーした際、ファイルやフォルダも丸ごとコピー
&nesp;したため、phpファイルをVSCodeで編集している際に、どのフォルダに
入っているか分かりづらくなってしまったため、ウィンドウの表示を
フルパスに変更しようと思い、調べた。window.showFullPath: true;
これを設定フォルダに追記するとフルパス表示になるとのこと
なので、試そうとすると、そもそも存在しない設定、とVSCodeから
注意が出た。
さらに調べると、しばらく前のバージョンで設定が削除されたとの
公式のアナウンスがあり、それに代わる設定としてwindow.title
が追加されたとのこと。
表示形式の設定がいろいろあるようだが、とりあえずフルパス表示
できるようにwindow.title: "${activeEditorLong}"
と設定し、VSCodeを再度立ち上げる。
結果フルパス表示になり、ファイルの場所を把握しやすくなった。
2017年12月8日
-
作業内容
- CSV一覧の表示ストレージ側サーバにファイル名取得用PHPファイルを設置し、
javascript形式で動作させてCSV一覧を取得しようと試みる
->{{csrf_field()}}
が動作せずexpiredに実行側サーバに設置してファイル名の取得を試みる
-> 最後にhiddenパラメタに格納された1ファイルのみ表示される
=> 昇順、降順で一番最後に取得したファイル名のみ取得javascript形式での動作は不可能と判断し別の手段を考える
PHPファイルを再度ストレージ側サーバに配置
-> CSVファイル名を取得した後、「ファイル名,ファイル名,…」の形式で表示
されるようにする()
=> cURLで表示文字列を取得しCSV形式で読み込む
-> 全CSVファイル名を一覧表示させることができた -
感想
- 思わぬところで時間を使ったため、少し強引な方法になってしまった。
後でもう少し簡単に一覧として取れる方法がないか探してみたい。
この機能は本サービスに組み込む可能性があるため、できる限り
今回のような強引な実装は減らせるようにしていきたい。
2017年12月11日
-
作業内容
- CSV->XLSX変換パラメータの配列化
Controllerを通してパラメータを渡す際に、一つ一つ別の名前を付けて
送信しており、渡された先でPOSTされたパラメータとそれ以外の変数の
見分けがつかない状態になっていた。
これを解決するため、public function xxxxx(Request $request){ $posted = $request->post_param; $param = []; if (file_exists($csvName)){ $param['aaaaa'] = $posted; $param['bbbbb'] = FALSE; return view('ooooo', ['param' => $param]); } else { $param['bbbbb'] = TRUE; return view('ooooo', ['param' => $param]); } }
$param[]
のように配列を用意しパラメータをまとめることで
見分けをつきやすくし、POST先でも$judge = $param['bbbbb'];
というように、渡されたパラメータであることを判別できるようにソースを
書き換えた。
気が付くと変数がぐちゃぐちゃになっていることが多々あるため、気を付けて
いきたい。 -
感想
- 代表から英語力についての話があった。
英語はたまに海外のニュースを見たりしているため
読んだり聞いたりする分には苦手ではないが、いざ
英語を使って会話するとなると非常に難しいと感じる。udemy等のオンライン講座を利用するか検討している
とのことだが、英語による講座がほとんどであるため、
そのためにある程度英語力をつけておく必要がある。
英語を本格的に学習するのは高校以来だが、真剣に
取り組んでいきたい。