如何使用 CakePHP 3.4 输出自定义 HTTP 正文内容?回显导致“无法发出标头”错误

Posted

技术标签:

【中文标题】如何使用 CakePHP 3.4 输出自定义 HTTP 正文内容?回显导致“无法发出标头”错误【英文标题】:How to output custom HTTP body contents with CakePHP 3.4? Echoing causes "Unable to emit headers" error 【发布时间】:2017-07-11 18:04:12 【问题描述】:

使用 Cakephp 3.4、PHP 7.0。

我正在尝试做一个非常简单的控制器方法来输出一些 JSON。它正在输出“无法修改标题...”。

public function test() 
    $this->autoRender = false;
    echo json_encode(['method' => __METHOD__, 'class' => get_called_class()]);

浏览器输出

"method":"App\\Controller\\SomeController::test", "class":"App\\Controller\\SomeController"

Warning (512): Unable to emit headers. Headers sent in file=...
Warning (2): Cannot modify header information - headers already sent by (output started at ...)
Warning (2): Cannot modify header information - headers already sent by (output started at ...)

我完全理解为什么 PHP 会抱怨这一点。问题是为什么 CakePHP 会抱怨,我该怎么办?

需要注意的是,CakePHP 2.x 允许这样做。

【问题讨论】:

【参考方案1】:

CakePHP 3 有一个名为 JSON views 的东西,它允许您返回 JSON 数据。我之前没有做过任何 CakePHP,所以我不知道请求的生命周期,但值得研究一下。

【讨论】:

【参考方案2】:

控制器不应该回显数据!回显数据会导致各种问题,从测试环境无法识别数据,到无法发送标头,甚至数据被截断。

这样做在 CakePHP 2.x 中已经是错误的了,尽管它可能在某些甚至大多数情况下都有效。随着新 HTTP 堆栈的引入,CakePHP 现在在回显响应之前显式检查发送的标头,并相应地触发错误。

发送自定义输出的正确方法是配置并返回响应对象,或者使用序列化视图,在 3.x 中仍然如此。

引用自文档:

控制器动作通常使用Controller::set() 来创建视图用来渲染视图层的上下文。由于 CakePHP 使用的约定,您不需要手动创建和呈现视图。相反,一旦控制器动作完成,CakePHP 将处理渲染和交付视图。

如果出于某种原因您想跳过默认行为,您可以从带有完整创建响应的操作中返回 Cake\Network\Response 对象。

* 从 3.4 开始,这将是 \Cake\Http\Response

Cookbook > Controllers > Controller Actions

配置响应

使用符合 PSR-7 的接口

$content = json_encode(['method' => __METHOD__, 'class' => get_called_class()]);

$this->response = $this->response->withStringBody($content);
$this->response = $this->response->withType('json');
// ...

return $this->response;

符合 PSR-7 的接口使用不可变方法,因此使用了 withStringBody()withType() 的返回值。在 CakePHP withStringBody() 不可用,可以直接写入 body 流,不会改变响应对象的状态:

$this->response->getBody()->write($content);

使用已弃用的接口

$content = json_encode(['method' => __METHOD__, 'class' => get_called_class()]);

$this->response->body($content);
$this->response->type('json');
// ...

return $this->response;

使用序列化视图

$content = ['method' => __METHOD__, 'class' => get_called_class()];

$this->set('content', $content);
$this->set('_serialize', 'content');

这还需要使用请求处理程序组件,并启用扩展解析和使用附加了 .json 的相应 URL,或者发送带有 application/json 接受标头的正确请求。

另见

Cookbook > Controllers > Controller Actions Cookbook > Request & Response Objects > Setting the Body Cookbook > Views > JSON and XML views PHP FIG Standards > PSR-7 HTTP message interfaces

【讨论】:

以上是关于如何使用 CakePHP 3.4 输出自定义 HTTP 正文内容?回显导致“无法发出标头”错误的主要内容,如果未能解决你的问题,请参考以下文章

Cakephp MiddleWare 类中如何导入自定义组件?

如何在 CakePHP 中创建自定义 MySQL 查询?

CakePHP 3自定义验证:如何在编辑期间将新值与旧值进行比较?

如何将 CakePHP 2 模型关联更改为与 bindModel 和自定义查找器的深度链接?

CakePHP 3 - 自定义数据源在哪里?

在CakePHP 3中放置自定义PHP类的位置?