So you think your Kohana v3 powered application is cool? Suddenly you encountered a page not found (HTTP 404 error) message where Kohana dumps the error messages in debug mode! It is already part of the daily web but yet, Kohana v3 has no native support for trapping 404 errors.
UPDATE I have updated my 404 trapping and use the hints provided on the Kohana forum which is trapping ReflectionException. I added a custom trapping that will trap 404 errors not only for ReflectionException but also for Kohana_Request_Exception for invalid routes. HTTP 500 error is added as well.
UPDATE (2010-09-30) – Fixed bug where the requested page link in 404 page is an invalid link.
By the way, there are great tutorials on how to create a custom 404 page handler and one of them is in the unofficial Kohana v3 wiki. However, I don’t like the idea of setting a catch-all route and adding a pre-determined controllers. Instead, I followed the code used by shadowhand on his wingsc.com site.
The problem I encountered with the wingsc.com code is that its template is not compatible with my template. My template is a bit customized that using his code without modification will throw a lot of errors. So, combining the great ideas from kerkness and shadowhand, here it is.
Basic structure
With my version of custom 404 page, it uses the ideas from kerkness’ to create a custom controller/action dedicated for the 404 page together with the view of course and combined with shadowhand’s catch-all exception strategy. You will need to these:
- Create a controller/action/view for the 404 page.
- Trap exception at bootstrap and show the 404 page.
- Other types of exception will show the error 500 page.
- Display some useful information to the user.
The idea behind the catch-all exception strategy is that in bootstrap, the request is executed in a try and catch block. When no exception is thrown, the normal response is sent. However, when there is an exception, the error 404/500 page is sent as the response.
This leads us to the problem where other exceptions may be interpreted as 404 error. So we need to decide! However, I prefer to pursue on this and catch exceptions carefully at controller/model level instead. That way, only 404/500 errors will not be caught.
The custom 404/500 page
First, we need to create the controller and the action for our 404/500 page. Since I’m using a template, I will be extending that template for our 404/500 page so that it will have the same look and feel as the rest of the website.
application/classes/errors.php
<?php defined('SYSPATH') or die('No direct script access.'); /** * Handles HTTP errors and the like, which are not catched * by the application */ class Controller_Errors extends Controller_Site { /** * Serves HTTP 404 error page */ public function action_404() { $this->template->description = 'The requested page not found'; $this->template->keywords = 'not found, 404'; $this->template->title = 'Page not found'; $this->view = View::factory('errors/404'); $this->request->status = 404; } /** * Serves HTTP 500 error page */ public function action_500() { $this->template->description = 'Internal server error occured'; $this->template->keywords = 'server error, 500, internal error, error'; $this->template->title = 'Internal server error occured'; $this->view = View::factory('errors/500'); $this->request->status = 500; } }
Next, we will create the view.
application/views/errors/404.php
<h1><strong>Page not found</strong></h1> <div class="entries"> <div class="entry-body"> <p>The requested page <a href="{KOHANA_REQUESTED_PAGE}">{KOHANA_REQUESTED_PAGE}</a> is not found.</p> <p>It is either not existing, moved or deleted. Make sure the URL is correct. </p> <p>To go back to the previous page, click the Back button.</p> <p><a href="<?php echo URL::site('/', true) ?>">If you wanted to go to the main page instead, click here.</a></p> </div> </div>
application/views/errors/500.php
<h1><strong>Internal server error</strong></h1> <div class="entries"> <div class="entry-body"> <p>Something went wrong while we are processing your request. You can try the following:</p> <ul> <li>Reload / refresh the page.</li> <li>Go back to the previous page.</li> </ul> <p>This incident is logged and we are already notified about this problem. We will trace the cause of this problem.</p> <p>For the mean time, you may want to go to the main page.</p> <p><a href="<?php echo URL::site('/', true) ?>">If you wanted to go to the main page, click here.</a></p> </div> </div>
You may notice the string KOHANA_REQUESTED_PAGE. We will replace that will the actual page requested by the user – the page that does not exists. We will replace that in bootstrap.
Bootstrap
Finally, in our bootstrap page, we will modify the request flow a bit.
application/bootstrap.php
/** * Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO']. * If no source is specified, the URI will be automatically detected. */ try { // Attempt to execute the response $request = Request::instance()->execute(); // Display the request response. echo $request->send_headers()->response; } catch (Exception $e) { if ( ! IN_PRODUCTION) { // Just re-throw the exception throw $e; } // Log the error Kohana::$log->add(Kohana::ERROR, Kohana::exception_text($e)); // Create new request for serving error pages $request = null; // 404 errors are usually thrown as ReflectionException or // Kohana_Request_Exception when a controller/action is not // found or a route is not set for a specific request if ($e instanceof ReflectionException OR $e instanceof Kohana_Request_Exception) { // Create a 404 response $request = Request::factory('errors/404')->execute(); // insert the requested page to the error reponse $uri = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : '/'; $page = array('{KOHANA_REQUESTED_PAGE}' => URL::site($uri, true)); $request->response = strtr((string) $request->response, $page); } else { // create a 500 response $request = Request::factory('errors/500')->execute(); } echo $request->send_headers()->response; }
As you’ve noticed, we tried to execute the request and when it fails, we decide if it is a 404 error or 500 error. Usually, ReflectionException and Kohana_Request_Exception is thrown for invalid controller/action and invalid route respectively. In that case, we will treat them as 404 error. For 404 errors, we get the URL of the page the user requested and replace it to the string KOHANA_REQUESTED_PAGE from our response. Lastly, we serve the 404 page.
For exceptions other than ReflectionException and Kohana_Request_Exception, we serve the HTTP 500 error.
Showing 404/500 page is only activated in production mode since not all exceptions are really 404/500 pages which is most likely in a development environment where your application is in constant change.
Nice article! Does the code for the bootstrap.php-file belong under the routes in the current bootstrap.php, or above the routes?
Hi Espresso,
Yes they are under the routes, just replace the whole rendering of response with that. However, this is for Kohana 3.0.x or perhaps 3.0.7 and above. For 3.1, visit here:
http://blog.lysender.com/2011/02/kohana-3-1-migration-custom-error-pages/
Such a nice post this is. I’m a beginner for Kohana and I have problem so much with routing of this framework. I hope I’ll be better when I’ve found your post. Thanks!