CakePHP 3.5 redirect missing controller exception - cakephp

I am struggeling with the missing controller exception thrown by CakePHP.
Once an unknown controller is called, it should redirect on my login page.
First I tried to route to my standart login page once there is an controller/action unknown.
//default routing
$routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'InflectedRoute']);
$routes->connect('/:controller/:action/*', [], ['routeClass' => 'InflectedRoute']);
$routes->connect('/', ['controller' => 'Users', 'action' => 'login']);
//redirect if controller is not known
$routes->connect('/*', ['controller' => 'Users', 'action' => 'login']);
However, this did not work at all, so I googled alot and it turned out that you should catch an the missing controller excpetion instead of rerouting. I can't find out where the error can be caught and it's very little written about it.
Did anyone already have something to do with it?

Short answer
As far as I know catching the MissinControllerException can only be done by customizing the Error Handling flow in Cakephp
Longer answer
To achieve the result you want to need to do the following.
If you are using Middleware in your Application class you need to disable the Error Handling middleware by commenting out this line:
->add(ErrorHandlerMiddleware::class)
Next you need to overwrite the default CakePHP Error handler with a custom one. So in src\Error create the file AppError.php with contents similar to this:
class AppError extends ErrorHandler
{
public function _displayException($exception)
{
if($exception instanceof MissingControllerException){
$response = new Response();
$response = $response->withLocation(Router::url(
['controller'=>'Users', 'action'=>'login'])
);
$emitter = new ResponseEmitter();
$emitter->emit($response);
}else{
parent::_displayException($exception);
}
}
}
Finally, in your bootstrap.php file you need to register your new Error handler. As per docs something like this needs to be added:
$errorHandler = new \App\Error\AppError();
$errorHandler->register();
Potential problems
Doing this redirect will hide away all of your missing controller exceptions. This may cause you trouble in case of typo's in the URL, since you will no longer get a clear error, but instead be redirected to the login page. The error.log file should still show you the original exception.
Good luck!
Potential Problems 2 - CakePHP >= 3.6.x
As pointed out by ndm in the comments disabling the Error Handling Middleware is not always a good idea, especially for CakePHP 3.6. In this case a better solution is to extend the ErrorHandling Middleware and register that.

You could try to handle this exception using custom Exception Renderer:
In src/Error create a new file, named eg MyExceptionRenderer.php, and handle missing controller exception there:
namespace App\Error;
use Cake\Error\ExceptionRenderer;
class MyExceptionRenderer extends ExceptionRenderer
{
public function missingController($error)
{
return $this->controller->redirect("/");
}
}
You will need also to enable this custom renderer in config/app.php:
'Error' => [
'errorLevel' => E_ALL,
'exceptionRenderer' => 'App\Error\MyExceptionRenderer',
'skipLog' => [],
'log' => true,
'trace' => true,
],
With this, when MissingControllerException will be raised, user will be redirected, in this case to main page.
More info can be found here:Error Handling

Related

Cakephp redirect in isAuthorized method not working

