Cakephp 3.x Exception render - different layout - cakephp

This document shows how to assign layout for error:
$this->layout = 'my_error';
http://book.cakephp.org/3.0/en/development/errors.html#exception-renderer
But i've 2 different layouts for frontend and backend. When NotFoundException is thrown, i want to assign different layout accordingly.
How can i do that? Please help me.

Just check appropriate criteria and set layout accordingly in your template. For e.g.
if ($this->request->param('prefix') === 'admin') {
$this->layout = 'admin';
} else {
$this->layout = 'default';
}

As #ADmad suggest.
Do check the request to identify frontend or backend inside error400.ctp file (you may have another error file, just is a example).
In my case use admin prefix, i did like this:
if (!empty($this->request->params['prefix']) && $this->request->params['prefix'] == 'admin') {
$this->layout = 'Admin/default';
$app = [
'App.imageBaseUrl' => 'admin/img/',
'App.cssBaseUrl' => 'admin/css/',
'App.jsBaseUrl' => 'admin/js/'];
Configure::write($app);
} else {
$this->layout = 'Frontend/default';
}

If you want to re-set layout you have to re-render the action, a redirect would be the most easy solution here. You could also use an AppView.
$view = new AppView();
$view->set($variables);
$view->render();
http://book.cakephp.org/3.0/en/views.html#the-app-view

Related

CakePHP 3.4.2 Testing POST's response always returning NULL

i'm currently testing an app that simply searches a record by the given id.
It works fine but the testing refuses to return the response in the code. Strangely it is ONLY shown in the CLI.
I'm using phpunit provided by cakephp:
"phpunit/phpunit": "^5.7|^6.0"
Here is the conflicting code:
$this->post('/comunas/findByBarrio',[
'barrio_id'=>1
]);
var_dump($this->_response->body());die(); //This is just a test which always returns NULL... while the CLI shows the actual response, which is a JSON.
Also the same problem occurrs while doing GET or POST to any other action.
But here is the targeted controller's code:
public function findByBarrio()
{
$this->autoRender = false;
if ($this->request->is('POST'))
{
$data = $this->request->getData();
if (!empty($data['barrio_id']))
{
$this->loadModel('Comuna');
$barrio_id = $data['barrio_id'];
$comuna = $this->Comuna->find('list',['conditions' => ['barrio_id'=>$barrio_id]])
->hydrate(false)
->toArray();
if ($comuna)
{
echo json_encode($comuna);
}
else
{
throw new NotFoundException('0');
//echo 0; //Comuna no encontrada para el barrio recibido
}
}
else
{
echo -1;
}
}
}
Thank you in advance!
UPDATE 1: I've only managed to get the output by using "ob_start()" and "ob_get_clean()" around the "$this->post" method. I wish there were a cleaner way though...
UPDATE 2: Now it's working! Just by using the PSR-7 compliant interface. Thank you!
Here is the corrected controller:
public function findByBarrio()
{
$this->autoRender = false;
$this->response = $this->response->withType('json'); //CORRECTION
if ($this->request->is('POST'))
{
$data = $this->request->getData();
if (!empty($data['barrio_id']))
{
$this->loadModel('Comuna');
$barrio_id = $data['barrio_id'];
$comuna = $this->Comuna->find('list',['conditions' => ['barrio_id'=>$barrio_id]])
->hydrate(false)
->toArray();
if ($comuna)
{
$json = json_encode($comuna);
$this->response->getBody()->write($json); //CORRECTION
}
else
{
//Comuna no encontrada para el barrio recibido
$this->response->getBody()->write(0); //CORRECTION
}
}
else
{
//No se recibió el barrio
$this->response->getBody()->write(-1); //CORRECTION
}
}
return $this->response; //CORRECTION
}
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

CakePHP3 Render View to a Variable

I want to render the view to a variable without directly sending it to the browser. I used to do it with cakephp2.*. But, I cannot figure out how to do it in CakePHP3. Could you please tell me how to do it?
ViewBuilder was introduced in CakePHP 3.1 and handles the rendering of views. When ever I want to render to a variable I always go look at how send email works.
From a controller:
function index() {
// you can have view variables.
$data = 'A view variable';
// create a builder (hint: new ViewBuilder() constructor works too)
$builder = $this->viewBuilder();
// configure as needed
$builder->layout('default');
$builder->template('index');
$builder->helpers(['Html']);
// create a view instance
$view = $builder->build(compact('data'));
// render to a variable
$output = $view->render();
}
For Ajax request/response, I use this:
public function print(){
if ($this->request->is('ajax')) {
$data = $this->request->getData();
$builder = $this->viewBuilder()
->setTemplatePath('ControllerName')
->setTemplate('print');
->setLayout('ajax');
->enableAutoLayout(false);
$view = $builder->build(compact('data'));
$html = $view->render();
$res = ['html' => $html];
$this->set('response',$res);
$this->set("_serialize",'response');
}
}
And the print.ctp is under Template/ControllerName

