Handle / Log Exceptions that are caught in the whole application - php

I am currently maintaining a application with a large code base. The application is still under development.
My goal is to log all the exceptions that are thrown in the application. Also the ones that are caught in the try catch block. Due to the large code base I cannot add single lines of code in the catch blocks or create my custom exception class.
The way I tried to solve it or look for a solution are:
Listen for class construction in the whole application
Override the Exception class (can't do this because it is an core php class)
My most recent code that I tried is:
My test exception (dashboardcontroller.php)
try {
throw new \Exception('custom thrown exception');
} catch (\Exception $e) {
Log::info('Exception caught');
}
Exceptions\Handler.php
public function report(Exception $exception)
{
Log::info($exception);
parent::report($exception);
}
Log
[2020-03-10 17:19:09] local.INFO: Exception caught
So the question is: How can I log / handle thrown exceptions that are caught in the try catch block without adding code that requires lot of changes?

Short answer: You can't (as far as I know that is).
Exceptions that are caught are, normally, caught for a reason; they are handled (or: they should be) in the catch-statement (i.e. exception handler). It's the uncaught exceptions that you want to log.
Exceptions should never be used for program-flow, but it does happen anyway and sometimes for good reason:
if (fileExists('foo.txt')) {
fileDelete('foo.txt'); // May result in a race-condition
}
V.s.:
try {
fileDelete('foo.txt')
catch (IOException) {
// NO-OP
}
Another common pattern:
while (true) {
try {
item = queue.Receive(10); // Wait max. 10 seconds
process(item); // Process item
} catch (TimeOutException) {
// Nothing on queue, handle other stuff and then continue waiting...
DoStuff();
}
}
Above patterns are quite common. Again: exceptions shouldn't be used for program-flow and the queue example above surely shouldn't exist but the reality is that it happens. Real world applications, libraries, API's etc. sometimes just work that way and as soon as you're dealing with 3rd party applications, libraries or API's you're in trouble because you can't always change everything to what you would like it to be.
Can you imagine your logfile in the above queue-example with your 'generic application wide catch logger' for an application running 24/7?
When to log an exception (and maybe just as important: what to log) should be considered on a case-by-case basis.

Related

Try {} catch {} on API call or handle differently?

In a Laravel 5.2 app, I'm trying to figure out the most elegant way to handle exceptions when those exceptions are thrown by API calls of external services. These shouldn't stop the program from continuing, because there are other parts of the app that still can be run afterwards that can be done even without the problematic API call.
E.g. currently I have
try {
$statistics->results = $api->call($parameter);
$statistics->status = Statistic::SUCCESS;
} catch (ExternalApiCallException $e) {
$statistics->results = null;
$statistics->status = Statistic::API_CALL_ERROR;
}
I was thinking of using Laravel's app/Exceptions/Handler.php and using
if ($e instanceof ExternalApiCallException $e) {
Log::warning("API Call didn't work");
}
but then I wouldn't be able to set the status of the statistics there, because Handler.php wouldn't have access to it. Is there a better way or are try-catch-blocks the way to go here?
try-catch-finally is definitely a good approach here. In your case API exceptions are localized and should not appear globally (I presume), so I would opt for keeping logic in one place and not putting exception handling as a condition in Handler.php file. In my opinion Handler.php should be used as a last resort option, to style and report otherwise uncaught and unexpected exceptions. For all other cases where exceptions are not critical or even expected, local try {} blocks are easier to maintain, as important parts of the code are not hidden from developer in another file.
Do not forget that you can also use finally {} block, which will be executed after both try{} and catch{} blocks, no matter if the exception was triggered or not.
try
{
// run this first
}
catch (ExternalApiCallException $e)
{
// in case something went wrong
}
finally
{
// this runs after everything else
}

Error logging, in a smooth way

I've been reading on in particular 'error logging' And I have come up with the function 'error_log' which seem to be a good tool to use to handle the error logging. But how is the smoothest and best way to use it?
If I have a
try {
//try a database connection...
} catch (PDOException $e) {
error_log($e->getMessage(), 3, "/var/tmp/my-errors.log");
}
This would log the error in the my-errors.log file. But what If I sometime need to change the position of where the file is, a new folder, or something. If I have tons of files I need to change them all.
Now I started of thinking to use a variable to set the path to the error log. Sure that could work, but what If I want to use the error_log in a function or class method? Then I would need to set the variable as global, but that is considered bad practise! But what If I shouldn't use the function deep in a class, wouldn't that also be considered bad practise? What is a good solution here?
<?php
function legit() {
try {
if (1 == 1) {
throw new Exception('There was an error here');
}
} catch (Exception $e) {
throw new Exception('throw the error to the try-catch outside the function...');
}
}
try {
legit();
} catch (Exception $e) {
echo 'error here' . $e->getMessage();
//log it
}
This is an example of what I was talking about above (Not having the logging deep in a class/function... Is it a good way?)
Furtheron:
I am not quite sure how I should use the Exceptions in general. Let's say I want to do a INSERT to a database with SQL inside a method, would I use a try/catch and then rethrow the exception if it fails? Is that considered good practise? Examples please.
Firstly, I'd like to commend you for looking at the standard error methods within PHP. Unfortunately error_log has some limitations as you found out.
This is a long answer, read on to find out about:
Errors
Logging the error directly vs trigger_error and set_error_handler
Where good errors go bad - Fatal Errors.
Exceptions
SPL
What to do with them?
Code
Setup
Usage
TL;DR Use trigger_error for raising errors and set_error_handler for logging them.
Errors
=========
When things don't go as expected in your program, you will often want to raise an error so that someone or something is notified. An error is for a situation where the program may continue, but something noteworthy, possibly harmful or erroneous has occurred. At this point many people want to log the error immediately with their logging package of choice. I believe this is exactly the wrong thing to do. I recommend using trigger_error to raise the error so that it can be handled with a callback set by set_error_handler. Lets compare these options:
Logging the error directly
So, you have chosen your logging package. Now you are ready to spread the calls to your logger wherever an error occurs in your code. Lets look at a single call that you might make (I'll use a similar logger to the one in Jack's answer):
Logger::getLogger('standard')->error('Ouch, this hurts');
What do you need in place to run this code?
Class: Logger
Method: getLogger
Return: Object with method 'error'
These are the dependencies that are required to use this code. Everyone who wants to re-use this code will have to provide these dependencies. This means that a standard PHP configuration will no longer be sufficient to re-use your code. With the best case, using Dependency Injection you still require a logger object to be passed into all of your code that can emit an error.
Also, in addition to whatever the code is responsible for, it also has responsibility for logging the error. This goes against the Single Responsibility Principle.
We can see that logging the error directly is bad.
trigger_error to the rescue
PHP has a function called trigger_error which can be used to raise an error just like the standard functions do. The error levels that you use with it are defined in the error level constants. As a user you must use one of the user errors: E_USER_ERROR, E_USER_WARNING or the default value E_USER_NOTICE (other error levels are reserved for the standard functions etc.). Using a standard PHP function to raise the error allows the code to be re-used with any standard PHP installation! Our code is no longer responsible for logging the error (only making sure that it is raised).
Using trigger_error we only perform half of the error logging process (raising the error) and save the responsibility of responding to the error for the error handler which will be covered next.
Error Handler
We set a custom error handler with the set_error_handler function (see the code setup). This custom error handler replaces the standard PHP error handler that normally logs messages in the web server error log depending on the PHP configuration settings. We can still use this standard error handler by returning false within our custom error handler.
The custom error handler has a single responsibility: to respond to the error (including any logging that you want to do). Within the custom error handler you have full access to the system and can run any sort of logging that you want. Virtually any logger that uses the Observer design pattern will be ok (I'm not going to go into that as I believe it is of secondary importance). This should allow you to hook in new log observers to send the output to where you need it.
You have complete control to do what you like with the errors in a single maintainable part of your code. The error logging can now be changed quickly and easily from project to project or within a single project from page to page. Interestingly even # suppressed errors make it to the custom error handler with an errno of 0 which if the error_reporting mask is respected should not be reported.
When Good Errors go Bad - Fatal Errors
It is not possible to continue from certain errors. The following error levels can not be handled from a custom error handler: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING. When these sorts of errors are triggered by a standard function call the custom error handler is skipped and the system shuts down. This can be generated by:
call_this_function_that_obviously_does_not_exist_or_was_misspelt();
This is a serious mistake! It is impossible to recover from, and the system is about to shut down. Our only choice is to have a register_shutdown_function deal with the shutdown. However this function is executed whenever a script completes (successful, as well as unsuccessful). Using this and error_get_last some basic information can be logged (the system is almost shutdown at this point) when the last error was a fatal error. It can also be useful to send the correct status code and show an Internal Server Error type page of your choosing.
Exceptions
=============
Exceptions can be dealt with in a very similar way to basic errors. Instead of trigger_error an exception will be thrown by your code (manually with throw new Exception or from a standard function call). Use set_exception_handler to define the callback you want to use to handle the exception with.
SPL
The Standard PHP Library (SPL) provides exceptions. They are my preferred way of raising exceptions because like trigger_error they are a standard part of PHP which does not introduce extra dependencies to your code.
What to do with them?
When an exception is thrown there are three choices that can be made:
Catch it and fix it (the code then continues as if nothing bad happened).
Catch it, append useful information and re-throw it.
Let it bubble up to a higher level.
At each level of the stack these choices are made. Eventually once it bubbles up to the highest level the callback you set with set_exception_handler will be executed. This is where your logging code belongs (for the same reasons as the error handling) rather than spread throughout catch statements in your code.
3. Code
Setup
Error Handler
function errorHandler($errno , $errstr, $errfile, $errline, $errcontext)
{
// Perform your error handling here, respecting error_reporting() and
// $errno. This is where you can log the errors. The choice of logger
// that you use is based on your preference. So long as it implements
// the observer pattern you will be able to easily add logging for any
// type of output you desire.
}
$previousErrorHandler = set_error_handler('errorHandler');
Exception Handler
function exceptionHandler($e)
{
// Perform your exception handling here.
}
$previousExceptionHandler = set_exception_handler('exceptionHandler');
Shutdown Function
function shutdownFunction()
{
$err = error_get_last();
if (!isset($err))
{
return;
}
$handledErrorTypes = array(
E_USER_ERROR => 'USER ERROR',
E_ERROR => 'ERROR',
E_PARSE => 'PARSE',
E_CORE_ERROR => 'CORE_ERROR',
E_CORE_WARNING => 'CORE_WARNING',
E_COMPILE_ERROR => 'COMPILE_ERROR',
E_COMPILE_WARNING => 'COMPILE_WARNING');
// If our last error wasn't fatal then this must be a normal shutdown.
if (!isset($handledErrorTypes[$err['type']]))
{
return;
}
if (!headers_sent())
{
header('HTTP/1.1 500 Internal Server Error');
}
// Perform simple logging here.
}
register_shutdown_function('shutdownFunction');
Usage
Errors
// Notices.
trigger_error('Disk space is below 20%.', E_USER_NOTICE);
trigger_error('Disk space is below 20%.'); // Defaults to E_USER_NOTICE
// Warnings.
fopen('BAD_ARGS'); // E_WARNING fopen() expects at least 2 parameters, 1 given
trigger_error('Warning, this mode could be dangerous', E_USER_WARNING);
// Fatal Errors.
// This function has not been defined and so a fatal error is generated that
// does not reach the custom error handler.
this_function_has_not_been_defined();
// Execution does not reach this point.
// The following will be received by the custom error handler but is fatal.
trigger_error('Error in the code, cannot continue.', E_USER_ERROR);
// Execution does not reach this point.
Exceptions
Each of the three choices from before are listed here in a generic way, fix it, append to it and let it bubble up.
1 Loggable. Let it bubble up:
// Don't catch it.
// Either it will be caught by error handler
// Or PHP will log it as a fatal error
2 Fixable:
try
{
$value = code_that_can_generate_exception();
}
catch (Exception $e)
{
// We decide to emit a notice here (a warning could also be used).
trigger_error('We had to use the default value instead of ' .
'code_that_can_generate_exception\'s', E_USER_NOTICE);
// Fix the exception.
$value = DEFAULT_VALUE;
}
// Code continues executing happily here.
3 Append:
Observe below how the code_that_can_generate_exception() does not know about $context. The catch block at this level has more information which it can append to the exception if it is useful by rethrowing it.
try
{
$context = 'foo';
$value = code_that_can_generate_exception();
}
catch (Exception $e)
{
// Raise another exception, with extra information and the existing
// exception set as the previous exception.
throw new Exception('Context: ' . $context, 0, $e);
}
It has been requested to make this answer more applicable to a larger audience, so here goes.
Preamble
Error handling is usually not the first thing you will want to think about when writing an application; as an indirect result it gets bolted on as the need arises. However, it doesn't have to cost much to leverage existing mechanisms in PHP either.
It's a fairly lengthy article, so I've broken it down into logical sets of text.
Triggering errors
Within PHP there are two distinct ways for errors to get triggered:
Errors from PHP itself (e.g. using undefined variables) or internal functions (e.g. imagecreatefromjpeg could not open a file),
Errors triggered by user code using trigger_error,
These are usually printed on your page (unless display_errors is switched off or error_reporting is zero), which should be standard for production machines unless you write perfect code like me ... moving on); those errors can also be captured, giving you a glimpse into any hitch in the code, by using set_error_handler explained later.
Throwing exceptions
Exceptions are different from errors in three main ways:
The code that handles them may be far removed from the place where they are thrown from. The variable state at the origin must be explicitly passed to the Exception constructor, otherwise you only have the stack trace.
The code between the exception and the catch is skipped entirely, whereas after an error occurs (and it was not fatal) the code still continues.
They can be extended from the main Exception class; this allows you to catch and handle specific exceptions but let others bubble down the stack until they're caught by other code. See also: http://www.php.net/manual/en/language.exceptions.php
An example of throwing exceptions is given later on.
Handling errors
Capturing and handling errors is pretty straightforward by registering an error handler, e.g.:
function my_error_handler($errno, $errstr, $errfile = 'unknown', $errline = 0, array $errcontext = array())
{
// $errcontext is very powerful, it gives you the variable state at the point of error; this can be a pretty big variable in certain cases, but it may be extremely valuable for debugging
// if error_reporting() returns 0, it means the error control operator was used (#)
printf("%s [%d] occurred in %s:%d\n%s\n", $errstr, $errno, $errfile, $errline, print_r($errcontext, true));
// if necessary, you can retrieve the stack trace that led up to the error by calling debug_backtrace()
// if you return false here, the standard PHP error reporting is performed
}
set_error_handler('my_error_handler');
For kicks, you can turn all the errors into an ErrorException as well by registering the following error handler (PHP >= 5.1):
function exception_error_handler($errno, $errstr, $errfile, $errline)
{
throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
}
set_error_handler("exception_error_handler");
Handling exceptions
In most cases you handle exceptions as close as possible to the code that caused it to allow for backup plans. For instance, you attempt to insert a database record and a primary key constraint exception is thrown; you can recover by updating the record instead (contrived as most databases can handle this by themselves). Some exceptions just can't be handled locally, so you want those to cascade down. Example:
function insertRecord($user, $name)
{
try {
if (true) {
throw new Exception('This exception should not be handled here');
}
// this code is not executed
$this->db->insert('users', array('uid' => $user, 'name' => $name));
} catch (PDOException $e) {
// attempt to fix; an exception thrown here will cascade down
throw $e; // rethrow exception
// since PHP 5.3.0 you can also nest exceptions
throw new Exception("Could not insert '$name'", -1, $e);
} catch (WhatEverException $e) {
// guess what, we can handle whatever too
}
}
The slippery exception
So what happens when you don't catch an exception anywhere? You can catch that too by using set_exception_handler.
function my_exception_handler(Exception $exception)
{
// do your stuff here, just don't throw another exception here
}
set_exception_handler('my_exception_handler');
This is not encouraged unless you have no meaningful way to handle the exception anywhere in your code.
Logging the error / exception
Now that you're handling the error you have to log it somewhere. For my example, I use a project that Apache ported from Java to PHP, called LOG4PHP. There are others, but it illustrates the importance of a flexible logging facility.
It uses the following concepts:
Loggers - named entities that perform logging upon your behalf; they can be specific to a class in your project or shared as a common logger,
Appenders - each log request can be sent to one or more destinations (email, database, text file) based on predefined conditions (such as log level),
Levels - logs are classified from debug messages to fatal errors.
Basic usage to illustrate different message levels:
Logger::getLogger('main')->info('We have lift off');
Logger::getLogger('main')->warn('Rocket is a bit hot');
Logger::getLogger('main')->error('Houston, we have a problem');
Using these concepts you can model a pretty powerful logging facility; for example, without changing above code, you can implement the following setup:
Collect all debug messages in a database for developers to look at; you might disable this on the production server,
Collect warnings into a daily file that you might email at the end of the day,
Have immediate emails sent on fatal errors.
Define it, then use it :)
define('ERRORLOG_PATH', '/var/tmp/my-errors.log');
error_log($e->getMessage(), 3, ERRORLOG_PATH);
Alternatively just make the third parameter of error_log optional, defaulting it to the path you want.
As an addition, for error logging (and in fact all logging) I would use event dispatcher, in a way that symfony framework does.
Take a look at this sf component (its very lightweight dependency, entire framework is not required, there are maybe 3 relevant php classes and 2 interfaces)
https://github.com/symfony/EventDispatcher
this way you can create dispatcher somewhere in your application bootstrap:
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;
$dispatcher = new EventDispatcher();
//register listeners
$dispatcher->addListener('application.log', function (Event $event) {
//do anything you want
});
Then you can raise an event in any place of your code by something like
$dispatcher->dispatch(new GenericEvent('application.log', array('message' => 'some log', 'priority' => 'high'));
Of course you can subclass event class with your own events:
class LogEvent extends GenericEvent {
public function __construct($message, $priority = 'INFO') {
parent::__construct('application.log', array('message'=>$message,'priority'=>$priority));
}
public function getMessage() { return $this->getArgument('message'); }
public function getPriority() { return $this->getArgument('priority'); }
}
// now raising LogEvent is much cleaner:
$dispatcher->dispatch(new LogEvent('some log'));
This will also allow you to create more customized events like ExceptionEvent
class ExceptionEvent extends GenericEvent {
public function __construct(Exception $cause) {
parent::__construct('exception.event', array('cause' => $cause));
}
}
And handle them accordingly.
Advantages
you separate logging logic from your application
you can easily add and remove loggers in runtime
you can easily register as many loggers you want (i.e. DebugLogger which logs everything into text file, ErrorLogger which logs only errors to error_log, CriticalLogger which logs only critical errors on production environment and sends them by email to administrator, etc.)
you can use event dispatcher for more things than just logging (in fact for every job for which observer pattern is appropriate)
actual logger becomes nothing more than 'implementation detail' - it's so easy to replace that it doesn't matter where your logs go - you will be able to replace log destination at any time without having to refactor names of your methods, or changing anything in code.
it will be easy to implement complex log routing logic or globally change log format (by configuring loggers)
everything becomes even more flexible if you use dependency injection for both listeners (loggers) and dispatcher (into classes that notifies log event)
Actual Logging
As someone already stated, I would advice to go with out-of-the-box library, like mentioned Monolog, Zend_Log or log4php, there is probably no reason to code these things by hand (and the last thing you want is broken error logger!)
PS: Treat code snippets as pseudo-code, I didn't test them. Details can be found in docs of mentioned libraries.
If you still need a custom way of handling logs (i.e. you don't want to use standard trigger_error()), I'd recommend looking at Zend_Log (http://framework.zend.com/manual/en/zend.log.overview.html) for these reasons:
this can be used as a standalone component, ZF is not a full-stack framework. You may copy only Zend_Loader and Zend_Log namespaces , instantiate Zend_Loader and use it. See below:
require_once('Zend/Loader/Autoloader.php');
$loader = Zend_Loader_Autoloader::getInstance();
$logger = new Zend_Log();
$writer = new Zend_Log_Writer_Stream('php://output');
$logger->addWriter($writer);
$logger->log('Informational message', Zend_Log::INFO);
You were offered many logging libraries, but I believe that Zend team (founders of PHP lang) know what they do
You may use any writers (database, STDOUT - see above, file, whatever, you may customize it to write your own to post log messages to a web service even)
log levels
may change log format (but the one that is out-of-box is great to my mind). The above example with standard formatter will produce something like this:
2012-05-07T23:57:23+03:00 INFO (6): Informational message
just read the reference, it may be configured to catch php errors
If the PHP way of handling errors is not flexible enough for you (e.g. sometimes you want to log to database, sometimes to file, sometimes whatever else), you need to use / create a custom PHP logging framework.
You can browse through the discussion in https://stackoverflow.com/questions/341154/php-logging-framework or just go and give the top choice, KLogger, a try. I am not sure, though, if it supports custom destinations for logging. But at the very least, it's a small and easy-to-read class and you should be able to extend it further for your own needs.
I'd go with Tom vand der Woerdt's logging solution, simplest and most effective for your requirements.
As for the other question:
You do not need to catch / rethrow the exception inside the function unless there is a specific kind of exception you have a solution for.
Somewhat simplistic example:
define('ERRORLOG_PATH', '/var/tmp/my-errors.log');
function do_something($in)
{
if (is_good($in))
{
try {
return get_data($in);
} catch (NoDataException $e) {
// Since it's not too big a deal that nothing
// was found, we just return false.
return false;
}
} else {
throw new InvalidArguementException('$in is not good');
}
}
function get_data($data)
{
if (!is_int($data))
{
InvalidArguementException('No');
}
$get = //do some getting.
if (!$get)
{
throw new NoDataException('No data was found.');
} else {
return $get;
}
}
try {
do_something('value');
} catch (Exception $e) {
error_log($e->getMessage(), 3, ERRORLOG_PATH);
die ('Something went wrong :(');
}
Here you'd only catch the NoDataException because you have some other logic to sort that out, all other errors fall though to the first catch and are handled by the top catch because all thrown exceptions must at some point in their hierarchy inherit from Exception.
Obviously if you throw an Exception again (outside the initial try {} or in the top catch {}) your script will exit with an Uncaught Exception error and error logging is lost.
If you wanted to go all the way, you could also implement a custom error handling function using set_error_handler() and put your logging in there too.
There are two challenges to meet. The first is to be flexible in logging to different channels. In this case you should take a look at for example Monolog.
The second challenge is to weave in that logging into your application. Imho the best case is no to use logging explicitly. Here for example aspect orientation comes in handy. A good sample is flow3.
But this is more a bird's eye view on the problem...
I use my own function which allows me to write multiple types of log files by setting or changing the second parameter.
I get past the conceptual questions you are asking about "what is the right way" to do it, by including the log function in a library of functions that I consider "native" to my development projects.
That way I can consider those functions to be just part of "MY" php core, like date() or time()
In this basic version of dlog, I also handle arrays. while I originally used this to log errors, I ended up using it for other 'quick and dirty' short term tracking such as logging the times that the code entered a certain section, and user logins, etc.
function dlog($message,$type="php-dlog")
{
if(!is_array($message) )
$message=trim($message);
error_log(date("m/d/Y h:i:s").":".print_r($message,true)."\n",3, "/data/web/logs/$_SERVER[HTTP_HOST]-$type.log");
}
Most error loggers and exception loggers are useless to most people because they haven't got access to the log files.
I prefer to use a custom error handler and a custom exception handler and have those, during production, log errors directly to the database if the system is running on a database.
During development, when display_errors are set, they log nothing as all errors gets raised in the browser.
And as a side note to that: Don't make your custom error handler throw exceptions! It's a really bad idea. It can cause bugs in the buffer handler and in some of the extensions. Also some core PHP functions like fopen() causes a warning or notice on failure, these should be dealt with accordingly and should not halt the application has an exception would do.
The mention of having the error handler throwing exceptions in the PHP documentation is a note bug.
As KNL states, which is quite right, but unfortunately as of yet undocumented, having errors throwing exceptions is not something recommended by the PHP developers and someone made a mistake in the documentation. It can indeed cause bugs with many extensions so don't do it.
This has already been debated on #PHP on irc.
The "However, errors can be simply translated to exceptions with ErrorException." on http://php.net/manual/en/language.exceptions.php is going to be removed.

Why should I use exception handling in php?

I've been programming PHP for a long time, but not so much PHP 5... I've known about exception handling in PHP 5 for some time, but never really looked into it. After a quick Google it seems fairly pointless to use exception handling - I can't see the advantages of using it over just using some if() {} statements, and perhaps my own error handling class or whatever.
There's got to be a bunch of good reasons for using it (I guess?!) otherwise it wouldn't have been put into the language (probably). Can anyone tell me of some good benefits it has over just using a bunch of if statements or a switch statement or something?
Exceptions allow you to distinguish between different types of errors, and is also great for routing. For example...
class Application
{
public function run()
{
try {
// Start her up!!
} catch (Exception $e) {
// If Ajax request, send back status and message
if ($this->getRequest()->isAjax()) {
return Application_Json::encode(array(
'status' => 'error',
'msg' => $e->getMessage());
}
// ...otherwise, just throw error
throw $e;
}
}
}
The thrown exception can then be handled by a custom error handler.
Since PHP is a loosely typed language, you might need to ensure that only strings are passed as arguments to a class method. For example...
class StringsOnly
{
public function onlyPassStringToThisMethod($string)
{
if (!is_string($string)) {
throw new InvalidArgumentException('$string is definitely not a string');
}
// Cool string manipulation...
return $this;
}
}
...or if you need to handle different types of exceptions in different ways.
class DifferentExceptionsForDifferentFolks
{
public function catchMeIfYouCan()
{
try {
$this->flyForFree();
} catch (CantFlyForFreeException $e) {
$this->alertAuthorities();
return 'Sorry, you can\'t fly for free dude. It just don\'t work that way!';
} catch (DbException $e) {
// Get DB debug info
$this->logDbDebugInfo();
return 'Could not access database. What did you mess up this time?';
} catch (Exception $e) {
$this->logMiscException($e);
return 'I catch all exceptions for which you did not account!';
}
}
}
If using transactions in something like Zend Framework:
class CreditCardController extends Zend_Controller_Action
{
public function buyforgirlfriendAction()
{
try {
$this->getDb()->beginTransaction();
$this->insertGift($giftName, $giftPrice, $giftWowFactor);
$this->getDb()->commit();
} catch (Exception $e) {
// Error encountered, rollback changes
$this->getDb()->rollBack();
// Re-throw exception, allow ErrorController forward
throw $e;
}
}
}
Exception handling: If condition versus Exception isn't specific to PHP, but gives a good perspective. Personally, Exception(s) & try/catch are implemented in languages to enforce good behaviour amongst developers that normally wouldn't be as attentive to error checking / handling.
If you are confident that your if/else if/else is catching all scenarios, than cool.
Here is an overview of the Advantages of Exceptions - Java -- At one point, there is a snippet of code that has many if/else statements and the following excerpt:
There's so much error detection, reporting, and returning here that the original seven lines of code are lost in the clutter. Worse yet, the logical flow of the code has also been lost, thus making it difficult to tell whether the code is doing the right thing: Is the file really being closed if the function fails to allocate enough memory? It's even more difficult to ensure that the code continues to do the right thing when you modify the method three months after writing it. Many programmers solve this problem by simply ignoring it — errors are reported when their programs crash.
So really, it comes down to personal preference in the end. If you want code that is readable and can be consumed by other people, it's generally a better approach and enforces best-behaviour
If you are following the object-oriented methodology then exceptions comes handy for the error handling. It is convenient to communicate the errors through exception across the objects.
Exceptions are really helpful if you go with layered design approach.
If you are not coding in object-oriented way, then exceptions are not required.
We use exception handling if we are not sure about the code results. We put that snippet of code in try block and catch that error in catch block. Please check this link for more information.
In general there are two good reasons to use exception handling:
You might now always know where an exception will occur - something unexpected could arise. If you use a global exception handler you can make sure that no matter what goes wrong, your program has a chance to recover. Similarly a particularly sensitive piece of code (like something that does I/O) could have all sorts of different errors that can only be detected at runtime and you want to catch any possible contingency. Some things might not occur during testing; like what if a server outside of your control fails? This may never be tested before it really happens (although good testing would include this). This is the more important reason really.
Performance. Typically exceptions are implemented so that everything is fast so long as nothing goes wrong. Exceptions are caught after they occur. This means that no 'if' statement has to be evaluated in advance if something goes wrong, and the overhead is very low in that case. If you don't use exceptions you will be forced to add a lot of 'if' statements to your code. While usually this isn't much of a problem, this can kill a performance-critical application. This is especially true because a branch mis-prediction in the CPU can cause a pipeline flush.
I that reason is that Exception is called after trigger_error(); function and you can send also some additional information to that exception = better debugging?
I'm not sure but I think that's it
example:
class db
{
function connect()
{
mysql_Connect("lolcalhost", "root", "pass:)") or trigger_error("Test");
}
}
try
{
}
catch (db
One of the primary reasons for having an exceptions framework is so that if the code ever gets to the point where it cannot proceed, it has the ability to tell the surrounding context that something has gone wrong. It means that if I have a class Foo which needs to have $fooInstance->setBarHandler($barHandler) called before $fooInstance->doFoo(); can succeed, the class can provide a message to the greater context instead of failing silently and returning FALSE. Further, it allows the context to say, "Huh. That broke. Well, I can now tell the user/logs/something else that something bad happened, and I can decide whether I need to keep on chugging."
Exceptions can provide much more data than simple -1 or false.
Exceptions can do advanced error handling. Keep in mind that try .. catch blocks can be nested and there could be more than one catch block in try .. catch block.
Exceptions force you to handle errors. When you're not using them you do something like:
function doSomething($a, $b, $c) {
...
if ($a < $b && $b > $c) {
return -1; // error
}
...
}
$d = doSomething($x, $y, $z);
if ($d === -1) {
die('Fatal error!');
}
And everything is fine as long as you remember to check whether function returned error. But what happen if you forgot to check that? It's actually a quite common problem.
Exceptions make the flow of a program much more natural.
Exceptions are hard to use in the correct context,especially in php. Personally i use exceptions when these 3 things happen:
Resource failure exception - You can throw an exception maybe when your program runs out of memory. for example in php when you run a script that exceeds 30 seconds executing. Though you can chanage that in .ini
Client code errors exceptions - For example when trying to connect to a database with the wrong credentials or unlinking a file not on server. Or when the database server is down and unresponsive, you can throw an exception.
Programmer error exception - These are errors generated due to your own coding problems.This can also be used when you are not sure of the results your code will give you. for example when dividing by 0.

Exceptions in PHP. How to use them? How to extend 'em?

Few days ago I deal with errors like this...
exit( 'Error!' );
or exit( 'Error!' );
Doh, right? =] Now I'm trying to learn Exceptions. This is how far I got...
http://pastie.org/1555970
Is that correct use of them? It would be cool that I can have more info about 'em. Like file where exception is thrown and line of that file. I know that there are build-in methods (in Exception class), but I want to somehow extend it so I don't need to write...
throw new My_Exception( '...' );
catch( My_Exception $e ) {
echo $e->my_method();
}
...but use old syntax.
throw new Exception( '...' );
catch( Exception $e ) {
echo $e->getMessage();
}
...or maybe you have any greater thought of Exceptions? How to deal with them? Help me! =]
In general you only need to echo/log exceptions, that cannot be otherwise handled. This pretty much means, that if you put your entire application within try block, there's only one place where you need to put echoing/logging logic (i.e. the catch block associated with the outermost try block).
If on the other hand the exception can be handled without stopping the application (in your example this could be providing a default numeric value, instead of incorrect value), then there's usually no need to echo/log it.
If you do want to log such exceptions (for debugging for example), then your application should contain a logging framework, so that it's enough to do in your catch blocks something like
} catch (Exception $e) {
ExceptionLogger::log($e); //static method == ugly, but it's for simplicity in this example
// do whatever needs to be done
}
log() method above would check if the logging is enabled, and if it is savenecessary data to a file.
Exceptions should be typed based upon the error that you find. The Spl Exceptions are a good start, but you really should be creating your own exceptions as well. Some common ones that I use:
FileNotFoundException extends RuntimeException <- self explanatory
HTTPException extends RuntimeException <- Used for http classes when a non-200 result is encountered
DatabaseQueryException extends LogicException <- Used for database query errors
Now, by typing them specifically, it lets you handle the errors in your code. So let's say that you want to fetch a HTTP resource. If that fails with anything but a 404, you want to try a backup URL. You could do that with:
try {
return getHttp($url1):
} catch (HttpException $e) {
if ($e->getCode() != 404) {
try {
return getHttp($url2);
} catch (HttpException $e2) {
//It's ok to ignore this, since why know it's an HTTP error and not something worse
return false;
}
} else {
return false;
}
}
As far as your example code that you posted, I would change a few things:
Change the thrown exception to InvalidArgumentException since it has more semantic meaning (I almost never throw a raw exception).
You should try to avoid catch(Exception $e) at all costs. You have no idea what exception was thrown, so how can you possibly handle it?
Only catch exceptions that you are reasonably sure you know how to handle (and outputting an error/logging is not handling, it's removing the usefulness of the exception). You should never see something like catch($e) { logerror($e); } or catch($e) { print $e->getMessage(); } since netiher is actually handling the exception.
If you don't fix or workaround the cause of the exception in your catch block, you should re-throw it. Let the code above you in the stack try to handle it. This is especially true with libraries and classes that are reused all over the place.
Now, with user interfaces, it may be acceptable to catch the exception and show the user an error message. So your example where you print the exception's message might be ok, but you'd really need to think about the use-cases of it. Are you calling it from a model or controller? If so, it may be ok display an error message. Are you calling it from a library? If so, it's probably better to let the exception bubble up.
Also, don't use a global try{} catch() {} block. Instead, install an Exception Handler to handle it for you. It's cleaner, and more semantically correct (since any try{}catch{} implies that you know how to handle the exception that you caught, whereas the exception handler is precisely meant for exceptions that weren't handled because you didn't know how to handle them.
Exceptions are for exceptional circumstances. Do not use them for all error conditions. If a user submits a password that's too short, don't throw an exception, handle that in validation. But if your hash function is expecting sha256 to be available and it isn't, that's a time for an exception. Exceptions are useful for program errors (when a condition that is unexpected happens, such as invalid input to a function), state errors (when the application enters a state that is unknown or unstable, such as if the requested view does not exist) and runtime errors (when the application encounters an error that can only be detected at runtime, such as a file-not-found error).
There is an entire page of the PHP manual devoted to extending exceptions and that page also gives you a lot of information on the methods to identify file/line number, backtrace etc. where the exception was thrown. This is the type of information that is extremely useful for debugging.

Exceptions: Is this a good practice?

This is written in PHP, but it's really language agnostic.
try
{
try
{
$issue = new DM_Issue($core->db->escape_string($_GET['issue']));
}
catch(DM_Exception $e)
{
throw new Error_Page($tpl, ERR_NOT_FOUND, $e->getMessage());
}
}
catch(Error_Page $e)
{
die($e);
}
Is nested try, catch blocks a good practice to follow? It seems a little bulky just for an error page - however my Issue Datamanager throws an Exception if an error occurs and I consider that to be a good way of error detecting.
The Error_Page exception is simply an error page compiler.
I might just be pedantic, but do you think this is a good way to report errors and if so can you suggest a better way to write this?
Thanks
You're using Exceptions for page logic, and I personally think that's not a good thing. Exceptions should be used to signal when bad or unexpected things happen, not to control the output of an error page. If you want to generate an error page based on Exceptions, consider using set_exception_handler. Any uncaught exceptions are run through whatever callback method you specify. Keep in mind that this doesn't stop the "fatalness" of an Exception. After an exception is passed through your callback, execution will stop like normal after any uncaught exception.
I think you'd be better off not nesting. If you expect multiple exception types, have multiple catches.
try{
Something();
}
catch( SpecificException se )
{blah();}
catch( AnotherException ae )
{blah();}
The ideal is for exceptions to be caught at the level which can handle them. Not before (waste of time), and not after (you lose context).
So, if $tpl and ERR_NOT_FOUND are information which is only "known" close to the new DM_Issue call, for example because there are other places where you create a DM_Issue and would want ERR_SOMETHING_ELSE, or because the value of $tpl varies, then you're catching the first exception at the right place.
How to get from that place to dying is another question. An alternative would be to die right there. But if you do that then there's no opportunity for intervening code to do anything (such as clearing something up in some way or modifying the error page) after the error but before exit. It's also good to have explicit control flow. So I think you're good.
I'm assuming that your example isn't a complete application - if it is then it's probably needlessly verbose, and you could just die in the DM_Exception catch clause. But for a real app I approve of the principle of not just dying in the middle of nowhere.
Depending on your needs this could be fine, but I am generally pretty hesitant to catch an exception, wrap the message in a new exception, and rethrow it because you loose the stack trace (and potentially other) information from the original exception in the wrapping exception. If you're sure that you don't need that information when examining the wrapping exception then it's probably alright.
I'm not sure about PHP but in e.g. C# you can have more then one catch-Block so there is no need for nested try/catch-combinations.
Generally I believe that errorhandling with try/catch/finally is always common-sense, also for showing "only" a error-page. It's a clean way to handle errors and avoid strange behavior on crashing.
I wouldn't throw an exception on issue not found - it's a valid state of an application, and you don't need a stack trace just to display a 404.
What you need to catch is unexpected failures, like sql errors - that's when exception handling comes in handy. I would change your code to look more like this:
try {
$issue = DM_Issue::fetch($core->db->escape_string($_GET['issue']));
}
catch (SQLException $e) {
log_error('SQL Error: DM_Issue::fetch()', $e->get_message());
}
catch (Exception $e) {
log_error('Exception: DM_Issue::fetch()', $e->get_message());
}
if(!$issue) {
display_error_page($tpl, ERR_NOT_FOUND);
}
else
{
// ... do stuff with $issue object.
}
Exceptions should be used only if there is a potentially site-breaking event - such as a database query not executing properly or something is misconfigured. A good example is that a cache or log directory is not writable by the Apache process.
The idea here is that exceptions are for you, the developer, to halt code that can break the entire site so you can fix them before deployment. They are also sanity checks to make sure that if the environment changes (i.e. somebody alters the permissions of the cache folder or change the database scheme) the site is halted before it can damage anything.
So, no; nested catch handlers are not a good idea. In my pages, my index.php file wraps its code in a try...cache block - and if something bad happens it checks to see if its in production or not; and either emails me and display a generic error page, or shows the error right on the screen.
Remember: PHP is not C#. C# is (with the exception (hehe, no pun intended :p) of ASP.net) for applications that contain state - whereas PHP is a stateless scripting language.

Categories