WHAT'S NEW?
Loading...

CakePHP1.2で内容をカスタマイズしたCSVをダウンロードさせる【3/3】

さて、正しくCSVファイルがダウンロードされただろうか。
今回はCSVファイルのカスタマイズなので、ここまでで終わりではない。

このようなCSVファイルを
id item_id name price created
1 1 あきたこまち 1980 2010-01-01 24:59:59
2 2 ささにしき 2034 2010-01-01 24:59:59
3 3 こしひかり 1680 2010-01-01 24:59:59
4 1 あきたこまち 1980 2010-01-01 24:59:59
5 3 こしひかり 1680 2010-01-01 24:59:59

このようにしたいわけだ。

ID 商品名 単価 個数 合計額
1 あきたこまち 1980 2 3,960円
2 ささにしき 2034 1 2,034円
3 こしひかり 1680 2 3,360円

それではやってみよう。




■コントローラのカスタマイズ
まずはコントローラ側で、各商品ごとに集計させるところからはじめる。
基本的には$conditions配列に条件を書けばいいが、まずは素のSQLを指定してみる。
※MySQLを使用
  1. select *, sum(price) as total , count(item_id) as amount  
  2. from histories as History  
  3. group by History.item_id   
このSQLをphpMyAdminなどで直接実行してみると、データベースフィールドの最後にtotalとamountフィールドが追加されたはずだ。
totalには集計された金額、amountには商品の個数が設定されている。

id item_id name price created total amount
1 1 あきたこまち 1980 2010-01-01 23:59:59 3960 2
2 2 ささにしき 2034 2010-01-01 23:59:59 2034 1
3 3 こしひかり 1680 2010-01-01 23:59:59 3360 2

この中から、item_id、name、price、total、amountを使用すればよい。
つまり、コントローラ側としては任意のフィールドを指定してfindすればよい、と言うことになる。
$conditions にはfirldsという設定があるので、これでフィールド名を設定してみる。
  1. $conditions = array(  
  2.   'conditions' => array(  
  3.     'History.created >' => '2010-01-01 23:59:59',  
  4.   ) ,  
  5.   'fields' => array(  
  6.     'History.item_id',  
  7.     'History.name',  
  8.     'History.price',  
  9.     'count(History.item_id) as amount',  
  10.     'sum(History.price) as total',  
  11.   ),  
  12.   'group' => array(  
  13.     'History.item_id',  
  14.   ),  
  15. );  
  16. $this->set('histories'$this->History->find('all'$conditions));  
これを試しにブラウザで表示させると、こうなる。

Id Name Price Amount Total
1 あきたこまち 1980 2 3960
2 ささにしき 2034 1 2034
3 こしひかり 1680 2 3360

これで取得したいデータはそろった。
次は項目名を任意の文字列に変更する。


■ビューのカスタマイズ
現在ビュースクリプトであるcsv.ctpには、細かい設定などが一切されてない状態になっている。
しかしCSVヘルパーにはいくつか便利な機能が含まれているので、それらを使ってカスタマイズすることにする。

まず、項目名はデータベースフィールドのCamelCase方式で表示されてしまっているので、これらを任意の文字列にするときのアルゴリズムを考える。

ちょっとひねった考え方をすると、データベースフィールド名と任意の文字列をセットした、連想配列にして出力すればよいと思われがちだが、もっと簡単に出来る方法がある。
  1. 項目名を非表示設定にする
  2. 1行目を任意の配列として挿入する
これでOKだ。

というわけで、まずは項目名を非表示にする設定をする。
[/Path/To/CakePHP/App/views/histories/csv.ctp]
  1. $csv->addGrid($histories);  
  2. $csv->setFilename('支払.csv');  
  3. echo mb_convert_encoding($csv->render(), 'SJIS''UTF-8');  
最初のメソッドであるaddGridだが、引数をいくつか指定することが可能になっている。
以下のように2番目の引数にfalse を指定すると、項目名が表示されなくなる。
  1. $csv->addGrid($histories, false);  
これで第1の条件はクリアした。

次に、任意の文字列を表示させる。
任意の文字列は配列で、以下の様な内容だ。
  1. $columns = array(  
  2.   'ID' ,  
  3.   '商品名',  
  4.   '単価',  
  5.   '個数',  
  6.   '合計額',  
  7. );  
これを全レコードの先頭に追加できれば良い。

CSVヘルパーでは、全データはaddGridが呼ばれて初めてデータがそろう仕組みになっている。
つまり、addGridを呼ぶ前に、先頭に1行だけ追加できれば良い。
そのためのメソッドが用意されている。そのまんまの名前で、addRowだ。これをaddGridの前に追加する。
  1. $csv->addRow($columns);  

最終的にcsv.ctpは以下のようになったはずだ。
[/Path/To/CakePHP/App/views/histories/csv.ctp]
  1. $columns = array(  
  2.   'ID',  
  3.   '商品名',  
  4.   '単価',  
  5.   '個数',  
  6.   '合計額',  
  7. );  
  8. $csv->addRow($columns);  
  9. $csv->addGrid($histories, false);  
  10. $csv->setFilename('支払.csv');  
  11. echo mb_convert_encoding($csv->render(), 'SJIS''UTF-8');  
これで、任意の文字列を項目名とした、任意のフィールドを取得するとが出来る。
しかしこれだけでは終わらない。

