CakePHP 3将.ctp模板输出为CSV文件

时间:2019-01-03 15:08:37

标签: php csv cakephp

在CakePHP 3.5.13中,我有一个Controller动作,如下所示:

class ReportController extends AppController
{
    public function download($id, $format)
    {

    }
}

我想做的是将报表ID和格式(例如XLSX,CSV)的参数传递给download函数,例如/report/download/123/csv将获取报告123的数据并将其输出为CSV。

我在数组$data中拥有报告所需的所有数据。我想创建单独的模板(.ctp文件)以将数据设置为所需的每种格式,然后将download()函数将其发送到浏览器进行下载。

所以我对我的功能进行了如下修改:

public function download($id, $format)
{
    $data = // ... Model data in array format

    $filename = '123'; // filename for the download

    switch ($format):
        case 'csv':
            header('Content-type: text/csv');
            header('Content-Disposition: attachment; filename="'.$filename.'.csv"');

            $this->viewBuilder()->setLayout('ajax');
            $this->set('data', $data);
            $this->render('download_csv');
        break;
    endswitch;
}

这可以正确呈现我从Template/Report/download_csv.ctp获得的模板。它还可以在ajax布局中正确呈现($this->viewBuilder()->setLayout('ajax');表示它使用Template/Layout/ajax.ctp,因此不包含任何“包装” HTML)。

访问/report/download/123/csv时,浏览器会发送一个名为123.csv的文件,其中包含我请求的数据。但是,响应类型是text/html,而不是我的控制器操作中指定的text/csv

这导致文件内容格式错误-将其视为HTML而不是CSV。例如,如果我的download_csv.ctp模板中包含以下内容:

"Field 1", "123\n456",
"Field 2", "789"

它的输出如Excel中所示:

enter image description here

我猜这个问题是由于响应实际上是HTML而不是CSV,但我不确定。

我的问题是,是否可以通过这种方式使用.ctp文件,并以指定格式将它们作为下载内容输出到浏览器?

我知道使用路由和URL扩展可以做其他事情,但是这个问题与这些无关。我也不想使用插件/扩展。我想知道是否可以使用$this->render()并让Cake渲染一个模板,该模板以请求的格式发送到浏览器以供下载?

1 个答案:

答案 0 :(得分:3)

调用render()只会渲染给定的模板,并使用渲染的数据填充响应主体,而与响应将要发生的情况以及发送内容的类型无关,因此答案是是的,您可以渲染模板并将其作为下载。

Excel无法按预期导入数据,这很可能与CakePHP无关,因为CSV文件不包含任何内容类型信息。也许在导入文件时选择了错误的文本限定符(")。

也就是说,控制器不应该输出包含标头的数据!在控制器中输出数据会导致各种问题,从测试环境中无法识别的数据到无法发送的报头,甚至数据都被切断。而是使用适当的响应对象方法相应地准备下载响应,在这种情况下,您正在寻找withType()withDownload(),例如:

case 'csv':
    $this->response = $this->response->withType('csv');
    $this->response = $this->response->withDownload($filename. '.csv');

    $this->viewBuilder()->setLayout('ajax');
    $this->set('data', $data);
    $this->render('download_csv');
break;

这将添加正确的Content-TypeContent-Disposition标头。

另请参阅: