Vous ne devriez pas attraper l'exception à moins que vous n'ayez l'intention de faire quelque chose de significatif .
"Quelque chose de significatif" pourrait être l'un d'entre eux :
Traitement de l'exception
L'action significative la plus évidente consiste à traiter l'exception, par exemple en affichant un message d'erreur et en abandonnant l'opération :
try {
$connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
echo "Error while connecting to database!";
die;
}
Journalisation ou nettoyage partiel
Parfois, vous ne savez pas comment traiter correctement une exception dans un contexte spécifique ; peut-être manquez-vous d'informations sur la "situation globale", mais vous voulez enregistrer l'échec aussi près que possible du point où il s'est produit. Dans ce cas, il est préférable d'attraper, d'enregistrer et de relancer :
try {
$connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
logException($e); // does something
throw $e;
}
Un scénario connexe est celui où vous êtes au bon endroit pour effectuer un nettoyage de l'opération qui a échoué, mais pas pour décider comment l'échec doit être traité au niveau supérieur. Dans les versions antérieures de PHP, ceci serait implémenté comme suit
$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
$connect->insertSomeRecord();
}
catch (Exception $e) {
$connect->disconnect(); // we don't want to keep the connection open anymore
throw $e; // but we also don't know how to respond to the failure
}
PHP 5.5 a introduit la fonction finally
Ainsi, pour les scénarios de nettoyage, il existe désormais une autre façon d'aborder la question. Si le code de nettoyage doit s'exécuter quoi qu'il arrive (c'est-à-dire aussi bien en cas d'erreur qu'en cas de succès), il est maintenant possible de le faire tout en permettant de manière transparente la propagation des exceptions levées :
$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
$connect->insertSomeRecord();
}
finally {
$connect->disconnect(); // no matter what
}
Abstraction des erreurs (avec chaînage des exceptions)
Le troisième cas est celui où l'on souhaite regrouper logiquement plusieurs défaillances possibles sous un même chapeau. Un exemple de regroupement logique :
class ComponentInitException extends Exception {
// public constructors etc as in Exception
}
class Component {
public function __construct() {
try {
$connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
throw new ComponentInitException($e->getMessage(), $e->getCode(), $e);
}
}
}
Dans ce cas, vous ne voulez pas que les utilisateurs de Component
de savoir qu'il est mis en œuvre à l'aide d'une connexion à une base de données (vous souhaitez peut-être garder vos options ouvertes et utiliser un stockage basé sur des fichiers à l'avenir). Ainsi, votre spécification pour Component
dirait que "dans le cas d'un échec de l'initialisation, ComponentInitException
seront lancés". Cela permet aux consommateurs de Component
pour attraper les exceptions du type attendu tout en permettant au code de débogage d'accéder à tous les détails (dépendant de l'implémentation). .
Fournir un contexte plus riche (avec le chaînage des exceptions)
Enfin, il y a des cas où vous pouvez vouloir fournir plus de contexte pour l'exception. Dans ce cas, il est logique d'envelopper l'exception dans une autre exception qui contient plus d'informations sur ce que vous essayiez de faire lorsque l'erreur s'est produite. Par exemple :
class FileOperation {
public static function copyFiles() {
try {
$copier = new FileCopier(); // the constructor may throw
// this may throw if the files do no not exist
$copier->ensureSourceFilesExist();
// this may throw if the directory cannot be created
$copier->createTargetDirectory();
// this may throw if copying a file fails
$copier->performCopy();
}
catch (Exception $e) {
throw new Exception("Could not perform copy operation.", 0, $e);
}
}
}
Ce cas est similaire au précédent (et l'exemple n'est probablement pas le meilleur que l'on puisse trouver), mais il illustre l'intérêt de fournir davantage de contexte : si une exception est levée, cela nous indique que la copie du fichier a échoué. Mais pourquoi a-t-il échoué ? Ces informations sont fournies dans les exceptions enveloppées (dont il pourrait y avoir plus d'un niveau si l'exemple était beaucoup plus compliqué).
L'intérêt de cette démarche est illustré si vous pensez à un scénario dans lequel, par exemple, la création d'une UserProfile
provoque la copie de fichiers parce que le profil de l'utilisateur est stocké dans des fichiers et qu'il prend en charge la sémantique des transactions : vous pouvez "annuler" les modifications car elles ne sont effectuées que sur une copie du profil jusqu'à ce que vous les validiez.
Dans ce cas, si vous avez
try {
$profile = UserProfile::getInstance();
}
et que, par conséquent, vous obteniez une exception d'erreur "Le répertoire cible n'a pas pu être créé", vous auriez le droit d'être confus. Envelopper cette exception "principale" dans des couches d'autres exceptions qui fournissent un contexte rendra l'erreur beaucoup plus facile à traiter ("La création de la copie du profil a échoué" -> "L'opération de copie du fichier a échoué" -> "Le répertoire cible n'a pas pu être créé").