Asked  7 Months ago    Answers:  5   Viewed   44 times

Using CakePHP 3.4, PHP 7.0.

I'm attempting to do a really simple controller method to output some JSON. It is outputting "Cannot modify headers...".

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

Browser output

{"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 ...)

I fully understand why PHP complains about this. The question is why does CakePHP complain and what can I do about it?

It should be noted that CakePHP 2.x allowed this.

 Answers

24

Controllers should never echo data! Echoing data can lead to all kinds of problems, from the data not being recognized in the test environment, to headers not being able to be sent, and even data being cut off.

Doing it that way was already wrong in CakePHP 2.x, even though it might have worked in some, maybe even most situations. With the introduction of the new HTTP stack, CakePHP now explicitly checks for sent headers before echoing the response, and will trigger an error accordingly.

The proper way to send custom output was to configure and return the response object, or to use serialized views, and it's still the same in 3.x.

Quote from the docs:

Controller actions generally use Controller::set() to create a context that View uses to render the view layer. Because of the conventions that CakePHP uses, you don’t need to create and render the view manually. Instead, once a controller action has completed, CakePHP will handle rendering and delivering the View.

If for some reason you’d like to skip the default behavior, you can return a CakeNetworkResponse object from the action with the fully created response.

* As of 3.4 that would be CakeHttpResponse

Cookbook > Controllers > Controller Actions

Configure the response

Using the PSR-7 compliant interface

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

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

return $this->response;

The PSR-7 compliant interface uses immutable methods, hence the utilization of the return value of withStringBody() and withType(). In CakePHP < 3.4.3, withStringBody() is not available, and you can instead directly write to the body stream, which will not change the state of the response object:

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

Using the deprecated interface

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

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

return $this->response;

Use a serialized view

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

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

This requires to also use the request handler component, and to enable extensing parsing and using correponsing URLs with .json appended, or to send a proper request with a application/json accept header.

See also

  • Cookbook > Controllers > Controller Actions
  • Cookbook > Views > JSON and XML views
  • PHP FIG Standards > PSR-7 HTTP message interfaces
Wednesday, March 31, 2021
 
Anax
answered 7 Months ago
47

you can always use explicit paths:

/path/to/app_dir>..libCakeConsolecake bake

from your APP dir of the current cake project. this is foolprove and always works with the right cake version.

I do it this way and use a shortcut on my keyboard to print this (up to "cake") on a single key stroke.

Saturday, May 29, 2021
 
vuliad
answered 5 Months ago
56

I am considering black-listing IE6 so that only it gets the slow meta refresh and everyone else gets the fast 302.

I would do something like that. Also include a plain HTML link in the body for accessibility.

Note that some other browsers do give a similar warning about leaving an HTTPS site, but in their case it is accompanied by a (generally pre-ticked) “don't ask me again” button. So by the time they get to your site they will almost certainly have told that warning to disappear. This doesn't make the warning less pointless, but at least it alleviates the problem.

  1. The secure server sends a 302 redirect to the client

You shouldn't 302 in response to POST. A theoretical browser that took the HTTP RFC seriously might respond to that by re-POSTing the form to the new URL. (Which, ironically, would make IE6's warning about information “being retransmitted to a nonsecure site” less misleading.) Instead use “303 See other”.

Sunday, August 1, 2021
 
tedders
answered 3 Months ago
18

http://api.cakephp.org/2.4/source-class-Controller.html#209-214

Set the Controller::$ext property in your app controller to "tpl" and your're done.

Searching before asking is also always a good idea, see CakePHP View change extension

Saturday, August 21, 2021
 
Ryley
answered 2 Months ago
45

It's probably not possible without tradeoffs. Next.js has Automatic Static Optimization, so pages that can be statically exported will be exported to plain .html files. And .html files require no code execution on a server so there is no place to add a custom HTTP header.

Alternatively, you could add custom HTTP headers on every response with getServerSideProps in _app.js:

export async function getServerSideProps(context) {

  // set HTTP header
  context.res.setHeader('Content-Type', 'application/json')

  return {
    props: {}, // will be passed to the page component as props
  }
}

But having getServerSideProps would disable static optimization as all pages will be only server-side rendered.

Server-side Rendering

Thursday, October 14, 2021
 
godot
answered 1 Week ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :