Related
My php setup currently records errors in the standard php_errorlog file. These errors tell me things like the type of error and the line it occurred on (standard stuff).
I'd like to add more information to this log, but only when an error occurs.
For example, I'd like to create a variable $error_details at the top of my script, into which I'd put things like the id of the user logged in at the time, and the php://input details.
I know I can write error_log($error_details), but this would record every time my script runs. I want it to record only when there is an error.
You can use the error_get_last function.
if (error_get_last()) {
//Error occurred
}
There are several ways to do that control error logs with libraries or self coding with tomuch works.
Monolog - Logging for PHP
Library and nice examples for basic types on Loggy
PHP Error Log Types
PHP provides a variety of error log types for identifying the severity of errors encountered when your application runs.
The error levels indicate if the engine couldn’t parse and compile your PHP statements, or couldn’t access or use a resource needed by your application, all the way down to letting you know if you have a possible typo in a variable name.
Although the error levels are integer values, there are predefined constants for each one.
Using the constants will make your code easier to read and understand and keep it forward-compatible when new error levels are introduced.
Common error levels encountered include:
Check for Predefined Constants
And here nice Tutorials : Ultimate Guide to Logging
Another Way is the base exception
PHP has a base Exception class that is available by default in the language.
The base Exception class is extensible if required.
Example: (assumes directive log_errors = 1).
try{
if(true){
throw new Exception("Something failed", 900);
}
} catch (Exception $e) {
$datetime = new DateTime();
$datetime->setTimezone(new DateTimeZone('UTC'));
$logEntry = $datetime->format('Y/m/d H:i:s') . ‘ ‘ . $e;
// log to default error_log destination
error_log($logEntry);
//Email or notice someone
//OR use fopen(); function and write in to error_log file
}
The example above would produce the following error log entry:
2020/01/05 15:02:24/Something failed/900//var/www/html/index.php/4
I'm trying to connect to a LDAP server using Laravel. It is important to say that I'm using the PHP functions ldap_connect and ldap_bind instead of using a package to handle it.
The point is that when I provide wrong user and password, the ldap_bind function gives to us a PHP warning. I'm OK with this warning and, as is in the documentation, the function returns false when the bind does not occur.
But, Laravel is throwing an exception when this warning is triggered. This is not an exception, Laravel should not throw an exception and I wouldn't like to handle this as an exception; I just have to build an if condition that will return a message to the user.
Does Laravel catch all warnings as an Exception?
This is the intended behavior for Laravel. Laravel will turn any error into an ErrorException instance. Here's the bootstrap() method inside the Illuminate/Foundation/Bootstrap/HandleExceptions.php class.
public function bootstrap(Application $app)
{
$this->app = $app;
error_reporting(-1);
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
register_shutdown_function([$this, 'handleShutdown']);
if (! $app->environment('testing')) {
ini_set('display_errors', 'Off');
}
}
The error_reporting(-1); will set PHP to report all errors (read more here).
While this part of code:
set_error_handler([$this, 'handleError']);
Will set a custom error handler. If you check the handleError() method, it's pretty clear that Laravel will turn any error into an ErrorException instance.
public function handleError($level, $message, $file = '', $line = 0, $context = [])
{
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
}
}
Read more about user-defined error handler here.
Hope this clear things up. :)
Not sure exactly the reason because I didn't write it but I assume having it work this way makes logging much easier.
Warnings get logged in php's error log but it's not possible to pass additional information along with it as context.
If that warning gets turned into an exception though, then you can pass the exception to the logger as well as other information such as the route which was hit, the request variables, etc... and log everything together. You also get to dictate the severity of the issue by designating it as log, debug, info, notice, warning, error, critical, alert, and emergency and through monolog, handle each of those as you see fit. This creates a log which is much easier to read and makes your code much easier to debug.
Additionally, as others have pointed out, it also allows you to do your work in try catch blocks which I believe also creates neater and cleaner code than trying to check if some variable === false
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.
I've seen lots of tutorials that demo simple try catches with, say, the act of opening a file. But I've never seen a big, "real" example. Can someone provide me with a few cases in which they have or would use exceptions? And is it really necessary to extend the exception class just to throw an exception? And finally, when throwing exceptions, does it cause the script to exit(); ? Or, does it log it and continue on with script execution?
We use exceptions extensively within our projects.
One specific instance is for actions that require the user to be logged in or upon registration. We use Exceptions for flow control on error conditions. If the current user is not logged in we throw an exception. The exception handler then redirects them to the loggin page.
Using our registration action as an example, we extend the Exception like this:
class RegistrationFailed extends Exception {}
Now in our catch statement within the registration code we can test for the RegistrationFailed exception and handle it accordingly. Otherwise, when the exception is not a RegistrationFailed, we allow it to bubble up because we are not interested in it.
try {
// do registration here
}
catch(RegistrationFailed $e) {
// handle the failed registration
}
catch(SomeOtherException $e) {
// handle other errors like this...
}
// All other errors will not be caught and will bubble up
Another example is within our wrapper classes which developers must extended. We use Reflection to ensure the child classes have properly implemented their methods and provided the correct interface. If not we notify the developer of that class via Exceptions letting them know a specific interface or method must be provided by the child class.
Edit:
I can already hear the comments about "You shouldn't use error handling for flow control!" however, for the project discussed above, it was necessary.
In the normal flow of the program a failed registration is expected due to the many validation rules that might fail, like a password that's too short.
However, it's an ajax application, so it's possible that someone might try to access an ajax url manually, when they are not logged in. This is as exception and thus we handle it as such.
Exceptions are meant to handle errors (at least in PHP). Suppose you are in a routine, and an error is occured that you can't handle in the current context.
Example:
<?php
/**
* #throws Exception_NoFile
*/
function read_file($file) {
if(!file_exists($file)) {
throw new Exception_NoFile($file);
}
/* ... nominal case */
}
In this situation you can't continue with the nominal case, becouse there is no file to process. You have to choose:
return with an invalid return value (this is the C practice, e.g: return -1 or using status flags)
throw an exception, and hope, someone will catch it above. If your client code excepts it, no problem, it may try an other path or rethrow an exception. If your client isn't ready to handle those situations where the requested file doesn't exist... your code will fail with an uncached exception, as it would do with a read of a nonexisting file in the other approach.
I feel a lot of people confuse 'failures' and 'exceptions' as the same thing. The word 'error' could refer to either but I use it for failure.
Failure - when the operation doesn't succeed
Exception - when an unexpected or out of the normal flow condition arises
For example, if a robot tries to walk to a destination and misses the mark - that's failure. But if it breaks a leg or the roof falls on it, that's an exception.
If the roof falls, I throw an exception that the roof fell.
If the robot misses the mark, I don't throw an Exception, I return false or return an error message like "Could not reach the destination because the roof fell."
try {
Walk to the cabinet;
}
catch (RoofFell_Exception $e) {
return "Could not reach the destination because the roof fell.";
}
catch (Legbroke_Exception $e) {
return "Could not reach the destination because a leg broke.";
}
if($current_location == 'cabinet') {
return "Destination reached";
}
return false;
Exception handling is tricky. It requires careful consideration of the project at hand and the way errors can be dealt with. You should try to define your Exceptions Guidelines early in your project and adhere to it.
I have written a general Exceptions Guidelines best practices that I have come up with after extensive research of the subject. Most of these guidelines can be used to all projects in any language that supports exceptions. Some of the guidelines will be Java specific. At the end you need to have a robust set of guidelines that can help you handle and deal with exceptions and error conditions.
Here are some points to consider
Don’t expose internal, implementation specific details to your clients
Avoid exposing internal implementation specific exceptions to your clients, especially those contained in a third party library. This is a general object oriented rule of thumb and it’s as valid for your exceptions hierarchy design. You have no control over the third party library which can change its exceptions signatures and break all of your API contracts with your clients. Instead wrap those third party exceptions (such as an SQLException) in your own custom exceptions. This way you’ll have much greater flexibility to change the third party library in the future without breaking your clients’ API contract.
Create your own exceptions hierarchy for complex projects
Generally speaking create your own exceptions hierarchy for more complex modules especially if you are dealing with implementation specific exceptions in third party libraries. Each of your packages/modules could have its own top-level generic exceptions. For Java at least one should be defined that inherits from RuntimeException. Wrap all implementation specific exceptions in your custom exceptions so that your clients should only depend on your custom exceptions and/or generic Java exceptions. This will give you greater flexibility to refactor the implementation specific code later without breaking your API contracts.
If more fine grained error handling is necessary then you can further subclass your custom exceptions to handle specific cases and allow for error recovery. For example if you are connecting to an SQL database you can throw a ConnectionTimeoutException in such a way that if needed the client can retry the connection N times before giving up. This way you can later change your database engine to NoSQL and still allow for reconnects and the client code will stay the same.
Document all exceptions
Carefully document all exceptions your package/module/app throws in the javadoc definition of each public method. Failing to do so will frustrate your API users and cause them to not trust your API docs. You don’t really want your clients to dig in your source just to find out that you are throwing a specific exception, right?
Throw exceptions as early as possible.
Check all inputs to your public API methods and throw an exception as soon as you find inconsistencies between your expected parameters and what has been supplied. The earlier you throw an exception the less will be the chance of data corruption, because bad data won’t make it into the deeper parts of your code. It also gives valuable feedback to your clients in a timely manner instead of deep in your code where something throws an obscure exception with a bad message such as ‘Internal Error’ or NullPointerException.
Log exceptions properly
Follow the guidelines of your logging framework in order to properly log exceptions with their message and stack trace. You don’t want to loose either
You should check out symfony framework - they really use a lot of Exceptions there.
They use Exception for configuration errors, say you forgot to put a file where the controller expects to find it - this will be an Exception, because there isn't anything framework can do it about it.
They use Exception for unknown errors: database failed for some weird reason, there's nothing framework can do about it - so it throws an Exception
And they have different Exception handlers for different environments. When exception occurs in "devel" mode, you get a nice page with stack trace and an explanation, when you are in "production" mode, you are redirect to custom 500 page.
Exceptions are simply a way to move edge cases or errors (which are really just big edge-case events) out of the larger body of code to stop them from making 99% of the basic stream of code cluttered with tons of switches/ifs.
You can think of them as a kind of reverse switch statement, where the events inside the try{} determine which, if any, catch block happens too.
What that means is that you don't ever have to use them if you don't like them. But they can make code easier to read.
A great place to use exception handling is when your program attempts to connect to, or access, I/O (files, databases, networking, devices).
Use exception handling when a block of calling code (function/method) attempts to access a file.
Use exception handling when a block of calling code (function/method) attempts a database connection.
Use exception handling when a block of calling code (function/method) attempts to run a query on a database (any attempt at accessing the database tables/views and such).
You could say the same about a network connection or access.
Memory access requires I/O (that includes storing things in $_SESSION files), but most beginners don't put their entire program within the confines of a try...catch structure. A good example of the use of exceptions and extending the Exception class can be found in Matt Doyle's book Beginning PHP 5.3, ch. 20, p. 652-60.
I might also say that learning to combine the use of exception handling with set_error_handler(), trigger_error(), error_log() inside of the catch block can allow you to preserve custom, developer friendly, error messages you may have been echoing to your output device (browser/stdout) in development. That is, in production, your php.ini will have display_errors off and log_errors will be on. Where you may have echoed something like "Oops, I cannot connect to the accounting database" when a connection to said database failed, simply send that same string of text to error_log() and your personal error messages can still be logged.
Example:
function custom_handler($arg1, $arg2, $arg3, $arg4, $arg5)
{
...
...
error_log(blah, blah, blah)
}
set_error_handler('custom_handler'); <--takes over this duty from PHP
$error = NULL;
try
{
if(!connect_to_mythical_database('accounting'))
{
$error = 'I cannot connect to the accounting database';
throw new Exception(supply-correct-arguments);
}
}
catch (Exception $e)
{
trigger_error(supply-correct-arguments); <-- does what 'custom_handler' instructs.
error_log($error, blah, blah); <---Your friendly message here
header('Location: http://www.myhomepage.com');
exit;
}
Note: Inside of custom_handler(), you can use error_log() to log PHP error messages in a file, email them, or both log and email the standard PHP error messages. Thus, trigger_error() is either controlled by PHP (default) or by your 'custom_handler', which can implement error_log()', all of which can be activated by thecatch` block of your exception handler.
For example, if we try to get to some api but fail, or try to connect to our database but also fail.
There are several ways to deal with this:
Bubble the error into your server log file
Write the error to a text file
Store the errors in a database table
You can user PHP Error Handling. See set_error_handler
php error handling is tricky, and there are many points to take into account. In php, we have 3 types of errors:
Fatal errors, like calling an undefined function. There's not much you can do about them. At least, set display_errors=0, log_errors=1 in php.ini, so that they won't be displayed. Better yet, write a petition on bugs.php.net and demand eliminating fatals altogether. Fatal errors are such a shame!
"Normal" warnings and notices. The best is to convert them into exceptions, see example #1
Exceptions - the funny part. You can catch them in your code when appropriate + install a generic exception handler as a last chance option to log the error and tell users that something wrong is happened.
There is a default function of PHP to log errors; error_log
Example from PHP.net:
<?php
// Send notification through the server log if we can not
// connect to the database.
if (!Ora_Logon($username, $password)) {
error_log("Oracle database not available!", 0);
}
// Notify administrator by email if we run out of FOO
if (!($foo = allocate_new_foo())) {
error_log("Big trouble, we're all out of FOOs!", 1,
"operator#example.com");
}
// another way to call error_log():
error_log("You messed up!", 3, "/var/tmp/my-errors.log");
?>
Be aware of extensive logging. Especially on productive Systems.
If you just try to handle programming errors, first you should raise a debug mode flag in your code. strict php handling is also very helpful. (set in php.ini or by your apache vhost settings).
dont try browser/screen debugging. as mentioned in other postings. set diplay_errors=0 and log_errors=1 in your php ini (or set it by your apache vhost settings)
then open a console window and do: tail -f
on your php_error.log (path is set in php.ini or by apache vhost settings)
if you use a framework. use the framework debugging tools (cake)
if you have your own framework/or just code. You probably should write an own exception handler class with debug capabilities.
example:
class MyFactory{
public static function getLogger(){
return new MyLogger();
}
}
class ExampleExceptionWithLogging extends Exception{
public __construct ($message=''){
MyFactory::getLogger()->exception($message,$this->getTrace());
}
}
class MyLogger{
/**
* #var string $logfile
**/
protected $logFile = '/var/log/php_error.log';
/**
* #param string $message
* #param array $stackTrace
**/
public function exception($message,$stackTrace){
$prefix = '[EXCEPTION] ';
$this->writeOut($prefix.$message.' '.print_r($stackTrace,TRUE));
}
/**
* writes $value to given Logfile.
* #param string $value
* #param string|NULL $logFile FileName with full path
*/
protected function writeOut($value,$logFile = NULL){
if(is_null($logFile)){
$logFile = $this->logFile;
}
error_log($value,3,$logFile);
}
}
Usage:
throw new ExampleExceptionWithLogging('Sample Message');
There are 3 issues here:
redirecting the program flow to the error handling
Capturing information relevant to resolving the error
Making that information available to the relevant parties
As others have said, (1) can be dealt with using set_error_handler(), note that you can instantiate your own customer errors within your code, e.g.
if (!$_SESSION['authenticated_user']) {
$login="<a href='/login.php'>login</a>";
trigger_error("Not authorized please $login", E_USER_WARNING);
}
The established practice for capturing information is the stack trace - and this is indeed available in PHP, however this is a static snapshot of the state of the PHP code at the point the error occurred - if you've tested your code properly, then the fault likely has nothing to do with your PHP code. Its a good indicator of where you should try to fix your code, but not a good indicator of what you need to fix. The stacktrace is still a useful tool, but it was often the only tool for programs which were running for any length of time, other than recording detailled logs of what the program did in the run up to the error. As well as an obvious performance hit, wading through several megabytes of logfiles looking for an error can be like looking for a needle in a haystack. However since PHP programs usually just generate a web page the exit, this presents the opportunity to accumulate the detailled log of events in a PHP variable, then you can choose to write the variable to a file only once an error occurs.
Like most things about programming, there is a trade-off here - if an error has happenned that you didn't expect/ plan for, how do you know that you're error handling is going to work?
In terms of making the data available, you probably should not record it in a database - chances are your program may have failed because the database isn't working properly - error handling must be very, very robust. Dumping it into a uniquely named file is a good approach which avoids the file contention problems you'd have with appending to a consolidated log file. Or use the syslog facility. You might even email a copy of the error out (but again this is relying on another complex subsystem).
HTH
C.