CakePHPでCSVエクスポートをコンポーネント化
コードと解説
CSVコンポーネント
<?php
namespace App\Controller\Component;
use Cake\Controller\Component;
/**
* Csv component
*/
class CsvComponent extends Component
{
/**
* Default configuration.
*
* @var array
*/
protected $_defaultConfig = [];
/**
* initialize
*
* @param array $config Config.
* @access public
* @return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->controller = $this->getController();
}
/**
* CSVエクスポートする
*
* @param string $fileName file name
* @param array $header csv header
* @param array $data csv data
*/
public function export($fileName, $header, $data)
{
$fiveMBs = 5 * 1024 * 1024;
$filePointer = fopen('php://temp/maxmemory:' . $fiveMBs, 'a');
fputcsv($filePointer, $header);
foreach ($data as $row) {
fputcsv($filePointer, $row);
}
rewind($filePointer);
$csv = stream_get_contents($filePointer);
$csv = mb_convert_encoding($csv, 'SJIS-win', 'utf8');
fclose($filePointer);
$this->controller->response->withType('csv');
$this->controller->response->download($fileName);
$this->controller->response->getBody()->write($csv);
$this->controller->autoRender = false;
}
}
解説
initializeメソッド
$this->controller = $this->getController();
▼・CSVコンポーネントで呼び出し元のcontrollerのインスタンスを使用できるようにする
※ CakePHPのバージョンが3.xの場合は
$this->controller = $this->_registry->getController();
を代用してください(https://book.cakephp.org/3/ja/controllers/components.html#id9)
exportメソッド
$filePointer = fopen('php://temp/maxmemory:' . $fiveMBs, 'a');
▼・php://temp
は読み書き可能なストリームで、一時データをファイルのように保存できるラッパー
・php://temp
は基本的にデータをメモリに格納するが、定義済みの上限 (デフォルトは2MB) に達するとテンポラリファイルを使うようになっている
・php://temp
のメモリ制限を制御するには/maxmemory:NN
を追加する。このNNはメモリに保持するデータの最大量(単位はバイト)。 このサイズを超えるとテンポラリファイルを使う。今回は5MBを設定
・fopen()
の第二引数をaモードに設定することで、第一引数で指定した上記のラッパーを書き出し用のみでオープンする(ファイルが存在しない場合には作成する)。ファイルポインタはファイルの終端に置く。書き込みは、常に追記となる。
▽参照元
・https://www.php.net/manual/ja/wrappers.php.php
・https://www.php.net/manual/ja/function.fopen.php
fputcsv($filePointer, $header);
▼・fputcsv()
は、第二引数で渡された配列をCSVとしてフォーマットし、それを第一引数で指定したファイルの一番下に書き込む
・第一引数のファイルポインタには、fopen()
もしくはfsockopen()
で正常にオープンされ、かつfclose()
でクローズされていないファイルを指定する必要がある
rewind($filePointer);
▼・引数のファイル位置指示子を、 ファイルストリームの先頭にセットする
・引数のファイルポインタはfopen()
で正常にオープンされたファイルを指定する必要がある
$csv = stream_get_contents($filePointer);
▼・fopen()
で正常にオープンされたファイルに対して操作を行う。データを取得して文字列に保存する
▽参照元
・https://www.php.net/manual/ja/function.stream-get-contents.php
$csv = mb_convert_encoding($csv, 'SJIS-win', 'utf8');
▼・第一引数の文字エンコーディングを、第三引数の文字コードから第二引数の文字コードに変換する
・PHPで使用されている文字コードはデフォルトでUTF-8だが、ExcelはUTF-8に対応していないため文字化け対策としてSJIS-winに変換する
▽参照元
・https://www.php.net/manual/ja/function.mb-convert-encoding.php
・https://codelikes.com/php-csv-convert-encoding/
fclose($filePointer);
▼・オープンされたファイルポインタをクローズする
・引数のファイルポインタには、fopen()
もしくはfsockopen()
で正常にオープンされたファイルを指定する必要がある
$this->controller->response->withType('csv');
▼・withTypeはContext-Typeを指定する。今回はcsvを指定。
▽参照元
・https://book.cakephp.org/3/ja/appendices/3-4-migration-guide.html#id3
・https://book.cakephp.org/3/ja/controllers/request-response.html
$this->controller->response->download($fileName);
▼・任意のダウンロードファイル名を指定する
▽参照元
・https://book.cakephp.org/3/ja/appendices/3-4-migration-guide.html#id3
・https://book.cakephp.org/3/ja/controllers/request-response.html
$this->controller->response->getBody()->write($csv);
▼・レスポンスのボディに文字列コンテンツを挿入する
・$this->controller->response->body($csv)
でも良いが、CakePHP3.4以降では非推奨のため使用しない
・$this->response->withBody($csv);
の場合、引数はStreamInterface型じゃなきゃいけないため今回は使用しない
▽参照元
・https://book.cakephp.org/3/ja/controllers/request-response.html
・https://github.com/cakephp/cakephp/issues/10290#issuecomment-282068317
$this->controller->autoRender = false;
▼・CakePHPは$this->autoRender = false
をセットしない限り、アクションの後に自動的に描画メソッドを呼び出すが、今回は再描画してほしくないため設定する
使い方
// 配列データをCSV形式で出力する
export(string $fileName, array $header, array $data): void
パラメータ
fileName | header | data |
---|---|---|
出力するCSVのファイル名を文字列で渡す | CSVデータのヘッダーに指定したい項目を配列で渡す | CSVとして出力したいデータを配列で渡す |
例
/**
* CSVエクスポートする
*
* @param \Cake\ORM\ResultSet $array array.
* @return void
*/
protected function exportCsv($array)
{
$fileName = Time::now()->format('Ymd') . '_サンプル.csv';
$header = [
"リーグ名",
"チーム名",
"選手名",
];
$data = [
["プレミアリーグ", "マンチェスターシティ", "グリーリッシュ"],
["プレミアリーグ", "リバプール", "マティプ"],
["セリエA", "ローマ", "ディバラ"],
];
$this->loadComponent('Csv');
$this->Csv->export($fileName, $header, $data);
}
リーグ名,チーム名,選手名
"プレミアリーグ","マンチェスターシティ","グリーリッシュ"
"プレミアリーグ","リバプール","マティプ"
"セリエA","ローマ","ディバラ"
参考
- https://github.com/cakephp/cakephp/issues/10290#issuecomment-282068317
- https://book.cakephp.org/4/ja/controllers/components.html#id9
- https://book.cakephp.org/3/ja/controllers.html
- https://book.cakephp.org/3/ja/controllers/request-response.html
- https://book.cakephp.org/3/ja/appendices/3-4-migration-guide.html#id3
- https://www.php.net/manual/ja/wrappers.php.php
- https://www.php.net/manual/ja/function.fopen.php
- https://www.php.net/manual/ja/function.fputcsv.php
- https://www.php.net/manual/ja/function.rewind.php
- https://www.php.net/manual/ja/function.stream-get-contents.php
- https://www.php.net/manual/ja/function.mb-convert-encoding.php
- https://www.php.net/manual/ja/function.fclose.php
- https://qiita.com/kazu56/items/d2df449eb58e4ef9410e
- https://codelikes.com/php-csv-convert-encoding/
Discussion