Move Error functions to a Controller - El Forum - 04-09-2008
[eluser]Pascal Kriete[/eluser]
New Version <a href="http://ellislab.com/forums/viewthread/76340/#381933">here</a>
I've been looking for a better way to route errors to my own error controller (while keeping the proper url). Up until now I've always included my error controller along with the request controller. Then I check if the requested controller has the function I want, and if it doesn't, I instantiate the error class and call the 404.
Needless to say that this is an awful solution.
So here's my attempt at something more flexible. It replaces the normal show_error function of the Exceptions class. If an error is encountered it opens a connection to the same server, but changes the url to call an error function. So you have can have your errors in a controller with full library access, etc.
Code: <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
* CI_Exceptions extension to route errors to controller
*
* @package CodeIgniter
* @subpackage Libraries
* @category Exceptions
* @author Pascal Kriete
*/
class MY_Exceptions extends CI_Exceptions {
var $CI;
/**
* Controller
*
* @access public
*/
function MY_Exceptions()
{
parent::CI_Exceptions();
}
// --------------------------------------------------------------------
/**
* General Error Page
*
* @access private
* @param string disabled - there to keep ci happy
* @param string ditto for this one
* @param string the error function name
* @return string
*/
function show_error($heading, $message, $template = 'error_general')
{
// Prevent infinite loops
if($_SERVER['HTTP_USER_AGENT'] === 'CI_Error')
die('Invalid error handler.');
// Figure out url to error function
$handler = 'error/' . $template;
$ci_conf =& load_class('Config');
$ci_uri = $ci_conf->site_url($handler);
//Drop http:// if it exists
$ci_uri = str_replace('http://', '', $ci_uri);
//Seperate host from path
$ci_uri = explode('/', $ci_uri, 2);
// Let the stream do it's magic
$error_output = $this->_stream_error($ci_uri[0], '/'.$ci_uri[1]);
return $error_output;
}
// --------------------------------------------------------------------
/**
* Sneaky backdoor stream
*
* @access private
* @param string host name (no http://)
* @param string path to request
* @return string
*/
function _stream_error($host, $path = '/')
{
// Open socket connection
$err = fsockopen($host, 80);
// Toss info to the other side
fputs($err, "GET $path HTTP/1.1\r\n");
fputs($err, "Host: $host\r\n");
fputs($err, "User-Agent: CI_Error\r\n");
fputs($err, "Connection: Close\r\n\r\n");
// Listen for response
$resp = '';
while (!feof($err))
$resp .= fgets($err, 128);
fclose($err);
// Remove http header
$resp = split("\r\n\r\n",$resp);
return $resp[1];
}
}
?>
The possible functions this calls are identical to the templates in the error folder. So a error controller could be:
Code: <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Error extends Controller {
/**
* Constructor
*
* @access public
*/
function Error()
{
parent::Controller();
}
// --------------------------------------------------------------------
/**
* Show 404 Error
*
* @access public
*/
function error_404()
{
header("HTTP/1.1 404 Not Found");
echo 'Ooops... the url you requested doesn\'t exist.<br/>';
}
/**
* Others that are defined by default
*
* function error_db() { }
* function error_general() { }
* function error_php() { }
*/
}
?>
I plan on using this for an open source project, so if anyone knows of any drawbacks to this please let me know.
Move Error functions to a Controller - El Forum - 04-09-2008
[eluser]Sam Dark[/eluser]
And we will get redirect instead of proper 404... no good.
Move Error functions to a Controller - El Forum - 04-09-2008
[eluser]Pascal Kriete[/eluser]
Nope, it doesn't redirect. It opens a connection to the error controller in the background and displays the result. Kind of like an ajax request. Just using php.
Or at least that's what it should do (and does in my tests).
Move Error functions to a Controller - El Forum - 04-09-2008
[eluser]Sam Dark[/eluser]
Ow... sorry. Now I see. But I don't think using socket connection to get error 404 text is actually good idea.
Move Error functions to a Controller - El Forum - 04-09-2008
[eluser]Pascal Kriete[/eluser]
Any particular reason? As this will be part of an open source project, I don't want to ship something incompatible.
The reasoning behind it was to be able to handle the error in a controller. There I have access to the ci super object and more importantly my header, footer, and breadcrumb will load properly. All the other solutions I've seen are rather limited.
Move Error functions to a Controller - El Forum - 04-10-2008
[eluser]Sam Dark[/eluser]
Well. fsockopen can be disabled at shared hosting for security reasons.
Move Error functions to a Controller - El Forum - 04-10-2008
[eluser]Pascal Kriete[/eluser]
You make an excellent point. I've figured out a way that works by setting an error flag (using regular php sessions), then redirecting to the same page. The router checks for the flag and calls up the error page if it is set.
Thanks for the feedback.
Move Error functions to a Controller - El Forum - 04-10-2008
[eluser]Sam Dark[/eluser]
Can you post code here to study how it works?
Move Error functions to a Controller - El Forum - 04-10-2008
[eluser]Pascal Kriete[/eluser]
Sure thing.
I'm using modular seperation in my project so my actual version is a little different. This code should work in a regular ci install though.
MY_Router.php:
Starts the session and checks for an error before validating the uri segments
Code: class MY_Router extends CI_Router {
/**
* Constructor
*
* @access public
*/
function MY_Router()
{
// Start session for error handling
if ( ! isset($_SESSION))
{
session_start();
}
parent::CI_Router();
}
// --------------------------------------------------------------------
/**
* Add error check to the beginning of _validate_request
*
* @access private
* @param mixed uri segments
* @return mixed
*/
function _validate_request($segments)
{
// Check for error flag
if (isset ($_SESSION['error']) )
{
// Uncomment if you need a sub-directory
//$this->set_directory('error');
// Error controller uses _remap so it's not callable by itself
// Supply bogus function to keep ci happy
$segments = array('error', 'placebo');
return $segments;
}
return parent::_validate_request($segments);
}
}
MY_Exceptions.php
Sets the error flag and refreshes the page.
Code: class MY_Exceptions extends CI_Exceptions {
/**
* Constructor
*
* @access public
*/
function MY_Exceptions()
{
parent::CI_Exceptions();
}
// --------------------------------------------------------------------
/**
* General Error Page
*
* @access private
* @param string the heading
* @param string the message
* @param string the error function name
* @return string
*/
function show_error($heading, $message, $template = 'error_general')
{
$conf =& load_class('Config');
$uri =& load_class('URI');
$error = array(
'heading' => $heading,
'message' => $message,
'template' => $template
);
// If there is an error in the error controller we get a loop - stop it
if (isset ($_SESSION['error']))
{
echo '<h2>Fatal Error</h2>';
echo $_SESSION['error']['message'];
unset($_SESSION['error']);
exit;
}
$redirect = $conf->site_url( $uri->uri_string() );
$_SESSION['error'] = $error;
header("Location: ".$redirect);
}
}
Error Controller:
Handles the error through _remap. Unsets error flag.
Code: class Error extends Controller {
/**
* Constructor
*
* @access public
*/
function Error()
{
parent::Controller();
}
// --------------------------------------------------------------------
/**
* Shows error pages depending on the type of error
*
* @access private
*/
function _remap()
{
$err = $_SESSION['error'];
unset($_SESSION['error']);
switch($err['template'])
{
case 'error_general':
$this->_error_general($err['heading'], $err['message']);
break;
case 'error_404':
default:
$this->_error_404($err['heading'], $err['message']);
}
}
// --------------------------------------------------------------------
/**
* Display General Errors
*
* @access private
* @param string heading
* @param string error message
* @return description
*/
function _error_general($heading, $message)
{
$this->load->view('error/general');
}
// --------------------------------------------------------------------
/**
* Display 404 Errors
*
* @access private
* @param string heading
* @param string error message
*/
function _error_404($heading, $message)
{
header("HTTP/1.1 404 Not Found");
$this->load->view('error/404');
}
}
Move Error functions to a Controller - El Forum - 11-14-2008
[eluser]mattalexx[/eluser]
Beautiful. Thanks, inparo. Now I can keep the bad URL in the address bar when there's a 404!
I had to change this so session_start() wasn't called more than once (it is called by a session handling library that I use).
Code: function MY_Router()
{
// Start session for error handling
if (isset($_SESSION) === false)) {
session_start();
}
parent::CI_Router();
}
|