Why are cookies not saving in Cakephp?

This is driving me crazy. In my AppController, I have the following:
public function beforeFilter() {
$this->Cookie->name = 'MyCookie';
$this->Cookie->time = '1 year';
$this->Cookie->domain = 'http://mydomain.com';
$firstVisit = $this->Cookie->read('foo');
if ( empty($firstVisit) ) {
$this->set('firstVisit', true);
$this->Cookie->write('foo', 'true');
} else {
$this->set('firstVisit', false);
}
}
This seems like it should work, but nothing is returned and the cookie is completely blank.
What could possible be preventing Cake from actually saving the Cookie?
The cookies are not set until the View is rendered. Maybe you do not have a view for your controller?
The http:// caused it to break. Removing that fixed the problem.

Cakephp multiple views per controller

I have a controller which has 3 functions. I wish to show 3 different views and layouts in each function depending on whether the user comes from mobile, website or facebook. I am passing in already where the user is coming from.
I am unsure how I would then show a particular view and layout for each. Here is some code that I started to do to change the layout. I have the views in a folder called res.
function availability() {
if ($_REQUEST['from'] == 'facebook') {
$this->layout = 'facebook';
print_r ('face');
}elseif ($_REQUEST['from'] == 'website'){
$this->layout = 'website';
print_r ('web');
}elseif ($_REQUEST['from'] == 'mobile'){
$this->layout = 'mobile';
print_r ('mobile');
};
}
Use $this->render() to change the view.
$this->layout = 'facebook';
$this->render( 'res/facebook' );
You could also put all the views for different layouts to their own folders and set the viewpath so that you don't have to choose the views manually in each function:
function beforeFilter() {
parent::beforeFilter();
$this->viewPath = $_REQUEST[ 'from' ];
}
Now the view for action "availability" for the Facebook layout is fetched from facebook/availability.ctp.

Cakephp - how to make error pages have its own layouts?

I wanna have a different layout for the page not found 404 page. How can i set a different layout for that page?
Savant from the IRC helped me out and he suggest in using beforeRender(){} in the app_controller
// Before Render
function beforeRender() {
if($this->name == 'CakeError') {
//$this->layout = 'error';
}
}
CakeError is a catchAll for errors :D
In CakePHP 2.2.2 I changed the ExceptionRenderer in core.php with my own, like this:
app/Config/core.php:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'MyExceptionRenderer', // this is ExceptionRenderer by default
'log' => true
));
app/Lib/Error/MyExceptionRenderer.php:
App::uses('ExceptionRenderer', 'Error');
class MyExceptionRenderer extends ExceptionRenderer {
protected function _outputMessage($template) {
$this->controller->layout = 'error';
parent::_outputMessage($template);
}
}
Just you need to make layout changes in your error400.ctp file under /app/View/Errors/error400.ctp
Open that file and set layout by
<?php $this->layout=''; //set your layout here ?>
better to create an error.php file in your app folder
class AppError extends ErrorHandler {
function error404($params) {
$this->controller->layout = 'error';
parent::error404($params);
}
}
so you can avoid the if-testing at EVERY page render that savants' solution introduces
My solution for CakePHP 2.3
Change the ExceptionRenderer in core.php to use your own renderer.
app/Config/core.php:
Configure::write('Exception', array(
'handler' => 'ErrorHandler::handleException',
'renderer' => 'MyExceptionRenderer',
'log' => true
));
app/Lib/Error/MyExceptionRenderer.php:
App::uses('ExceptionRenderer', 'Error');
class MyExceptionRenderer extends ExceptionRenderer
{
/**
* Overrided, to always use a bare controller.
*
* #param Exception $exception The exception to get a controller for.
* #return Controller
*/
protected function _getController($exception) {
if (!$request = Router::getRequest(true)) {
$request = new CakeRequest();
}
$response = new CakeResponse(array('charset' => Configure::read('App.encoding')));
$controller = new Controller($request, $response);
$controller->viewPath = 'Errors';
$controller->layout = 'error';
return $controller;
}
}
The advantage to this approach is that it ensures any exceptions thrown from AppController don't cause an endless loop when rendering the exception. Forces a basic rendering of the exception message every time.
This simplest way I know of is to create this function in your AppController:
function appError($method, $messages)
{
}
You can then do whatever you want with the error, display it however you like, or not display it at all, send an email etc.. (I'm not sure if this method if still valid.)
There is also an option of creating app_error.php in your app root, with class AppError extends ErrorHandler in it, which enables you to override all kinds of errors. But I haven't done this yet, so I can't tell you more about it.
See cake/libs/error.php and cake/libs/object.php and of course The Book for more info.
Edit: Forgot to mention, once you caught the error, there's nothing preventing you to - for example - store the error in session, redirect to your "error handling controller", and then display it in your controller however you want.

Resources