質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
CSV

CSV(Comma-Separated Values)はコンマで区切られた明白なテキスト値のリストです。もしくは、そのフォーマットでひとつ以上のリストを含むファイルを指します。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

CakePHP

CakePHPは、PHPで書かれたWebアプリケーション開発用のフレームワークです。 Ruby on Railsの考え方を多く取り入れており、Railsの高速性とPHPの機動性を兼ね備えています。 MVCやORMなどを「規約優先の考え方」で利用するため、コードを書く手間を省くことができます。 外部のライブラリに依存しないので、単体での利用が可能です。

Q&A

解決済

2回答

2987閲覧

[CakePHP3] JSON形式でデータ受信後、HTML や CSV 出力したい

xhashi84x

総合スコア14

CSV

CSV(Comma-Separated Values)はコンマで区切られた明白なテキスト値のリストです。もしくは、そのフォーマットでひとつ以上のリストを含むファイルを指します。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

CakePHP

CakePHPは、PHPで書かれたWebアプリケーション開発用のフレームワークです。 Ruby on Railsの考え方を多く取り入れており、Railsの高速性とPHPの機動性を兼ね備えています。 MVCやORMなどを「規約優先の考え方」で利用するため、コードを書く手間を省くことができます。 外部のライブラリに依存しないので、単体での利用が可能です。

0グッド

0クリップ

投稿2019/01/25 02:09

前提・実現したいこと

CakePHP3 で JavaScript から JSON 形式のデータを送信し、Controller で JSON 形式データを使って処理した結果をテンプレート(ctp)で HTML や CSV 出力したいのですが、テンプレートが呼ばれません。

コントローラーのビューを確認($this->viewClass)したところ、ビューが JosnView となっているためだと思われます。
単純な POST 形式での送信受信では viewClass は空でした。

どのよにすれば Json 形式で送受信したデータをテンプレートを使って HTML や CSV 形式で応答できるのでしょうか?

補足情報(FW/ツールのバージョンなど)

CentOS 7
PHP 5.6.38
CakePHP 3.2.12

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

tacsheaven さん

有難う御座いました。
ご支援の頂きましたことで、CSV ファイル出力が出来るようになりました。

ポイントしては、
1) Controller のアクション内で

$this->viewBuilder()->className('');

  する
2) Controller#beforeRender で Conten-Type を設定する
3) ajax の受信データの形式(dataType)を適切に設定(HTML: 'html', CSV: 'text' etc...)する
4) ajax で CSV ファイル形式で受信したデータを元に Download 内部リンクを生成&Clickする
かと思います。

◆JavaScript

