138

Maybe I'm missing it somewhere in the PHP manual, but what exactly is the difference between an error and an exception? The only difference that I can see is that errors and exceptions are handled differently. But what causes an exception and what causes an error?

Jason Baker
  • 192,085
  • 135
  • 376
  • 510

12 Answers12

100

Exceptions are thrown - they are intended to be caught. Errors are generally unrecoverable. Lets say for instance - you have a block of code that will insert a row into a database. It is possible that this call fails (duplicate ID) - you will want to have a "Error" which in this case is an "Exception". When you are inserting these rows, you can do something like this

try {
  $row->insert();
  $inserted = true;
} catch (Exception $e) {
  echo "There was an error inserting the row - ".$e->getMessage();
  $inserted = false;
}

echo "Some more stuff";

Program execution will continue - because you 'caught' the exception. An exception will be treated as an error unless it is caught. It will allow you to continue program execution after it fails as well.

gnarf
  • 105,192
  • 25
  • 127
  • 161
  • 37
    `Errors are generally unrecoverable` <-- actually, this isn't really true. `E_ERROR` and `E_PARSE` are the two most common unrecoverable errors (there are a couple of others) but the vast majority of errors you'll see in dev are recoverable (`E_NOTICE`, `E_WARNING` et al). Unfortunately PHP's error handling is a complete mess - all kinds of things trigger errors unnecessarily (the vast majority of the file system functions, for example). In general exceptions are "the OOP way", but unfortunately some of PHP's native OOP APIs use errors instead of exceptions :-( – DaveRandom Sep 19 '13 at 11:45
  • 1
    @DaveRandom E_NOTICE, E_WARNING are not "errors" by definition are they? I always thought them as 'messages' PHP displays to notify programmer that something might be wrong with the code s/he wrote. – slhsen Aug 14 '15 at 08:57
  • 4
    @slhsen the issue really is crappy terminology, all forms of these messages go through the "error handling system" in PHP, semantically all of these events are "errors", even though semantically notice/warning is most definitely not the same as an "error" in that context. Thankfully the upcoming PHP7 has at least paved the way to sorting this mess out by means of turning most of these things into catchable exceptions (by means of a new `Throwable` interface), giving a much more expressive and absolute way to distinguish and properly hand both real problems and advisory messages – DaveRandom Aug 14 '15 at 20:15
  • "Program execution will continue" has changed I suppose? Since PHP says "When an exception is thrown, code following the statement will not be executed" (http://php.net/manual/en/language.exceptions.php) – Robert Sinclair Aug 29 '17 at 18:04
  • 1
    I think what the OP meant was more about the difference between the descendants of `Error` VS the descendants of `Exception`. – XedinUnknown Oct 01 '18 at 14:58
  • 1
    As of PHP 7, both ```Error``` and ```Exception``` classes implement the ```Throwable``` interface and can be caught. – Nikolay Shindarov Mar 27 '19 at 13:30
62

I usually set_error_handler to a function that takes the error and throws an exception so that whatever happens i'll just have exceptions to deal with. No more @file_get_contents just nice and neat try/catch.

In debug situations i also have an exception handler that outputs an asp.net like page. I'm posting this on the road but if requested I will post example source later.

edit:

Addition as promised, I've cut and pasted some of my code together to make a sample.

<?php

define( 'DEBUG', true );

class ErrorOrWarningException extends Exception
{
    protected $_Context = null;
    public function getContext()
    {
        return $this->_Context;
    }
    public function setContext( $value )
    {
        $this->_Context = $value;
    }
    
    public function __construct( $code, $message, $file, $line, $context )
    {
        parent::__construct( $message, $code );

        $this->file = $file;
        $this->line = $line;
        $this->setContext( $context );
    }
}

/**
 * Inspire to write perfect code. everything is an exception, even minor warnings.
 **/
function error_to_exception( $code, $message, $file, $line, $context )
{
    throw new ErrorOrWarningException( $code, $message, $file, $line, $context );
}
set_error_handler( 'error_to_exception' );

function global_exception_handler( $ex )
{
    ob_start();
    dump_exception( $ex );
    $dump = ob_get_clean();
    // send email of dump to administrator?...

    // if we are in debug mode we are allowed to dump exceptions to the browser.
    if ( defined( 'DEBUG' ) && DEBUG == true )
    {
        echo $dump;
    }
    else // if we are in production we give our visitor a nice message without all the details.
    {
        echo file_get_contents( 'static/errors/fatalexception.html' );
    }
    exit;
}

function dump_exception( Exception $ex )
{
    $file = $ex->getFile();
    $line = $ex->getLine();

    if ( file_exists( $file ) )
    {
        $lines = file( $file );
    }
    
?><html>
    <head>
        <title><?= $ex->getMessage(); ?></title>
        <style type="text/css">
            body {
                width : 800px;
                margin : auto;
            }
        
            ul.code {
                border : inset 1px;
            }
            ul.code li {
                white-space: pre ;
                list-style-type : none;
                font-family : monospace;
            }
            ul.code li.line {
                color : red;
            }
            
            table.trace {
                width : 100%;
                border-collapse : collapse;
                border : solid 1px black;
            }
            table.thead tr {
                background : rgb(240,240,240);
            }
            table.trace tr.odd {
                background : white;
            }
            table.trace tr.even {
                background : rgb(250,250,250);
            }
            table.trace td {
                padding : 2px 4px 2px 4px;
            }
        </style>
    </head>
    <body>
        <h1>Uncaught <?= get_class( $ex ); ?></h1>
        <h2><?= $ex->getMessage(); ?></h2>
        <p>
            An uncaught <?= get_class( $ex ); ?> was thrown on line <?= $line; ?> of file <?= basename( $file ); ?> that prevented further execution of this request.
        </p>
        <h2>Where it happened:</h2>
        <? if ( isset($lines) ) : ?>
        <code><?= $file; ?></code>
        <ul class="code">
            <? for( $i = $line - 3; $i < $line + 3; $i ++ ) : ?>
                <? if ( $i > 0 && $i < count( $lines ) ) : ?>
                    <? if ( $i == $line-1 ) : ?>
                        <li class="line"><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? else : ?>
                        <li><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? endif; ?>
                <? endif; ?>
            <? endfor; ?>
        </ul>
        <? endif; ?>

        <? if ( is_array( $ex->getTrace() ) ) : ?>
        <h2>Stack trace:</h2>
            <table class="trace">
                <thead>
                    <tr>
                        <td>File</td>
                        <td>Line</td>
                        <td>Class</td>
                        <td>Function</td>
                        <td>Arguments</td>
                    </tr>
                </thead>
                <tbody>
                <? foreach ( $ex->getTrace() as $i => $trace ) : ?>
                    <tr class="<?= $i % 2 == 0 ? 'even' : 'odd'; ?>">
                        <td><?= isset($trace[ 'file' ]) ? basename($trace[ 'file' ]) : ''; ?></td>
                        <td><?= isset($trace[ 'line' ]) ? $trace[ 'line' ] : ''; ?></td>
                        <td><?= isset($trace[ 'class' ]) ? $trace[ 'class' ] : ''; ?></td>
                        <td><?= isset($trace[ 'function' ]) ? $trace[ 'function' ] : ''; ?></td>
                        <td>
                            <? if( isset($trace[ 'args' ]) ) : ?>
                                <? foreach ( $trace[ 'args' ] as $i => $arg ) : ?>
                                    <span title="<?= var_export( $arg, true ); ?>"><?= gettype( $arg ); ?></span>
                                    <?= $i < count( $trace['args'] ) -1 ? ',' : ''; ?> 
                                <? endforeach; ?>
                            <? else : ?>
                            NULL
                            <? endif; ?>
                        </td>
                    </tr>
                <? endforeach;?>
                </tbody>
            </table>
        <? else : ?>
            <pre><?= $ex->getTraceAsString(); ?></pre>
        <? endif; ?>
    </body>
</html><? // back in php
}
set_exception_handler( 'global_exception_handler' );

class X
{
    function __construct()
    {
        trigger_error( 'Whoops!', E_USER_NOTICE );      
    }
}

$x = new X();

throw new Exception( 'Execution will never get here' );

?>
Emosewaj
  • 25
  • 1
  • 7
Kris
  • 40,604
  • 9
  • 72
  • 101
  • That would be helpful. Anything to ease the times I'm made to deal with PHP will help. :-) – Jason Baker May 09 '09 at 14:42
  • Nice code, thanks. I don't get where the class X comes from though, and what its purpose is? – Alec Jun 09 '09 at 00:00
  • everything below "set_exception_handler( 'global_exception_handler' );" is just demo, you won't need it, it's just to show what would happen in a normally non-exception error situation. – Kris Jun 09 '09 at 09:48
  • Standard PHP defines ErrorException specifically to be thrown from a general error handler. Would you allow me to edit and update your post? – oxygen Sep 10 '13 at 16:10
  • @Tiberiu-IonuțStan: sure, but the working example will be out of sync. Also, nowadays I'd probably point people to https://github.com/theredhead/red.web/blob/master/src/lib/bootstrap.php from http://www.private-void.com instead. – Kris Sep 11 '13 at 04:26
  • @Kris `date_default_timezone_set('Europe/Amsterdam');` and non -1 `set_error_handler`, and this is just at first glance. I think it could have been a lot better, error handling wise, and timezone wise (UTC would have been a better pick for a default). Also, it is assuming HTML output (one of the reasons it doesn't register for CLI mode). – oxygen Sep 11 '13 at 07:25
  • You could make it a lib. With some options like pretty-print exceptions or just stash them into a static array in your error handler... Everything that fixes the crap out of php has potential. (ps: php needs a cleaner cousing language...) – SparK Apr 15 '16 at 13:57
  • Converting errors to exceptions is a good thing. But then you say "just nice and neat try/catch" and use a set_exception_handler to handle exceptions without try/catch. – OCDev Aug 08 '19 at 10:58
  • @OCDev, You do have a point but `set_exception_handler` does not prevent you from catching your own errors at all, it just sets up a fallback with a report in this example and the example serves more to demonstrate what makes exceptions nice than how you should use them. – Kris Aug 08 '19 at 12:04
31

The answer deserves talking about the elephant in the room

Errors is the old way of handling an error condition at run-time. Typically the code would make a call to something like set_error_handler before executing some code. Following the tradition of assembly language interrupts. Here is how some BASIC code would look.

on error :divide_error

print 1/0
print "this won't print"

:divide_error

if errcode = X
   print "divide by zero error"

It was hard to make sure that set_error_handler would be called with the right value. And even worse, a call could be made to a separate procedure that would change the error handler. Plus many times calls were interspersed with set_error_handler calls and handlers. It was easy for code to quickly get out of control. Exception handling came to the rescue by formalizing syntax and semantics of what good code was really doing.

try {
   print 1/0;
   print "this won't print";
} catch (DivideByZeroException $e) {
   print "divide by zero error";
}

No separate function or risk of calling the wrong error handler. The code now is guaranteed to be in the same place. Plus we get better error messages.

PHP used to only have error handling, when many other languages already had evolved to the preferable exception handling model. Eventually the makers of PHP implemented exception handling. But likely to support old code, they kept error handling and provided a way to make error handling look like exception handling. Except that, there is no guarantee that some code may not reset the error handler which was precisely what exception handling was meant to provide.

Final answer

Errors that were coded before exception handling was implemented, are likely still errors. New errors are likely exceptions. But there is no design or logic to which are errors and which are exceptions. It's just based in what was available at the time it was coded, and the preference of the programmer coding it.

Arturo Hernandez
  • 2,749
  • 3
  • 28
  • 36
8

As stated in other answers, setting error handler to exception thrower is the best way to handle errors in PHP. I use a bit simpler setup:

set_error_handler(function ($errno, $errstr, $errfile, $errline ) {
        if (error_reporting()) {
                throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
        }
});

Please note the error_reporting() check to keep @ operator working. Also, there is no need to define custom exception, PHP has one nice class for that.

Great benefit of throwing exceptions is that exception has stack trace associated with them, so it is easy to find where is the problem.

Josef Kufner
  • 2,851
  • 22
  • 28
8

One thing to add here is about handling exceptions and errors. For the purpose of the application developer, both errors and exceptions are "bad things" that you want to record to learn about the problems that your application has - so that your customers have a better experience in the long run.

So it makes sense to write an error handler that does the same thing as what you do for exceptions.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Alex Weinstein
  • 9,823
  • 9
  • 42
  • 59
6

Re: "but what exactly is the difference between an error and an exception?"

There are a lot of good answers about the differences here. I'll just add in something that hasn't yet been talked about - performance. Specifically, this is for the difference between throwing/handling exceptions and handling a return code (either success or some error). Usually, in php, this means returning false or null, but they can be more detailed such as with file uploading: http://php.net/manual/en/features.file-upload.errors.php You could even return an Exception object!

I've done a few performance runs in different languages/systems. Generally speaking, exception handling is about 10,000x slower than checking for an error return code.

So, if it absolutely, positively needs to finish executing before it even started - well, you're out of luck because time travel doesn't exist. Without time travel, return codes are the fastest option available.

Edit:

PHP is highly optimized for exception handling. Real world tests show that throwing an exception is only 2-10x slower than returning a value.

evan
  • 12,307
  • 7
  • 37
  • 51
  • 4
    Sure, but the amount of cycles lost to throwing Exceptions is more than made up for by the extra descriptive powers you get with Exceptions. You can throw specific types of exceptions, even add data to the exception to contain the error codes. I seriously doubt your 10,000* claim as well. Even if you are right about the time difference, the time spent doing return & if vs. new Execption, throw, catch in any real world scenario is likely so minuscule compared to the executed code that this is definitely a premature optimization. Throw exceptions, they are nicer to deal with 90% of the time. – gnarf Aug 02 '12 at 21:57
  • 1
    1. 10,000x is accurate - with some variance based on language and compiler options. 2. You don't have to return null/false. You can return a number - up to MAX_ULONG return codes right there. You could alternatively return a fail string and just check for a success string or int or null. 3. In real world scenarios every clock cycle counts. Facebook has 552 million daily active users. Assuming exceptions are only 2x and that checking user/pass takes .001s that means saving 153 hours of processing time every day. At 10,000x it saves 175 years. Just for checking login attempts - each day. – evan Aug 03 '12 at 05:45
  • @evan: FYI, here they tested code with exceptions and it does not seem to be slower: http://stackoverflow.com/a/445094/260080 – Marco Demaio Dec 29 '12 at 17:58
  • @MarcoDemaio That question only covers the try/catch block without throwing an exception. A better test would be to return a value in noexcept() and throw an exception in except(). Also, it should bubble up through multiple functions. http://stackoverflow.com/a/104375/505172 states that the difference in PHP is actually 54x. I ran my own test looking at real time and it seems to be 2-10x slower. This is all way better than expected. – evan Jan 17 '13 at 05:56
  • @evan: I wouldn't be worried then, I use exceptions only to track unexpected/unrecoverable errors so even if it would be 100 times slower I wouldn't care. My worries were about making the code slower by simply adding try/catch blocks. – Marco Demaio Jan 24 '13 at 19:40
4

I think the anwser you're looking for is that;

Errors are the standard stuff you're used to, like echoing a $variable that doesnt exist.
Exceptions are only from PHP 5 onwards and come when dealing with objects.

To keep it simple:

Exceptions are the errors you get when dealing with objects. The try/catch statement lets you do something about them though, and is used much like the if/else statement. Try to do this, if problem, doesnt matter, do this.

If you dont "catch" an exception, then it turns into a standard error.

Errors are the php fundemental errors which usually halt your script.

Try/catch is often used for establishing database connections like PDO, which is fine if you want to redirect the script or do something else if the connection doesnt work. But if you just want to display the error message and stop the script then you dont need it, the uncaught exception turns into a fatal error. Or you can use a site-wide error handling setting as well.

Hope that helps

Lan
  • 1,874
  • 2
  • 20
  • 37
4

From PHP: Exceptions - Manual:

As of PHP 7.1.0, a catch block may specify multiple exceptions using the pipe (|) character. This is useful for when different exceptions from different class hierarchies are handled the same.

try {
  // do something
} catch (Error | Exception $e) {
  echo $e->getMessage();
}
akinuri
  • 10,690
  • 10
  • 65
  • 102
Jehong Ahn
  • 1,872
  • 1
  • 19
  • 25
1

I intend to give you a most unusual discussion of error control.

I built a very good error handler into a language years ago, and though some of the names have changed, the principles of error processing are the same today. I had a custom built multi-tasking OS and had to be able to recover from data errors at all levels with no memory leaks, stack growth or crashes. So what follows is my understanding of how errors and exceptions must operate and how they differ. I will just say I do not have an understanding of how the internals of try catch works, so am guessing to some measure.

The first thing that happens under the covers for error processing is jumping from one program state to another. How is that done? I'll get to that.

Historically, errors are older and simpler, and exceptions are newer and a bit more complex and capable. Errors work fine until you need to bubble them up, which is the equivalent of handing a difficult problem to your supervisor.

Errors can be numbers, like error numbers, and sometimes with one or more associated strings. For example if a file-read error occurs you might be able to report what it is and possibly gracefully fail. (Hay, it's a step up from just crashing like in the old days.)

What is not often said about exceptions is that exceptions are objects layered on a special exception stack. It's like a return stack for program flow, but it holds a return state just for error trys and catches. (I used to call them ePush and ePop, and ?Abort was a conditional throw which would ePop and recover to that level, while Abort was a full die or exit.)

On the bottom of the stack is the information about the initial caller, the object that knows about the state when the outer try was started, which is often when your program was started. On top that, or the next layer on the stack, with up being the children, and down being the parents, is the exception object of the next inner try/catch block.

If you put a try inside a try you are stacking the inner try on top of the outer try. When an error occurs in the inner try and either the inner catch can't handle it or the error is thrown to the outer try, then control is passed to the outer catch block (object) to see if it can handle the error, i.e. your supervisor.

So what this error stack really does is to be able to mark and restore program flow and system state, in other words, it allows a program to not crash the return stack and mess up things for others (data) when things go wrong. So it also saves the state of any other resources like memory allocation pools and so it can clean them up when catch is done. In general this can be a very complicated thing, and that is why exception handling is often slow. In general quite a bit of state needs to go into these exception blocks.

So a try/catch block sort of sets a state to be able to return to if all else gets messed up. It's like a parent. When our lives get messed up we can fall back into our parent's lap and they will make it all right again.

Hope I didn't disappoint you.

Elliptical view
  • 3,338
  • 1
  • 31
  • 28
1

Exceptions are thrown intentionally by code using a throw, errors... not so much.

Errors come about as a result of something which isn't handled typically. (IO errors, TCP/IP errors, null reference errors)

cgp
  • 41,026
  • 12
  • 101
  • 131
  • 1
    This is not necessarily true. In many cases errors are checked for and return codes are intentionally sent back as appropriate. In fact, that is the case for every non-object oriented language. Exceptions are just that, as well, exceptions to the rule. In both cases, something goes wrong, is noticed, and should be handled. PHP file uploading is one example of intentional error handling via return codes - http://php.net/manual/en/features.file-upload.errors.php – evan Nov 17 '11 at 11:53
0

Once set_error_handler() is defined, error handler is similar to Exception's. See code below:

 <?php
 function handleErrors( $e_code ) {
   echo "error code: " . $e_code . "<br>";
 }

 set_error_handler( "handleErrors" ); 

 trigger_error( "trigger a fatal error", E_USER_ERROR);
 echo "after error."; //it would run if set_error_handler is defined, otherwise, it wouldn't show
?>
N Zhang
  • 101
  • 1
  • 7
0

You may add this comment

function doSomething()
{
   /** @noinspection PhpUnhandledExceptionInspection */
   throw new Exception();
}
Yolo
  • 3
  • 2