PHP Coding Standards (5) – Errors and Exceptions

1. The Zend Framework codebase must be E_STRICT compliant. Zend Framework code should not emit PHP warning (E_WARNING, E_USER_WARNING), notice (E_NOTICE, E_USER_NOTICE), or strict (E_STRICT) messages when error_reporting is set to E_ALL | E_STRICT .

 

That is to say, there are no errors in code writing. Only logic fault will shut down the application.

 

 

2. Zend Framework code should not emit PHP errors, if it is reasonably possible. Instead, throw meaningful exceptions. Zend Framework components have Exception class derivatives specifically for this purpose.

 

For example : 

[codesyntax lang=”php”]

class Zend_Exception extends Exception
{
}
class Zend_Db_Exception extends Zend_Exception
{
}
class Zend_Db
{
public static function factory($adapter, $config = array())
{
// ...
if (!is_array($config)) {
/**
* @see Zend_Db_Exception
*/

require_once 'Zend/Db/Exception.php';
throw new Zend_Db_Exception('Adapter parameters must be in'
. 'an array or a Zend_Config object');
}
}
}

[/codesyntax]

 

 

3. It is considered best practice within framework component code that exceptions are instantiated through the traditional new constructor method.

 

For example : 

require_once 'Zend_Component_SpecificException.php';

class Zend_Component
{
public function foo($condition)
{
if ($condition) {
throw new Zend_Component_SpecificException(
'Some meaningful exception message');
}
}
}

 

 

4. Exceptions must be lazy loaded before they are thrown.

 

For example : 

// right
if ($condition) {
require_once 'Zend_Component_SpecificException.php';
throw new Zend_Component_SpecificException(
'Some meaningful exception message');
} else {
// ...
}
// wrong
require_once 'Zend_Component_SpecificException.php';
if ($condition) {
throw new Zend_Component_SpecificException(
'Some meaningful exception message');
} else {
// ...
}

 

 

5. In general, if a Zend Framework component is asked to perform a duty that it cannot perform in a certain situation then throwing an exception is a sensible course of action. Conversely, if a component is able to perform its requested duty, despite some variance from expected input, then the component should continue with its work, rather than throw an exception.

 

For example : 

if ($canNotPerformThisAction) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('Can not perform this action !');
}

$foo = 'bar';
if ($foo == 'foo') {
echo 'That is true';
} else {
// No need to throw any exception
}

 

 

6. Avoid throwing the Exception base class, or other exception superclass. The more specific the exception, the better it communicates to the user what happened.

 

For example : 

class Zend_Db
{
public static function factory($adapter, $config = array())
{
if (!is_array($config)) {
throw new Exception('We do not know which kind of exception it is.');
}
if (!is_array($config)) {
require_once 'Zend/Db/Exception.php';
throw new Zend_Db_Exception("We all know it's Zend_Db exception.");
}
}
}

 

 

7. Avoid catching the Exception base class, or other exception superclass. If a try block might encounter more than one type of exception, write a separate catch block for each specific exception, not one catch block for an exception superclass.

 

For example : 

// index.php
try {
$app->run();
} catch (Zend_Db_Exception $e) {
die('Database exception !');
} catch (Zend_Acl_Exception $e) {
die('Access control list exception !');
} catch (Zend_Auth_Exception $e) {
die('Authentication exception !');
} catch (Zend_Exception $e) {
// Other exceptions
}

 

 

8. Some classes may require you to write more than one derived exception class. Write as many exception classes as needed, to distinguish between different types of situations. For example, "invalid argument value" is different from, "you don’t have a needed privilege." Create different exceptions to identify different cases.

 

For example : 

class Zend_Db_Exception extends Zend_Exception
{
// Database exception base class
}

class Zend_Db_Select_Exception extends Zend_Db_Exception
{
// For selection exceptions
}

class Zend_Db_Table_Exception extends Zend_Db_Exception
{
// For table exceptions
}

 

 

9. Don’t put important diagnostic information only in the text of the exception method. Create methods and members in derived exception classes as needed, to provide information to the catch block. Create an exception constructor method that takes appropriate arguments, and populate the members of the class with those arguments.

 

For example : 

class Zend_Exception extends Exception
{
}

class My_Exception extends Zend_Exception
{
private $_importantDiagnostic;

public function setImportantDiagnostic($value)
{
$this->_importantDiagnostic = $value;
}

public function getImportantDiagnostic()
{
return $this->_importantDiagnostic;
}

public function __construct($message = null, $code = 0, $value)
{
parent::__construct($message, $code);
$this->setImportantDiagnostic($value);
}
}

try {
if ($isMyFault) {
throw new My_Exception('No message.', 0, 'This is the solution.');
}
} catch (My_Exception $e) {
echo $e->getImportantDiagnostic();
}

 

 

10. Don’t silently suppress exceptions and allow execution to continue in an erroneous state. If you catch an exception, either correct the condition or throw a new exception.

 

For example : 

try {
} catch (My_Exception $e) {
tryToCorrectIt($e);
} catch (My_Exception $e) {
throw new My_Exception($e->getMessage(), '110');
} catch (My_Exception $e) {
// Do nothing, fool !
}

 

 

11. Keep implementation-specific exceptions isolated to the appropriate layer of your application. For instance, don’t propagate SQLException out of the data layer code and into business layer code.

 

For example : 

class My_Dao_User_Exception extends Zend_Db_Table_Exception
{
// User exception in data layer
}

class My_Dao_User extends Zend_Db_Table_Abstract
{
// User object in data layer
}

class My_Bis_User_Exception extends Zend_Exception
{
// User exception in business layer
}

/**
* User object in business layer
*/

class My_Bis_User
{
private $_daoUser = null;

public function setDaoUser($user)
{
$this->_daoUser = $user;
}

public function getDaoUser()
{
return $this->_daoUser;
}

public function throwSomething()
{
try {
if ($wrong) {
throw new My_Dao_User_Exception('You should not throw data access exception in business layer!');
} else if ($right) {
throw new My_Bis_User_Exception('This is the right one.');
}
} catch (Zend_Exception $e) {
// ...
}
}
}

 

 

12. Don’t use exceptions as a mechanism of flow control, or to return valid return values from a function.

 

For example : 

function itIsWrong()
{
try {
// Something
} catch (Exception_One $e) {
doSomething($e);
} catch (Exception_Two $e) {
doSomething($e);
} catch (Exception_Three $e) {
return true;
}
}

function isRight()
{
try {
// Something
} catch (Exception_One $e) {
correctIt($e);
} catch (Exception_Two $e) {
correctIt($e);
} catch (Exception_Three $e) {
// Never return;
throw new Exception('We throw it instead of return anything!');
}
}

 

 

13. Clean up resources such as database connections or network connections. PHP does not support a finally block as some programming languages do, so either clean up in the catch blocks, or else design flow control outside the catch block to perform cleanup, and let execution continue after the catch.

 

For example : 

try {
$db = getDbConnection();
throw new Exception('Some errors occur');
} catch (Exception $e) {
unset($db);
handleExceptionAndGoOn($e);
}

 

 

14. Use documentation from other languages for other best practices regarding using exceptions. Many of the principles are applicable, regardless of the language.

 

Posted in Coding Standards | Tagged , | Leave a comment