Zend Framework 2 : Programmatically handle 404 page
Sometime, in real life project, we need to handle 404 page more than only show the 404 page, but logging, redirecting, forwarding, change template/content, etc. We can handle it by programmatically at Module.php. In Zend Framework 2, there is 4 types of 404 ( page not found ), they are : ERROR_CONTROLLER_CANNOT_DISPATCH, ERROR_CONTROLLER_NOT_FOUND, ERROR_CONTROLLER_INVALID, and ERROR_ROUTER_NO_MATCH. Let’s investigate one by one.
1. ERROR_CONTROLLER_CANNOT_DISPATCH
It means the controller is matched, but the action that passed to the url can’t be dispatched. We can handle it via onBootstrap like the following :
namespace YourModule; use Zend\Mvc\MvcEvent; class Module { public function onBootstrap(MvcEvent $e) { $eventManager = $e->getApplication()->getEventManager(); $sharedManager = $eventManager->getSharedManager(); //controller can't dispatch request action that passed to the url $sharedManager->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', array($this, 'handleControllerCannotDispatchRequest' ), 101); } public function handleControllerCannotDispatchRequest(MvcEvent $e) { $action = $e->getRouteMatch()->getParam('action'); $controller = get_class($e->getTarget()); // error-controller-cannot-dispatch if (! method_exists($e->getTarget(), $action.'Action')) { $logText = 'The requested controller '. $controller.' was unable to dispatch the request : '.$action.'Action'; //you can do logging, redirect, etc here.. echo $logText; } } public function getConfig() { //common code here } public function getAutoloaderConfig() { //common code here } }
2. ERROR_CONTROLLER_NOT_FOUND
It means controller class not found with requested [/:controller] route that defined already at module.config.php
3. ERROR_CONTROLLER_INVALID
It means the controller is not dispatchable, it usually because the controller is not extends Zend\Mvc\Controller\AbstractActionController
4. ERROR_ROUTER_NO_MATCH
It means The requested URL could not be matched by routing, for example, there is no route with prefix /foo that passed at the url.
For point 2, 3, and 4, we can handle by :
namespace YourModule; use Zend\Mvc\MvcEvent; use Zend\Mvc\Application; class Module { public function onBootstrap(MvcEvent $e) { $eventManager = $e->getApplication()->getEventManager(); $eventManager->attach('dispatch.error', array($this, 'handleControllerNotFoundAndControllerInvalidAndRouteNotFound'), 100); } public function handleControllerNotFoundAndControllerInvalidAndRouteNotFound(MvcEvent $e) { $error = $e->getError(); if ($error == Application::ERROR_CONTROLLER_NOT_FOUND) { //there is no controller named $e->getRouteMatch()->getParam('controller') $logText = 'The requested controller ' .$e->getRouteMatch()->getParam('controller'). ' could not be mapped to an existing controller class.'; //you can do logging, redirect, etc here.. echo $logText; } if ($error == Application::ERROR_CONTROLLER_INVALID) { //the controller doesn't extends AbstractActionController $logText = 'The requested controller ' .$e->getRouteMatch()->getParam('controller'). ' is not dispatchable'; //you can do logging, redirect, etc here.. echo $logText; } if ($error == Application::ERROR_ROUTER_NO_MATCH) { // the url doesn't match route, for example, there is no /foo literal of route $logText = 'The requested URL could not be matched by routing.'; //you can do logging, redirect, etc here... echo $logText; } } public function getConfig() { //common code here } public function getAutoloaderConfig() { //common code here } }
Want to handle All ? Let’s do this :
namespace YourModule; use Zend\Mvc\MvcEvent; use Zend\Mvc\Application; class Module { public function onBootstrap(MvcEvent $e) { $eventManager = $e->getApplication()->getEventManager(); $sharedManager = $eventManager->getSharedManager(); //controller can't dispatch request action that passed to the url $sharedManager->attach('Zend\Mvc\Controller\AbstractActionController', 'dispatch', array($this, 'handleControllerCannotDispatchRequest' ), 101); //controller not found, invalid, or route is not matched anymore $eventManager->attach('dispatch.error', array($this, 'handleControllerNotFoundAndControllerInvalidAndRouteNotFound' ), 100); } public function handleControllerCannotDispatchRequest(MvcEvent $e) { $action = $e->getRouteMatch()->getParam('action'); $controller = get_class($e->getTarget()); // error-controller-cannot-dispatch if (! method_exists($e->getTarget(), $action.'Action')) { $logText = 'The requested controller '. $controller.' was unable to dispatch the request : '.$action.'Action'; //you can do logging, redirect, etc here.. echo $logText; } } public function handleControllerNotFoundAndControllerInvalidAndRouteNotFound(MvcEvent $e) { $error = $e->getError(); if ($error == Application::ERROR_CONTROLLER_NOT_FOUND) { //there is no controller named $e->getRouteMatch()->getParam('controller') $logText = 'The requested controller ' .$e->getRouteMatch()->getParam('controller'). ' could not be mapped to an existing controller class.'; //you can do logging, redirect, etc here.. echo $logText; } if ($error == Application::ERROR_CONTROLLER_INVALID) { //the controller doesn't extends AbstractActionController $logText = 'The requested controller ' .$e->getRouteMatch()->getParam('controller'). ' is not dispatchable'; //you can do logging, redirect, etc here.. echo $logText; } if ($error == Application::ERROR_ROUTER_NO_MATCH) { // the url doesn't match route, for example, there is no /foo literal of route $logText = 'The requested URL could not be matched by routing.'; //you can do logging, redirect, etc here... echo $logText; } } public function getConfig(){ // common code } public function getAutoloaderConfig(){ //common code } }
Done 😉
hi ! tnx for your tutorials ! they are great 🙂
i have a question :
how can i pass e variable to layout in this situation ( 404 page ) ?
you can set by :
and call in the layout/view :
Hallo Samsonasik,
I get it how to pass variable to the template. But how do I get the original variable that come with the standard template. Actually all I need is change the layout, but I need all the variables. The only thing I can have is the error.
Thanks!
$viewModel->getVariables()
awesome tutorial dear . I am beginner learn lot from your precious tutorial
Very nice! How to make a toRoute redirect if a ERROR_ROUTER_NO_MATCH?
you can do something like this :
very grateful! Your blog is a true source of good information. Thank!
HI,
Thanks for the great tutorial!
I have one question in relation to my current work. Why error-controller-cannot-dispatch is not caugth by MvcEvent::EVENT_DISPATCH_ERROR handler ?
because controller is mathed, but action is not
So as far as I see when action is not matched it is not dispatch.error and there is no other error that we can attach handler to catch it directly ?
I have implemented the above code & its not working, could you plz help in that.. In the Module.php file I have pasted the code on error block :
$sharedManager->attach(‘Zend\Mvc\Controller\AbstractActionController’,’dispatch’,
array($this, ‘handleControllerCannotDispatchRequest’ ), 404); & added the function :
public function handleControllerCannotDispatchRequest(MvcEvent $e)
{
$action = $e->getRouteMatch()->getParam(‘action’);
$controller = get_class($e->getTarget());
$error = $e->getError();
if ($error == Application::ERROR_ROUTER_NO_MATCH) {
// the url doesn’t match route, for example, there is no /foo literal of route
$logText = ‘The requested URL could not be matched by routing.’;
//you can do logging, redirect, etc here…
echo $logText; die;
}
}
but when i give wrong URL it’s not working, wrong URL mean subdomain not exists, example valid url is : text.mydomain.com, invalid one is test1.mydomain.com..
when I type test1.mydomain.com its not redirecting to anywhere just loading the home page content, which I to be redirected to 404 page.. Please help
check if you configure route hostname correctly, read the docs http://framework.zend.com/manual/current/en/modules/zend.mvc.routing.html#zend-mvc-router-http-hostname
How can i test the Application::ERROR_CONTROLLER_CANNOT_DISPATCH error?
I mean i want to end up with a viewModel that has the Application::ERROR_CONTROLLER_CANNOT_DISPATCH reason. Do you know what to do, to get this error?
I said it happen when controller found, but action not found, so, you may have route: `/[:controller]/[:action]` and when you access `/controllername/actionname` but you call un-registered action. if you use ZendSkeletonApplication, you already got 404 page 😉
The AbstractActionController::notFoundAction is called when there is no action found. and this renders the 404 page. but i want to respond with JSON. so i want to find a viewModel with the Application::ERROR_CONTROLLER_CANNOT_DISPATCH reason.
create response object. you may need something like this:
I you want to support both ActionController and RestfulController than you should use `Zend\Mvc\Controller\AbstractController` instead of `Zend\Mvc\Controller\AbstractActionController`.
Hi!
I have several modules which have own error templates like error/404.phtml and error/index.html (for other errors except 404).
I need to change path to template for each module. For EVENT_DISPATCH_ERROR & EVENT_RENDER_ERROR my code looks like
$stack = $event->getApplication()->getServiceManager()->get('ViewTemplateMapResolver');
if ($response->getStatusCode() == 404) {
$stack->add('error/404', $path.'404.phtml');
} else{
$stack->add('error/index', $path.'index.phtml');
}
But in ERROR_CONTROLLER_CANNOT_DISPATCH i cant do like this and error template displayed from one module every time.
Can you help me?
this may help: https://samsonasik.wordpress.com/2012/09/19/zend-framework-2-create-custom-error-page/
Hi Samsonasik,
I have a layout in which we have 3 sections – 1) Header, 2) Body & 3) Footer. Wherein header & footer is common across the application. Now when the user access invalid URL, I need to show 404.phtml content. I added the code which you provided on the top for various types of errors.
Instead of echo’ing some message I want to load 404.phtml content inside the BODY section.
I’m trying with the below code by defining the $path to the 404.phtml file path.
$stack = $event->getApplication()->getServiceManager()->get(‘ViewTemplateMapResolver’);
if ($response->getStatusCode() == 404) {
$stack->add(‘error/404′, $path.’404.phtml’);
}
But, it is not working. Can you please provide me the solution?
if you’re using zf skeleton application, 404 page will automatically uses module/Application/view/error/404.phtml, you can define the 404 in module/Application/config/module.config.php
Hi Samsonasik,
I have implemented the above code (defined in Module.php file for handling ROUTE NOT FOUND, INVALID ROUTE etc, but I’m facing a problem like, I have one action from which I redirect to another function(route not defined for this function – example captureadmincartdetails() {} ). In that case I’m getting an error as defined below :
Zend\View\Renderer\PhpRenderer::render: Unable to render template “organizations/organizations/captureadmincartdetails”; resolver could not resolve to a file.
I used function “captureadmincartdetails” for processing the order details & upon processed the order. Will redirect to different URL.
Can you please suggest the way I can handle such scenarios.
Thanks,
Srinivasu M
the error you got is very clear, you got page that doesn’t redirect, instead, it try to show the view. You can check the zf documentation on how to redirect to another page.
For view error, you can read in zf tutorial about how to fix that view render error https://docs.zendframework.com/tutorials/in-depth-guide/first-module/
What if i want to set a controller if ERROR_ROUTER_NO_MATCH .. how would i achieve this
you can do something like this:
Thank you for the reply.. But what i want is without changing the url set the controller and layout to the current url
that’s impossible without very hacky (pull global service manager , call controller plugin manager, call forward plugin, set controller, call dispatch, etc etc) , as the route already not match, there is no way to pull it and setParam(‘controller’) or setParam(‘action’), my suggestion is to create default render view for that, or just define 404 page properly for your site.
dynamic urls are getting created.. right now what i have hacked is i have written the module.config.php file everytime a new url is added.. But i find that not the correct way to do it.. and i don’t want my urls to be domain.com/page/page1 i want them domain.com/page1 .. Thanks again
you should not doing that, forwarding also should not be applied for 404 page. Just define a custom 404 page if you want.
This is not about 404.. its a cms that i have built on zend.. and looking for a solution where i can create dynamic urls which point to the same controller and action… My urls can be domain.com/page , domain.com/category/page domain.com/category/subcategory/page and i need all to point to the same action of the same controller.. so i truncate the url.. and check the seo tags , page layout , page data etc and if not found manually trigger 404.. Thanks again for the reply.. 🙂