This has bugged me for some time.
When I call set_error_handler(), errors and warnings and notices are routed to my handler, and when i call restore_error_handler(), my previously defined handler is restored. But what happens when an exception is thrown before restore_error_handler() is called?
I have the following defined within a class:
function do_something() {
set_error_handler([$this, 'convert_errors_to_exceptions']);
//stuff that may trigger error, e.g.
trigger_error('testing an error');
//will this actually ever be called?
restore_error_handler();
}
public function convert_errors_to_exceptions($num, $message, $file, $line) {
//if error reporting turned off
if (!(error_reporting() & $num)) return false;
//convert to exception - then what happens?
throw new Exception("Error: $message in $file on line $line");
return true;
}
Will restore_error_handler() ever be called if an error is triggered? And do I have to call restore_error_handler() immediately prior to throwing the exception if I want my previous handler to actually handle the exception itself?
Cart/horse problems!
Related
Is it possible to capture/gather all the errors on page and concatenate them to a string by using:
$allErrors = "";
$allErrors .= error_get_last(); // Each time an error shows up
I like to log errors in my database and would prefer to log all these PHP errors since I already have SQL-related PHP Fatal Errors logged.
error_get_last(), like the name is suggesting, only gives you the last error. And the fact that most errors will stop your script from running will get you only the last one and none of the previous ones. But you can set an own handler to catch every thrown error and exception. Here is an example
//function for exception handling
function handle_exception (Exception $exception) {
//here you can save the exception to your database
}
//function for error handling
function handle_error ($number, $message, $file, $line, $context = null) {
//pass/throw error to handle_exception
throw new ErrorException ($message, 0, $number, $file, $line);
}
//set error-handler but only for E_USER_ERROR and E_RECOVERABLE_ERROR
set_error_handler ('handle_error', E_USER_ERROR|E_RECOVERABLE_ERROR);
//exception-handler
set_exception_handler ('handle_exception');
How do i catch (custom)exceptions (with custom exception handler) that i have thrown in custom shutdown function ? I am not using any framework.
Example:
register_shutdown_function( 'shutdownFunction');
set_exception_handler( 'exceptionHandler');
function exceptionHandler(Exception $exception)
{
// handle and log exception for later debugging...
}
function shutdownFunction()
{
// something is not going right...
if (someVariable != someValue)
throw new RuntimeException('blah...'); // WILL NOT be caught by exception handler
}
// somewhere else in the code...
throw new Exception('something...'); // WILL be caught by exception handler (but not in shutdown function)
The script is using exceptions to communicate that it encountered an error during execution ie. unexpected variable passed to function, database failed to insert row etc...
You simply cannot do this in php.
The register_shutdown_function callback is the last thing that happens in your PHP application. Trying to throw an exception in there will not do anything but invoke a standard php handler. There isn't much to be found on the web regarding these inner workings.
However, I created my own solution for directing it to a single function.
set_exception_handler and register_shutdown_functionare very different functions:
set_exception_handler receives a single argument Exception
register_shutdown_function receives no arguments by default
I've made it so that the set_exception_handler (which receives $exception as argument) sets a property which I can use in the register_shutdown_function.
$lastException = null;
set_exception_handler(function ($e) use (&$lastException) {
$lastException = $e;
});
register_shutdown_function(function() use(&$lastException) {
if($error = error_get_last()) {
$lastException = new \ErrorException($error['message'], $error['type'], 1, $error['file'], $error['line']);
}
if($lastException) {
if (APPLICATION_ENV === 'production') {
Sentry\captureException($lastException);
} else {
var_dump($lastException);
}
}
});
I have no clue if this is a good way to solve the issue, but it allowed me to catch require unexisting_phpfile1389.php errors (Fatal) and regular throw \Exception()s in the same function.
Trying to throw an exception inside the shutdown handler will result in the following exception (how ironic):
( ! ) Fatal error: Uncaught Error: Can only throw objects in
C:...\index.php on line 34
( ! ) Error: Can only throw objects in
C:...\index.php on line 34
You can just wrap the body of your shutdownFunction with
function shutdownFunction()
try {
...
} catch (\Exception $e) {
// do something
}
}
and you will catch all exceptions becase Exception is the base class for all of them
It's quite simple:
function exception_handler (Exception $e) {
if ($e instanceof DBException)
error_handler (['query' => $e->getQuery ()]); // Your actions with your custom Exception object
}
function error_handler ($error) {
if (isset ($error['query']))
echo $error['query'];
else
// Another errors
}
set_error_handler ('error_handler', E_ALL);
set_exception_handler ('exception_handler');
I'm calling a method that I know could cause an error and I'm trying to handle the error by wrapping the code in a try/catch statement...
class TestController extends Zend_Controller_Action
{
public function init()
{
// Anything here happens BEFORE the View has rendered
}
public function indexAction()
{
// Anything `echo`ed here is added to the end of the View
$model = new Application_Model_Testing('Mark', 31);
$this->view->sentence = $model->test();
$this->loadDataWhichCouldCauseError();
$this->loadView($model); // this method 'forwards' the Action onto another Controller
}
private function loadDataWhichCouldCauseError()
{
try {
$test = new Application_Model_NonExistent();
} catch (Exception $e) {
echo 'Handle the error';
}
}
private function loadView($model)
{
// Let's pretend we have loads of Models that require different Views
switch (get_class($model)) {
case 'Application_Model_Testing':
// Controller's have a `_forward` method to pass the Action onto another Controller
// The following line forwards to an `indexAction` within the `BlahController`
// It also passes some data onto the `BlahController`
$this->_forward('index', 'blah', null, array('data' => 'some data'));
break;
}
}
}
...but the problem I have is that the error isn't being handled. When viewing the application I get the following error...
( ! ) Fatal error: Class 'Application_Model_NonExistent' not found in /Library/WebServer/Documents/ZendTest/application/controllers/TestController.php on line 23
Can any one explain why this is happening and how I can get it to work?
Thanks
use
if (class_exists('Application_Model_NonExistent')) {
$test = new Application_Model_NonExistent;
} else {
echo 'class not found.';
}
like #prodigitalson said you can't catch that fatal error.
An error and an exception are not the same thing. Exceptions are thrown and meant to be caught, where errors are generally unrecoverable and triggered with http://www.php.net/manual/en/function.trigger-error.php
PHP: exceptions vs errors?
Can I try/catch a warning?
If you need to do some cleanup because of an error, you can use http://www.php.net/manual/en/function.set-error-handler.php
Thats not an exception, thats a FATAL error meaning you cant catch it like that. By definition a FATAL should not be recoverable.
Exception and Error are different things. There is an Exception class, which you are using and that $e is it's object.
You want to handle errors, check error handling in php-zend framework. But here, this is a Fatal error, you must rectify it, can not be handled.
PHPs token_get_all function (which allows converting a PHP source code into tokens) can throw two errors: One if an unterminated multiline comment is encountered, the other if an unexpected char is found.
I would like to catch those errors and throw them as Exceptions.
Problem is: As those errors are parse errors they cannot be handled with an error handling function you would normally specify using set_error_handler.
What I have currently implemented is the following:
// Reset the error message in error_get_last()
#$errorGetLastResetUndefinedVariable;
$this->tokens = #token_get_all($code);
$error = error_get_last();
if (preg_match(
'~^(Unterminated comment) starting line ([0-9]+)$~',
$error['message'],
$matches
)
) {
throw new ParseErrorException($matches[1], $matches[2]);
}
if (preg_match(
'~^(Unexpected character in input:\s+\'(.)\' \(ASCII=[0-9]+\))~s',
$error['message'],
$matches
)
) {
throw new ParseErrorException($matches[1]);
}
It should be obvious that I'm not really excited to use that solution. Especially the fact that I reset the error message in error_get_last by accessing an undefined variable seems pretty unsatisfactory.
So: Is there a better solution to this problem?
Set a custom errorhandler using set_error_handler.
Call token_get_all.
Then unset the error handler by calling restore_error_handler.
This will allow you to catch warnings. Make sure you remove the # suppressor.
You can for instance register an error handler that is in a class that will just record any warnings for inspection later on.
Untested example code:
class CatchWarnings {
private $warnings = array();
public function handler($errno, $errstr, $errfile, $errline) {
switch ($errno) {
case E_USER_WARNING:
$this->warnings[] = $errstr;
return true; // cancel error handling bubble
}
return false; // error handling as usual
}
public function has_warnings() {
return count($this->warnings) > 0;
}
}
$cw = new CatchWarnings();
set_error_handler(array($cw, "handler"));
token_get_all();
restore_error_handler();
Usually validation and execution are two separate things, but it seems like there is no way to validate/lint a piece of PHP code (not since 5.x anyway).
basically i have a custom exception handler. When i handle an exception, i just want it to echo the message and continue the script. But after my method handles the exception, the script doesnt continue.
Is this a behaviour of php or is my exception handler doing something wrong?
This is a behavior of php. This differs from set_error_handler() in that, according to the manual on set_exception_handler(), Execution will stop after the exception_handler is called. Therefore, ensure you catch all exceptions, letting only those you want to kill your script through.
This is actually why set_error_handler() doesn't pair well with exceptions and set_exception_handler() when converting all errors to exceptions... unless you actually mean your application to be so strictly coded that any notice or warning halts the script. But at least it gives you a trace on that call involving an unset array key.
With a custom exception handler, you'll want to catch the exception in a try/catch block and do whatever handling you want in there.
The following is the example from The CodeUnit of Craig
try {
$error = 'Throw this error';
throw new Exception($error);
echo 'Never get here';
}
catch (Exception $e)
{
echo 'Exception caught: ', $e->getMessage(), "\n";
}
If you want to catch and print any unhandled exception, you can set a top level exception handler like this example from w3schools(near the bottom of the page)
<?php
function myException($exception){
echo "<b>Exception:</b> " , $exception->getMessage();
}
set_exception_handler('myException');
throw new Exception('Uncaught Exception occurred');
?>
should print: "Exception: Uncaught Exception occurred"
Look at the following code. it worked for me:
define(BR, "<br/>");
try {
echo "throwing exception" . BR;
throw new Exception("This is exception");
}
catch(Exception $ex) {
echo "caught exception: " . BR . $ex->getMessage() . BR;
}
echo "Keep on going!. ..." . BR;
it prints the following:
throwing exception
caught exception:
This is exception
Keep on going!. ...
What do you say ?
Can you show the code of your code handler ?
You could do this :
function handleError($errno, $errstring, $errfile, $errline, $errcontext) {
if (error_reporting() & $errno) {
// only process when included in error_reporting
return processError($errno, $errstring);
}
return true;
}
function handleException($exception){
// Here, you do whatever you want with the generated
// exceptions. You can store them in a file or database,
// output them in a debug section of your page or do
// pretty much anything else with it, as if it's a
// normal variable
}
function processError($code, $message){
switch ($code) {
case E_ERROR:
case E_CORE_ERROR:
case E_USER_ERROR:
// Throw exception and stop execution of script
throw new Exception($message, $code);
default:
// Execute exception handler and continue execution afterwards
return handleException(new Exception($message, $code));
}
}
// Set error handler to your custom handler
set_error_handler('handleError');
// Set exception handler to your custom handler
set_exception_handler('handleException');
// ---------------------------------- //
// Generate warning
processError(E_USER_WARNING, 'This went wrong, but we can continue');
// Generate fatal error :
processError(E_USER_ERROR, 'This went horrible wrong');
Alternate approach :
function handleError($errno, $errstring, $errfile, $errline, $errcontext) {
if (error_reporting() & $errno) {
// only process when included in error_reporting
return handleException(new \Exception($errstring, $errno));
}
return true;
}
function handleException($exception){
// Here, you do whatever you want with the generated
// exceptions. You can store them in a file or database,
// output them in a debug section of your page or do
// pretty much anything else with it, as if it's a
// normal variable
switch ($code) {
case E_ERROR:
case E_CORE_ERROR:
case E_USER_ERROR:
// Make sure script exits here
exit(1);
default:
// Let script continue
return true;
}
}
// Set error handler to your custom handler
set_error_handler('handleError');
// Set exception handler to your custom handler
set_exception_handler('handleException');
// ---------------------------------- //
// Generate warning
trigger_error('This went wrong, but we can continue', E_USER_WARNING);
// Generate fatal error :
trigger_error('This went horrible wrong', E_USER_ERROR);
An advantage of the latter strategy, is that you get the $errcontext parameter if you do $exception->getTrace() within the function handleException.
This is very useful for certain debugging purposes. Unfortunately, this works only if you use trigger_error directly from your context, which means you can't use a wrapper function/method to alias the trigger_error function (so you can't do something like function debug($code, $message) { return trigger_error($message, $code); } if you want the context data in your trace).
EDIT
I've found one dirty workaround for the trigger_error problem.
Consider the following code :
define("__DEBUG__", "Use of undefined constant DEBUG - assumed 'DEBUG'");
public static function handleError($code, $message, $file, $line, $context = false) {
if ($message == __DEBUG__) {
return static::exception(new \Exception(__DEBUG__, E_USER_WARNING));
} else {
if (error_reporting() & $code) {
return static::exception(new \Exception($message, $code));
}
return true;
}
}
public static function handleException($e) {
global $debug;
$code = $e->getCode();
$trace = $e->getTrace();
if ($e->getMessage() == __DEBUG__) {
// DEBUG
array_push($debug, array(
'__TIME__' => microtime(),
'__CONTEXT__' => array(
'file' => $trace[0]['file'],
'line' => $trace[0]['line'],
'function' => $trace[1]['function'],
'class' => $trace[1]['class'],
'type' => $trace[1]['type'],
'args' => $trace[0]['args'][4]
)
));
} else {
// NORMAL ERROR HANDLING
}
return true;
}
With this code, you can use the statement DEBUG; to generate a list of all available variables and a stack trace for any specific context. This list is stored in the global variable $debug. You can add it to a log file, add it to a database or print it out.
This is a VERY, VERY dirty hack, though, so use it at your own discretion. However, it can make debugging a lot easier and allows you to create a clean UI for your debug code.