So after login in isAuthorized method I'm trying to redirect user based on a condition. But problem is it's not redirecting. Below the code that I have tried.
protected function isAuthorized($LoginUser)
{
if ($this->getTable('Users')->hasCompany($LoginUser) == false){
$this->redirect(['controller'=>'Companies','action'=>'edit']);
dd("hello");
}
}
It's not redirecting and getting hello message. How can I redirect after login user to another page based on condition ?
As mentioned in the comments, the auth component's authorization objects are supposed to return a boolean, and depending on that, let the component do the unauthorized handling-
What you could do, is for example dynamically set the component's unauthorizedRedirect option (and probably also authError) from the controller's authorization handler for that specific case (I guess you'd also have to exclude the respective company controller's action from that check, as otherwise you'll end up in an infinite redirect loop):
if (!$this->getTable('Users')->hasCompany($LoginUser)) {
$message = __('You must provide company information in order to proceed.');
$url = \Cake\Routing\Router::url([
'controller' => 'Companies',
'action' => 'add'
]);
$this->Auth->setConfig([
'authError' => $message,
'unauthorizedRedirect' => $url,
]);
return false;
}
// ...
return true;
If you find yourself in a situation where there's no such possibility, brute forcing a redirect by throwing a \Cake\Http\Exception\RedirectException could be a solution too, even though it's ideally avoided, it's better than dying, as it will at least emit a clean redirect response:
$url = \Cake\Routing\Router::url([
'controller' => 'Companies',
'action' => 'add'
]);
throw new \Cake\Http\Exception\RedirectException($url);
See also
Cookbook > Controllers > Components > AuthComponent > Configuration options

Some requests get canceled after redirecting to a named route

I am currently redirecting from a store route back to my index route with a flash message like this:
public function store(StoreMemberRequest $request)
{
$this->authorize('create', Member::class);
RegisterNewMember::run($request);
return redirect()->route('admin.members.index')->with('success', 'Member successfully created');
}
Normally this flash message shows me a notification on my frontend. I have this bit of code in my HandleInertiaRequests middleware which shares the flash data:
public function share(Request $request)
{
return array_merge(parent::share($request), [
'auth' => [
'user' => $request->user(),
],
'flash' => fn () => [
'success' => session('success'),
'error' => session('error'),
],
]);
}
However, when I redirect back to my admin.members.index route the flash message is not shown. The only difference between the pages where it works and where it doesn't work is that I see that two requests are being canceled in the console:
More detailed information about the request (the response couldn't be loaded because no data was found):
What could be the cause of the notification not showing? It does show on all other pages where the requests doesn't get canceled. The two canceled requests are literally the only differences that I could find. If someone needs any more detailed information than I would be happy to provide that.
Versions:
"#inertiajs/inertia": "^0.10.0"
"#inertiajs/inertia-react": "^0.7.0"
"inertiajs/inertia-laravel": "^0.4.5"
"laravel/framework": "^8.65",
Seems like I had some code which checked for parameters in the url on pageload. When some parameters were set the page would reload. This caused the first request to be canceled. Adding an extra check to this function solved my problem. If anyone else has the same problem, check if anywhere in your code is made a new request.

requestAction is not working in CakePHP 2 Shell Command

I'm attempting to make a cron shell, and the ability to use requestAction for this is crucial.
For testing purposes, I've reduced my Shell down to just this:
class CronShell extends AppShell {
public $uses = array('Cron');
public function main() {
$this->requestAction(['controller' => 'events', 'action' => 'play', 38, 0, true, true]);
echo "This will never be printed to the console, it dies before this.";
}
}
And I still cannot get this to run. It simply returns apparently successful, but actually is not. (no error) Another SO question suggested beforeFilter (and other lifecycle methods) could be the cause, but I've ensured that is not the case. See update.
Other controller/actions aren't working.
Removing the action's return doesn't help.
Using a string-based call doesn't help.
CakePHP v2.10.22
Update: It looks like the Auth component may be causing a redirect to users/login in AppController. Yet I see nowhere that I tell it to redirect here, and I don't even have a users/login action. When I remove 'Auth' from my public $components = [], the shell runs on CLI.
But I'm having trouble disabling Auth! I've tried allow(*), setting the redirects to false, even trying $this->components = ['']. I am always redirected to users/login.
Well, it turns out it wasn't the beforeFilter or any part of the lifecycle, but I did have the Auth component on my AppController like such:
public $components = [
'Session',
'Auth' => [
'loginRedirect' => ['controller' => 'worlds', 'action' => 'route']
]
];
Which, no matter what I did to allow or simulate Auth for cli, just constantly redirected me. So I reduced the above to just Session and wrote under,
if (php_sapi_name() !== 'cli')
This:
$this->components = [
'Session',
'Auth' => [
'loginRedirect' => ['controller' => 'worlds', 'action' => 'route']
]
];
And that removed Auth from the equation. I would have expected CakePHP 2's console to give me any indication it was being redirected, but I'll bet future versions handle it better.

How to use $this->autoRender = false in cakephp 3.6.10?

I am trying to display the test data on the screen by running function test() in the pagesController. Used $this->autoRender = false for it, but it is still giving me error:
Please help me out. I think some version problem is there, but I can't figure it out. Thankyou.
Cakephp by default takes Pages controller's display actions as a home page. display function manages pages and subpages itself that is why you are getting error. Either you can change your default home page in your /config/routes.php
$routes->connect('/', ['controller' => 'Pages', 'action' => 'index']);
OR
define your test action in some other controller.
OR
Remove code from display action
class PagesController {
function display()
{
// default controller code here
// your custom code here
}
}
Hope this will work.

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