function csvExport() { var url = 'http://?????/XXXXX/csvExport/' + param; var obj = {key1:value1,key2:value2, etc...}; var jsonData = JSON.stringify(obj); var ajaxInfo = { type: 'POST', dataType: 'text', // Notes: 受信(レスポンス)データのデータ形式を 'text' にする data: jsonData, processData: false, contentType: 'application/json', timeStamp: new Date().getTime() }; $.ajax( url, ajaxInfo ).done(function( data ) { // Notes: // 受信した CSV ファイルをダウンロードできるようにする // (Excel でも文字コード: UTF-8 で開けるように BOM を設定) let bom = new Uint8Araay([0xEF, 0xBB, 0xBF]); let downloadData = new Blob([bom, data], {type: 'text/csv'}); let fileName = 'output.csv'; if (window.navigator.msSaveBlob) { window.navigator.msSaveBlob(downloadData, fileName); } else { let downloadUrl = (window.URL || window.webkitURL).createObjectURL(downloadData); let link = document.createElement('a'); link.href = downloadUrl; link.download = fileName; link.click(); (window.URL || window.webkitURL).revokeObjectURL(downloadUrl); } }).fail(function(jqXHR, textStatus, errorThrown) { window.alert(jqXHR.responseText); }); }

◆Controller

class XXXXXController extends AppController { public $helpers = array('Csv'); public function initialize() { parent::initialize(); $this->loadComponent('Flash'); $this->loadCompanent('RequestHandler'); } public function beforeRender(Event $event) { // Notes: // RequestHandlerComponet#beforeRender 内で Content-Type に関るする // 処理が行われるため、その後に呼ばれるController#beforeRenderを // override し、Content-Type を text/csv に上書きする $this->response->type('csv'); $this->response->download($fileName); } public function csvExport($param) { // JSON 形式データを取得 $data = $this->request->data(); // key: value で各データ取得しながら、CSV 元データを生成 $val = $data['[key]'); $csvRows = array(['item1-1', 'item1-2', etc ...], ['item2-1', 'item2-2', etc...], etc...); $fileName = 'output.csv'; ... $this->set('csvRows', csvRows); $this->set('fileName', $fileName); $this->viewBuilder()->className(''); $this->viewBuilder()->layout(false); } }

◆XXXXX/ajax/csv_export.ctp

<?php $this->Csv->setFilename($fileName); foreach($csvRows as $row) { $this->Csv->addRow($row); } echo $this->Csv->render(false);

◆CsvHelper.php

<?php namespace App\View\Helper; use Cake\View\Helper; class CsvHelper extends Helper { private $delimiter = ','; private $enclosure = '"'; private $filename = 'Export.csv'; private $line = array(); private $buffer = null; public function initialize(array $config) { parent::initialize($config); $this->clear(); } public function __destruct() { $this->closeBuffer(); } public function clear() { $this->closeBuffer(); $this->line = array(); $this->buffer = fopen('php://temp/maxmemory:'.(5*1024*1024), 'r+'); } public function setDelimiter($param) { $this->$delimiter = $param; } public function addField($value) { $this->line[] = $value; } public function endRow() { $this->addRow($this->line); $this->line = array(); } public function addRow($row) { fputcsv($this->buffer, $row, $this->delimiter, $this->enclosure); } public function setFilename($filename) { $this->filename = $filename; if (strtolower(substr($this->filename, -4)) != '.csv') { $this->filename .= '.csv'; } } public function render($outputHeaders = true, $toEncoding = null, $fromEncoding = "auto") { if ($outputHeaders) { if (is_string($outputHeaders)) { $this->setFilename($outputHeaders); } $this->renderHeaders(); } rewind($this->buffer); $output = stream_get_contents($this->buffer); if ($toEncoding) { $output = mb_convert_encoding($output, $toEncoding, $fromEncoding); } return $output; } private function renderHeaders() { // Notes: // ログで // Warning: Warning (2): Cannot modify header information - headers already sent by // (output started at /var/www/html/vendor/cakephp/cakephp/src/Error/Debugger.php:742) in // [/var/www/html/src/View/Helper/CsvHelper.php, line 104] // となるため、コメントアウト // header("Content-Type: text/csv"); // header("Content-Disposition: attachment; filename=".$this->filename); } private function closeBuffer() { if (null != $this->buffer) { fclose($this->buffer); $this->buffer = null; } } }

投稿2019/01/29 09:35

xhashi84x

総合スコア14

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

CakePHP の RequestHandlerComponent の動作により、リクエストの Accept ヘッダの内容によって自動的にビューのクラスが変わります。
※Accept: application/json ならば自動的に jsonView になる

ビューを展開する前(controller のアクション内)で、$this->viewBuilder() の設定を変更してやればビュークラスを入れ替えることができます。

PHP

1$this->viewBuilder()->className('');

でできたりしそうですが。(環境がないので試していませんが)

投稿2019/01/25 03:07

tacsheaven

総合スコア13703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xhashi84x

2019/01/25 05:36

tacsheaven さん ご回答有難う御座いました。 ご回答の通り controller のアクション内で ``` $this->viewBuilder()->className(''); ``` を行ったところ、ctp が呼ばれるようになりました。 しかし、Content-Type: application/json となってしまい JSON 形式での応答になってしまいます。 ctp 内で PHP の標準 API:header 関数で Content-Type を指定('text/csv')しています。 (CSV 出力のため header 関数で Content-Type + Content-Dispotition を指定しています)
tacsheaven

2019/01/25 05:43

逆に JavaScript からの呼び出し時に dataType: html にしたら(これでAccept: が変わるはず)ダメなんでしょうか?
xhashi84x

2019/01/28 07:18

tacsheavcen さん ご回答有難う御座います。 JavaScript で dataType: html を指定することで Cake\Network\Response の _contentType: 'text/html' となりました。 JSON リクエスト -> HTML レスポンスはこれで問題ないかと思います。 そこで、CSV 出力のため dataType: csv としてみましたが、最終的なブラウザ側の出力結果として期待した結果(今回は CSV ダウンロード)となりません。 (ログで ctp が呼ばれ、Response の _contentType が 'text/html' _body が生成した CSV 形式のデータとなっていることは確認しています) ソースコードを添付させていただきますので、もし、お気づきの点等ありましたら、ご教示いただければ幸いです。 ◆JavaScript ``` function csvExport() { var url = 'http://?????/XXXXX/csvExport/' + param; var obj = {key1:value1,key2:value2, etc...}; var jsonData = JSON.stringify(obj); var ajaxInfo = { type: 'POST', dataType: 'csv', data: jsonData, processData: false, contentType: 'application/json', timeStamp: new Date().getTime() }; $.ajax(url, ajaxInfo); } ``` ◆Controller ``` class XXXXXController extends AppController { public $helpers = array('Csv'); public function initialize() { parent::initialize(); $this->loadComponent('Flash'); $this->loadCompanent('RequestHandler'); } public function csvExport($param) { // JSON 形式データを取得 $data = $this->request->data(); // key: value で各データ取得しながら、CSV 元データを生成 $val = $data['[key]'); $csvRows = array(['item1-1', 'item1-2', etc ...], ['item2-1', 'item2-2', etc...], etc...); $fileName = 'output.csv'; ... $this->set('csvRows', csvRows); $this->set('fileName', $fileName); $this->viewBuilder()->className(''); $this->viewBuilder()->layout(false); $this->response->type('csv'); $this->response->download($fileName); } } ◆XXXXX/ajax/csv_export.ctp ``` <?php $this->log('********** csv_export.ctp が呼ばれている!!', 'debug'); // true: MacOS をサポートする $this->Csv->clear(true); $this->Csv->setFilename($fileName); foreach($csvRows as $row) { $this->Csv->addRow($row); } echo $this->Csv->render(true, 'uft-8'); ``` ◆CsvHelper.php ``` <?php namespace App\View\Helper; use Cake\View\Helper; class CsvHelper extends Helper { private $delimiter = ','; private $enclosure = '"'; private $filename = 'Export.csv'; private $line = array(); private $buffer = null; private $supportMacOS = false; public function initialize(array $config) { parent::initialize($config); $this->clear(false); } public function __destruct() { $this->closeBuffer(); } public function clear($supportMacOS = false) { $this->closeBuffer(); $this->line = array(); $this->buffer = fopen('php://temp/maxmemory:'.(5*1024*1024), 'r+'); if ($supportMacOS) { // MacOS サポート時は UTF-8 の BOM を先頭に付与 $bom = chr(0xEF) . chr(0xBB) . chr(0xBF); fputs($this->buffer, $bom); } $this->supportMacOS = $supportMacOS; } public function setDelimiter($param) { $this->$delimiter = $param; } public function addField($value) { $this->line[] = $value; } public function endRow() { $this->addRow($this->line); $this->line = array(); } public function addRow($row) { fputcsv($this->buffer, $row, $this->delimiter, $this->enclosure); } public function setFilename($filename) { $this->filename = $filename; if (strtolower(substr($this->filename, -4)) != '.csv') { $this->filename .= '.csv'; } } public function render($outputHeaders = true, $toEncoding = null, $fromEncoding = "auto") { if ($outputHeaders) { if (is_string($outputHeaders)) { $this->setFilename($outputHeaders); } $this->renderHeaders(); } if ($this->supportMacOS) { $toEncoding = 'utf-8'; } rewind($this->buffer); $output = stream_get_contents($this->buffer); if ($toEncoding) { $output = mb_convert_encoding($output, $toEncoding, $fromEncoding); } return $output; } private function renderHeaders() { // Notes: // ログで // Warning: Warning (2): Cannot modify header information - headers already sent by // (output started at /var/www/html/vendor/cakephp/cakephp/src/Error/Debugger.php:742) in // [/var/www/html/src/View/Helper/CsvHelper.php, line 104] // となるため、コメントアウト // header("Content-Type: text/csv"); // header("Content-Disposition: attachment; filename=".$this->filename); } private function closeBuffer() { if (null != $this->buffer) { fclose($this->buffer); $this->buffer = null; } } } ```
tacsheaven

2019/01/28 08:27

結果を CSV で返す、と、結果を CSV ファイルで返す、は異なりますよ? (CSVに限らず)ファイルを返す場合は、response のヘッダでファイルであることを明記してやらねばなりません(そうすればブラウザが適切に処理してくれます)。 CSV の場合は、 Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Disposition: atachment; filename={ファイル名} を出力してやれば、それなりに解釈してくれますよ。 ※Content-Type: text/csv だと、テキストとして表示してしまいます
xhashi84x

2019/01/28 13:10

tacsheaven さん ご多忙のところ、ご確認とご教示を頂き、有難う御座います。 controller のアクション内で、 ``` $this->response->type('xlsx'); ``` を行ったのですが、CSV ファイル出力となりません。 設定したタイミングでは Response#_contentType が、  application/vnd.openxmlformats-officedocument.spreadsheetml.sheet になるのですが、afterFilter で Response#_contentType を確認すると  text/html になっています。 原因(推測)としては、RequestHandlerComponet で $_ext が 'ajax' となっており、これが RequestHandlerComponet#respondAs で 'text/html' となるためではないかと思われます。 → Response#_mimeTypes が   ...<中略>... [json] => application/json   ...<中略>... [csv] = Array ( [0] => text/csv [1] => application/vnd.ms-excel )   ...<中略>... [xlsx] => application/vnd.openxmlformats-officedocument.spreadsheetml.sheet   ...<中略>... [ajax] => text/html  であるため。 この RequestHandlerComponet#$_ext: 'ajax' を 'csv', 'xlsx' に出来れば解決できると推測し、解析を進めていますが、解決に至っておりません。 お手数をお掛けして申し訳ございませんが、認識の齟齬や何か他に情報等が御座いましたらご教示いただければ幸いです。 P.S ご教示頂いた  Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet ですが、Excel 2007 以降のファイル形式であることを示すものと理解しました。 ご教示いただいた Content-Type では Excel が起動し、CSV データが起動した Excel へ展開されると推測しましが齟齬ありますでしょうか。 また、Content-Type: text/csv は "CSV ファイル" であることを明示し、Content-Disposition: atachment; filename={ファイル名} で Client側での出力ファイル名(default)を明示していると理解しており、こちらの指定でも問題ないと理解していましたが、齟齬ありますでしょうか。 Client側では、text/csv では ファイルダイアログが表示され、{ファイル名}が初期値で表示されると理解しています。 ※過去に Java で CSV ファイル出力を実装した際、この指定で CSV ファイル出力できました。
xhashi84x

2019/01/29 03:11

恥ずかしながら私の知識不足のようです。 ajax でデータを送信した場合、受信したデータについては JavaScript で何等かの処理(例:HTML 生成)を行わなけばならず、受信したデータがファイル(CSV, PDF etc...)の場合は JavaScript 内で受信したファイル形式のデータに対するリンクを生成し、リンクを自動クリックするようにしないとならないのですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問