Exceptions at the line

Exceptions are configured with the file and line where they were thrown. This is the classic situation : the exception is thrown immediately where a problem is detected. 

<?php

if (storeInDatabase($values) === NOT_STORED) {
    throw new StorageFailedException('Could not store in the database');
}

?>

Exceptions at a wrong line

It is not always convenient to throw the exception in the code. In particular, when a new layer of code is added to handle common operations. After all, this is what functions and classes are for.

<?php

if (validateData($values)) {
    storeInDatabase($values);
}

function validateData($values) {
    if (validateValues($values) === MISSING_ID) {
        throw new \Exception('Id is missing id for '.$values->name);
    }

    if (validateValues($values) === MISSING_NAME) {
        throw new \Exception('Id is missing name for '.$values->id);
    }
    ...
}

?>

Here, the validation is exported to a separate function, so as to avoid repeating (and forgetting) the various exceptions. One significant drawback of this situation is that validateData() now centralizes all the exceptions. Every detected exception is thrown from this particular function.

Fatal error: Uncaught Exception: Id is missing id for Smith in /tmp/test.php:9
Stack trace:
#0 /tmp/test.php(3): validateData(...)
#1 {main}
  thrown in /tmp/test.php on line 9

A message to Exception

There is a solution to this problem : creating a custom exception. Exceptions, and all its native tree, are a good source of base exceptions. Most of the time, they are customized with a new message : 

<?php

class myException extends \Exception {
    function __construct($message) {
        parent::__construct($message);
    }
}

?>

Yet, the exception classes include a lot of properties, including file and line number. 

<?php
class Exception extends Throwable
{
    protected $message = 'Unknown exception';   // exception message
    private   $string;                          // __toString cache
    protected $code = 0;                        // user defined exception code
    protected $file;                            // source filename of exception
    protected $line;                            // source line of exception
    private   $trace;                           // backtrace
    private   $previous;                        // previous exception if nested exception

    public function __construct($message = null, $code = 0, Exception $previous = null);


?>

This way, the exception may be designing another place in the code than the one where the exception was thrown : 

<?php

class myException extends \Exception {
    function __construct($message) {
        parent::__construct($message);
        
        $this->file = "myfile.php";
        $this->line = 1000;
    }
}

?>

The result looks like this : 

Fatal error: Uncaught Exception: Id is missing id for Smith in myfile.php:1000
Stack trace:
#0 /tmp/test.php(3): validateData(...)
#1 {main}
  thrown in /tmp/test.php on line 9

The right exception at the right place

You may notice that we only changed the file and line number with hard-coded values. This is not really helpful, since it doesn’t designate any real code. Using the magic constants __FILE__and __LINE__ leads to the same problem : it designates the current code, not the calling code.

The calling code, and its predecessors, is available with a call to debug_backtrace(). This native function builds the stack of calls that lead to the current execution. It returns an array of arrays : each contains the file, line, function or method, class and type of call. 

Here is the array, when debug_backtrace() is called with the DEBUG_BACKTRACE_IGNORE_ARGS.

Array
(
    [0] => Array
        (
            [file] => /tmp/test.php
            [line] => 9
            [function] => __construct
            [class] => myException
            [type] => ->
        )

    [1] => Array
        (
            [file] => /tmp/test.php
            [line] => 3
            [function] => validateData
        )

)

We can now access the file and line we’d like to display : 

<?php

class myException extends \Exception {
    function __construct($message) {
        parent::__construct($message);
        
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        $this->file = $trace[1]['file'];
        $this->line = $trace[1]['line'];
    }
}

?>

The resulting default error message is now pointing to useful coordinates. It may be personalized further with __toString() or a getMessage() call, in the try/catch.

Fatal error: Uncaught Exception: Id is missing id for Smith in /tmp/test.php:3
Stack trace:
#0 /tmp/test.php(3): validateData(...)
#1 {main}
  thrown in /tmp/test.php on line 9

Wrapping up

Exceptions may be configured beyond simple messages : it is possible to customise the file and line number, and the message displayed when the exception is used with echo. This is much more convenient than the default behavior. 

Don’t forget to chain exceptions : always relay the previous exceptions to the newly created one, so that the whole chain of exceptions is available to the last receiver.