Error Signaling in PHP5 PEAR packages
Error conditions in PEAR packages written for PHP5 must be signaled using exceptions. Usage of return codes or return PEAR_Error objects is deprecated in favor of exceptions. Naturally, packages providing compatibility with PHP4 do not fall under these coding guidelines, and may thus use the error handling mechanisms defined in the PHP4 PEAR coding guidelines.
An exception should be thrown whenever an error condition is met, according to the definition provided in the previous section. The thrown exception should contain enough information to debug the error and quickly identify the error cause. Note that, during production runs, no exception should reach the end-user, so there is no need for concern about technical complexity in the exception error messages.
The basic PEAR_Exception contains a textual error, describing the program state that led to the throw and, optionally, a wrapped lower level exception, containing more info on the lower level causes of the error.
The kind of information to be included in the Exception is dependent on the error condition. From the point of view of exception throwing, there are three classes of error conditions:
-
Errors detected during precondition checks
-
Lower level library errors signaled via error return codes or error return objects.
-
Uncorrectable lower library exceptions.
Errors detected during precondition checks should contain a description of the failed check. If possible, the description should contain the violating value. Naturally, no wrapped exception can be included, as there isn't a lower level cause of the error. Example:
<?php
function divide($x,$y)
{
if ($y == 0) {
throw new Example_Aritmetic_Exception('Divide by zero');
}
return $x/$y;
}
?>
Errors signaled via return codes by lower level libraries, if unrecoverable, should be turned into exceptions. The error description should try to convey all information contained in the original error. One example, is the connect method previously presented:
<?php
/**
* Connect to Specified Database
*
* @throws Example_Datasource_Exception when it can't connect to specified DSN.
*/
function connectDB($dsn)
{
$this->db =& DB::connect($dsn);
if (DB::isError($this->db)) {
throw new Example_Datasource_Exception(
"Unable to connect to $dsn:" . $this->db->getMessage()
);
}
}
?>
Lower library exceptions, if they can't be corrected, should either be rethrown or bubbled up. When rethrowing, the original exception must be wrapped inside the one being thrown. When letting the exception bubble up, the exception just isn't handled and will continue up the call stack in search of a handler.
One example for rethrowing:
<?php
function preTaxPrice($retailPrice, $taxRate)
{
try {
return $this->divide($retailPrice, 1 + $taxRate);
} catch (Example_Aritmetic_Exception e) {
throw new Example_Tax_Exception('Invalid tax rate.', e);
}
}
?>
And the same example for bubbling up:
<?php
function preTaxPrice($retailPrice, $taxRate)
{
return $this->divide($retailPrice, 1 + $taxRate);
}
?>
The case between rethrowing or bubbling up is one of software architecture: Exceptions should be bubbled up, except in these two cases:
-
The original exception is from another package. Letting it bubble up would cause implementation details to be exposed, violating layer abstraction, conducing to poor design.
-
The current method can add useful debugging information to the received error before rethrowing.