🧷

CakePHPでCSVエクスポートをコンポーネント化

2022/08/05に公開

コードと解説

CSVコンポーネント
CsvComponent.php
<?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()でクローズされていないファイルを指定する必要がある

▽参照元
https://www.php.net/manual/ja/function.fputcsv.php

 

rewind($filePointer);

・引数のファイル位置指示子を、 ファイルストリームの先頭にセットする
・引数のファイルポインタはfopen()で正常にオープンされたファイルを指定する必要がある

▽参照元
https://www.php.net/manual/ja/function.rewind.php

 

$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()で正常にオープンされたファイルを指定する必要がある

▽参照元
https://www.php.net/manual/ja/function.fclose.php

 

$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をセットしない限り、アクションの後に自動的に描画メソッドを呼び出すが、今回は再描画してほしくないため設定する

▽参照元
https://book.cakephp.org/3/ja/controllers.html

使い方

// 配列データを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","ローマ","ディバラ"

参考

Discussion