How to Output Custom Http Body Contents With Cakephp 3.4 - Echoing Causes "Unable to Emit Headers" Error

How to output custom HTTP body contents with CakePHP 3.4? Echoing causes Unable to emit headers error

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 Cake\Network\Response object from the action with the fully created response.

* As of 3.4 that would be \Cake\Http\Response

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 > Request & Response Objects > Setting the Body
  • Cookbook > Views > JSON and XML views
  • PHP FIG Standards > PSR-7 HTTP message interfaces

CakePHP 3.4.2 Testing POST's response always returning NULL

Controller actions are not supposed to echo data, even though it might work in some, maybe even most situations. The correct way of outputting data that doesn't stem from a rendered view template, is to configure and return the response object, or to use serialized views.

The test environment relies on doing this properly, as it doesn't buffer possible output, but will use the actual value returned from the controller action.

The following is basically a copy from https://stackoverflow.com/a/42379581/1392379


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 Cake\Network\Response object from the action with the fully created response.

* As of 3.4 that would be \Cake\Http\Response

Cookbook > Controllers > Controller Actions

Configure the response

Using the PSR-7 compliant interface

$content = json_encode($comuna);

$this->response->getBody()->write($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 withType(). Unlike setting headers and stuff, altering the body by writing to an existing stream doesn't change the state of the response object.

CakePHP 3.4.3 will add an immutable withStringBody method that can be used alternatively to writing to an existing stream.

$this->response = $this->response->withStringBody($content);

Using the deprecated interface

$content = json_encode($comuna);

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

return $this->response;

Use a serialized view

$content = json_encode($comuna);

$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

CakePHP4 Ajax: Send from Controller to View

try something like this:

config/routes.php

$routes->prefix('Api', function (RouteBuilder $routes) {
$routes->setExtensions(['json']);
$routes->fallbacks(DashedRoute::class);
});

Controller/Api/CountriesController.php

public function getall()
{
$countries = $this->Countries->find('all', [
'contain' => ['PLZ']
]);

$data = [
'countries' => $countries,
];

$this->set(compact('data'));
$this->viewBuilder()->setOption('serialize', ['data']);
}

see if this works first:
/api/countries/getall.json

jQuery ajax call can't read json encoded data from CakePHP 3.8 (gets an empty array)

OMG...found it! Okay, that is embarrassing but I am still gonna post it as a LESSON LEARNED and as a warning to other folks: if you have a problem in the evening that you can't solve, go home and have a good sleep, start again in the morning!

Causes of the problem:

1) my calculation function was actually returning a float not a string, and I was checking for emptiness, so when it returned 0, the code was not adding the 'available_yield' property to the $crop entity (because the line of code responsible for that was also in the wrong place! should have been outside of the if block)

At this point I was still like 'alright, but I should get consistent behaviour both in the browser and in the ajax call!!!', unless...

2) I did not notice that I used a different id for the browser check and for the ajax call, so the computer was right ... :-/

Always learning ...

  • double-check every single line and debug all possible vars!
  • double-check your TEST DATA!

The version of the code that works OK:

function myFunction(){
$params = $this->getRequest()->getQueryParams();
//debug($params);
$componentReference = $params['component_reference'];
$componentTypeId = $params['component_type_id'];

$matchingCrops = $this->Crops->find()->select(['id', 'grower_name', 'bulk'])->where(['reference' => $componentReference]);

$cropsWithYieldInfo = []; //to hold modify crop
$cropsWithYieldString = '';
foreach($matchingCrops as $crop){
$availableYield = $this->Crops->calculateAvailableYield($crop->id); //returns a float not string!
if(isset($availableYield)){ //<<- that was the cause of the problem; !empty(float) will ignore 0, just check if it's set
$crop->available_yield = number_format($availableYield,1);
}
$cropsWithYieldInfo[] = $crop;
}

// debug($cropsWithYieldInfo);
// debug($matchingCrops);

$content = json_encode($cropsWithYieldInfo);

//$this->response = $this->response->withStringBody($content);
//$this->response = $this->response->withType('application/json');
$this->autoRender = false;
//return $this->response;
//more concisely
return $this->response->withType('application/json')->withStringBody($content);

}

Thanks for your time guys, you kept me focused on finding the solution.

How do you specify an HTTP status code in Cakephp?

EDIT - This question is quite old and covers different versions of the CakePHP framework. Following is a summary of which version each answer applies to. Don't forget to vote on the solution that helps most.

  • CakePHP 3.x and 4.x - using response object (Roberto's answer)
  • CakePHP 2.x - using exceptions (Brad Koch's answer) [preferred solution]
  • CakePHP 2.x - setting header only (Asa Ayers' answer)
  • CakePHP 1.x - using error handler (my other answer)
  • CakePHP 1.x - setting header only (this answer)

EDIT #2 - A more detailed answer for CakePHP 2.x has been added by Mark37.

EDIT #3 - Added solution for CakePHP. (May 2018: CakePHP 3.5 did some function renaming, solution by Roberto is still valid.)


By looking at the relevant API code from the previous comment, it seems you can call Controller::header($status) to output a header without redirection. In your case, the proper usage is most likely:

$this->header('HTTP/1.1 403 Forbidden');


Related Topics



Leave a reply



Submit