■コントローラのカスタマイズ
実はfindで取得したデータをダンプしてみると、非常に厄介な構造になっているのが分かると思う。
ためしにcsvアクションを、次のように編集してみると良い。
  1. //Configure::write('debug', 0); // デバッグ情報不要    
  2.   //$this->layout = false; // layout 不要    
  3.   $conditions = array(  
  4.     'conditions' => array(  
  5.       'created <' => ’2010-01-01’ 23:59:59',  
  6.     ),  
  7.     'fields' => array(  
  8.       'History.item_id',  
  9.       'History.name',  
  10.       'History.price',  
  11.       'count(History.item_id) as amount',  
  12.       'sum(History.price) as total',  
  13.     ),  
  14.     'group' => array(  
  15.       'History.item_id',  
  16.     ),  
  17.   );  
  18.   $histories = $this->History->find('all'$conditions);  
  19.   pr($histories);exit(0);  
  20.   //$this->set('histories', $this->History->find('all', $conditions));   

最初の2行、最後の1行をコメントアウトし、findした内容をprで画面にダンプする設定だ。
これでブラウザでcsvアクションの実行結果を見ることが出来る。
ためしに見てみると、
  1. Array  
  2. (  
  3.     [0] => Array  
  4.         (  
  5.             [History] => Array  
  6.                 (  
  7.                     [item_id] => 1  
  8.                     [name] => あきたこまち  
  9.                     [price] => 1980  
  10.                 )  
  11.             [0] => Array  
  12.                 (  
  13.                     [amount] => 2  
  14.                     [total] => 3960  
  15.                 )  
  16.   
  17.         )  
  18.     [1] => Array  
  19.         (  
  20.             [History] => Array  
  21.                 (  
  22.                     [item_id] => 2  
  23.                     [name] => ささにしき  
  24.                     [price] => 2034  
  25.                 )  
  26.             [0] => Array  
  27.                 (  
  28.                     [amount] => 1  
  29.                     [total] => 2034  
  30.                 )  
  31.   
  32.         )  
  33.     [2] => Array  
  34.         (  
  35.             [History] => Array  
  36.                 (  
  37.                     [item_id] => 3  
  38.                     [name] => こしひかり  
  39.                     [price] => 1680  
  40.                 )  
  41.             [0] => Array  
  42.                 (  
  43.                     [amount] => 2  
  44.                     [total] => 3360  
  45.                 )  
  46.         )  
  47. )  
なんと言う事か、 item_id、name、priceと兄弟要素として並んでいると思ったamountとtotalが、別の親をインデックスに持つ配列になってしまっている。この場合、[0][amount]、[0][total]だ。

これは非常に迷惑な仕様だ。

先ほどの完成形のままだと、amountとtotalがnullになってcsvファイルに吐き出されてしまう。
なので[History][amount]と[History][total]というようになってないとまずい。
どうにかしないといけない。


ここら辺はビュースクリプトでやるのはおかしいので、コントローラでどうにかして兄弟にしておくこととする。
非常に遺憾なロジックだが、一度配列に代入した全データを総なめし、配列に入れなおすことにする。
  1. $histories$this->History->find('all'$conditions);  
  2. for($i=0; $i<count($histories);$i++) {  
  3.   $datas[$i]['History'] = am($histories[$i]['History'], ($histories[$i][0]));  
  4. }  
これで、$datasをビューに渡せば、以下の様な配列がわたることになる。
  1. Array  
  2. (  
  3.     [0] => Array  
  4.         (  
  5.             [History] => Array  
  6.                 (  
  7.                     [item_id] => 1  
  8.                     [name] => あきたこまち  
  9.                     [price] => 1980  
  10.                     [amount] => 2  
  11.                     [total] => 3960  
  12.                 )  
  13.         )  
  14.     [1] => Array  
  15.         (  
  16.             [History] => Array  
  17.                 (  
  18.                     [item_id] => 2  
  19.                     [name] => ささにしき  
  20.                     [price] => 2034  
  21.                     [amount] => 1  
  22.                     [total] => 2034  
  23.                 )  
  24.         )  
  25.     [2] => Array  
  26.         (  
  27.             [History] => Array  
  28.                 (  
  29.                     [item_id] => 3  
  30.                     [name] => こしひかり  
  31.                     [price] => 1680  
  32.                     [amount] => 2  
  33.                     [total] => 3360  
  34.                 )  
  35.         )  
  36. )  

最終的に出来上がったcsvアクションは以下になる。

[/Path/To/CakePHP/App/controllers/histories_controller.php]
  1. function csv()  
  2. {  
  3.   Configure::write('debug', 0); // デバッグ情報不要  
  4.   $this->layout = false; // layout 不要  
  5.   $conditions = array(  
  6.     'conditions' => array(  
  7.       'created <' => '2010-01-01 23:59:59',  
  8.     ),  
  9.     'fields' => array(  
  10.       'History.item_id',  
  11.       'History.name',  
  12.       'History.price',  
  13.       'count(History.item_id) as amount',  
  14.       'sum(History.price) as total',  
  15.     ),  
  16.     'group' => array(  
  17.      'History.item_id',  
  18.     ),  
  19.   );  
  20.   
  21.   $histories = $this->History->find('all'$conditions);  
  22.   for($i=0; $i<count($histories);$i++) {  
  23.     $datas[$i]['History'] = am($histories[$i]['History'],  
  24.       $histories[$i][0]  
  25.     );  
  26.   }  
  27.   
  28.   $this->set('histories'$datas);  
  29. }  


結構な面倒くささだ。
ここら辺まとめてコンポーネントにしておくと便利かもしれない。
とにかくお試しあれ。

じゃぁな。