Introduction à l'utilisation de PEAR_ErrorStack
pour une gestion avancée des erreurs
--
Utiliser PEAR_ErrorStack pour effectuer une gestion
des erreurs simples ou avancées.
PEAR_ErrorStack implémente une levée et une gestion d'erreurs utilisant le concept de pile.
Ceci a d'énormes avantages comparer à l'implémentation PEAR_Error.
PEAR_Error centralise toutes les créations et gestions d'erreurs dans le constructeur
de l'objet PEAR_Error. Une fois qu'un objet a été créé, tous les traitements doivent
être terminés, soit en contrôlant la valeur retournée par la méthode, soit en utilisant
une fonction de rappel locale. De plus, il est quasiment impossible de déterminer la
source d'une erreur ainsi que les goulots d'étranglement des méthodes lentes de la classe
de base PEAR accompagnant chaque création d'erreur.
<?php
// utilisation classique de PEAR_Error
require_once 'PEAR.php';
class myobj
{
// il n'est pas possible de savoir d'où viennent les erreurs
function errorCallback($err)
{
$this->display($err->getMessage());
$this->log($err->getMessage());
}
function log($msg)
{
error_log($msg, 3, 'somefile.log')
}
function display($msg)
{
echo $msg . '<br />';
}
}
$myobj = new myobj;
// utilisation d'une fonction de Callback
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array(&$myobj, 'errorCallback'));
$ret = SomePackage::doSomething();
if (PEAR::isError($ret)) {
// gestion de l'erreur - cette erreur est aussi affichée et loguée
}
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
$ret = SomePackage::doSomething();
if (PEAR::isError($ret)) {
// gère l'erreur - cette erreur n'est pas affichée ni loggée
}
PEAR::popErrorHandling();
?>
La classe PEAR_ErrorStack ayant hérité du savoir du package
Log,
peut facilement différencier et éventuellement reconditionner
des erreurs sans grandes difficultés.
<?php
// Gestion d'erreurs PEAR_ErrorStack
require_once 'PEAR/ErrorStack.php';
require_once 'Log.php';
define('MON_PACKAGE_ERROR_DBERROR', 1);
class myobj
{
var $_stack;
function myobj()
{
$this->_stack = &PEAR_ErrorStack::singleton('monpackage');
}
function errorCallback($err)
{
switch($err['package']){
case 'MyPackage':
// Dit à la pile d'erreurs de seulement logger l'erreur
// Elle ne sera pas poussée sur la pile
return PEAR_ERRORSTACK_LOG;
break;
case 'InternalDbPackage':
// rempaquète ces erreurs comme des erreurs mypackag
// à l'attention des utilisateurs finaux
$this->_stack->push(MON_PACKAGE_ERROR_DBERROR, 'error',
array('dbmessage' => $err['message'],
'dbcode' => $err['code'],
'Nous avons des problème de connection,' .
'veuillez réessayer plus tard'),
'', $err); // incluse l'erreur et rempaquète
// Demande à la pile DB erreur interne d'ignorer
// cette erreur
// comme si elle n'etait jamais arrivée
return PEAR_ERRORSTACK_IGNORE;
break;
} // switch
}
}
$myobj = &new myobj;
// Sépare les piles d'erreurs "my package" et DB interne
$dbstack = &PEAR_ErrorStack::singleton('InternalDbPackage');
$mystack = &PEAR_ErrorStack::singleton('monpackage');
// Prépare un fichier log utilisant PEAR::Log
$log = &Log::Factory('file', 'somefile.log', 'monpackage error log');
$mystack->setLogger($log);
// Prépare un log par defaut utilisé pour toutes les piles d'erreurs
PEAR_ErrorStack::setDefaultLogger($log);
// toutes les erreurs retournées par "my package" sont loggées
$ret = SomePackage::doSomething();
// Notez qu'il n'est pas nécessaire de contrôler les conditions de l'erreur
// sur $ret - les erreurs sont complètement separées du code.
if ($dbstack->hasErrors()) {
var_dump($dbstack->getErrors();
}
// Définission d'une fonction de callback pour toutes les erreurs
PEAR_ErrorStack::setDefaultCallback(array(&$myobj, 'errorCallback'));
// toutes les erreurs DB sont rempaquetées en erreurs "my package"
// de façon transparente
$ret = SomePackage::doSomething();
?>
Pourquoi écrire une nouvelle routine de gestion d'erreurs
alors que
PEAR_Error
existe déjà?
Il y a un nombre important de problèmes avec
PEAR_Error.
Bien qu'un message d'erreur soit présent dans une classe d'erreur,
ce message d'erreur ne peut pas facilement être traité une fois
qu'il a été placé dans PEAR_Error.
Il n'existe pas non plus de service standard pour stocker
des données associées aux erreurs dans la classe.
En plus de ces problèmes de messages d'erreurs,
il n'est pas possible de déterminer automatiquement de quel
paquet provient l'objet PEAR_Error
ou même la sévérité d'une erreur. En effet, les erreurs fatales ressemblent
exactement aux erreurs non fatales.
Le plus gros problème de l'objet PEAR_Error
est sa conception orientée « mono erreur ».
Chaque objet PEAR_Error
est juste un objet PEAR_Error.
Il n'y a pas de différenciation entre les sévérités des erreurs ou leurs origines.
Le seul moyen de déterminer sa sévérité est d'utiliser PEAR_ERROR_TRIGGER
et les constantes E_USER_NOTICE/E_USER_WARNING/E_USER_ERROR
de la fonction trigger_error
de PHP. Mais utiliser ces fonctionnalités ne justifie pas les 900 lignes de code
simplement parce que trigger_error() fait partie de PHP lui-même.
Maintenant pour commencer à utiliser vos objets d'erreur nouvellement créés,
changez tous vos appels PEAR::raiseError()
ou PEAR::throwError() de cette forme ...
<?php
require_once 'PEAR.php';
// ancienne technique :
$error_specific_info = 'bad';
$e = PEAR::raiseError("error message - very " . $error_specific_info .
" way to do things", MON_PACKAGE_ERROR_FOO);
// une autre ancienne technique :
$e = PEAR::throwError("error message - very " . $error_specific_info .
" way to do things", MON_PACKAGE_ERROR_FOO);
?>
... à quelquechose comme ceci :
<?php
require_once 'PEAR/ErrorStack.php';
// nouvelle méthode
// version 1: accès a l'instance de pile
$stack = &PEAR_ErrorStack::singleton('monpackage');
$stack->push(MON_PACKAGE_ERROR_DBERROR, 'error',
array('query' => $query, 'dsn' => $dsn),
'Critical Database Error: Contact Administrator immediately');
// version 2: accès statique singleton : considérablement plus lent
PEAR_ErrorStack::staticPush('monpackage', MON_PACKAGE_ERROR_DBERROR, 'error',
array('query' => $query, 'dsn' => $dsn),
'Critical Database Error: Contact Administrator immediately');
?>
Dans le cadre d'une utilisation basique,
voilà tout ce qui est nécessaire de faire pour utiliser le paquet
PEAR_ErrorStack à la place de PEAR_Error.
Fonctionnalités avancées
Affichage du contexte d'une erreur
Dans certains cas, vous pouvez souhaiter personnaliser
la génération des erreurs.
Par exemple, pour beaucoup d'exceptions,
il est utile d'inclure comme informations contextuelles :
le nom de fichier, le numéro de ligne et le nom de la classe/fonction
afin de tracer l'erreur. Une option par défaut est disponible
et sera suffisante dans la majorité des cas, c'est :
PEAR_ErrorStack::getFileLine().
Toutes les erreurs de paquet n'apparaissent pas dans le fichier source
PHP lui même. Par exemple, les erreurs de compilation d'un moteur de
templates apparaissent dans le fichier source du modèle,
les erreurs de base de données peuvent se produire dans le texte
d'une requête, ou d'une manière interne au serveur de base de données,
les erreurs propres aux paquets Internet peuvent apparaître
sur un autre serveur. Toutes ces informations peuvent être incluses dans
un message d'erreur en utilisant une fonction de rappel capturant le contexte.
<?php
require_once 'PEAR/ErrorStack.php';
class DatabaseClass
{
var $_dbError;
var $_dbErrorMsg;
var $_dbQuery;
var $_dbPos;
/**
* Context grabber for the Database package
* @param integer Error Code
* @param array Error parameters passed into {@link PEAR_ErrorStack::push()}
* @param array Output of debug_backtrace() (not used in this callback)
*/
function getErrorContext($code, $params, $backtrace)
{
$context = array(
'errorcode' => $this->_dbError,
'errormsg' => $this->_dbErrorMsg,
'query' => $this->_dbQuery,
'pos' => $this->_dbPos,
);
return $context;
}
}
$db = new DatabaseClass;
PEAR_ErrorStack::staticSetContextCallback('Database', array(&$db, 'getErrorContext'));
?>
L'information contextuelle est formatée pour être facilement traitée
par une application externe. Si vous souhaitez que les informations
contextuelles soient incluses dans le message d'erreur, la fonction
de rappel de message d'erreur devrait être utilisée pour
ajouter cette information d'une manière humainement lisible
au message d'erreur, tel que décrit dans la section suivante.
Génération de messages d'erreur personnalisés
Il y a trois méthodes de PEAR_ErrorStack prévues pour générer des messages
d'erreur de manière efficace. Pour les utiliser, vous devez faire une des deux
choses suivantes :
Appeler
PEAR_ErrorStack::setErrorMessageTemplate(),
et configurer un tableau en mettant en correspondance les codes
d'erreurs et les modèles correspondants, comme ceci :
<?php
define('ERREUR_UN', 1);
define('ERREUR_DEUX', 2);
define('ERREUR_TROIS', 3);
define('ERREUR_QUATRE', 4);
require_once 'PEAR/ErrorStack.php';
$stack = &PEAR_ErrorStack::singleton('monpackage');
$messages = array(
ERREUR_UN => 'The gronk number %num% dropped a %thing%',
ERREUR_DEUX => 'The %list% items were missing',
ERREUR_TROIS => 'I like chocolate, how about %you%?',
ERREUR_QUATRE => 'and a %partridge% in a pear %tree%',
);
$stack->setErrorMessageTemplate($messages);
?>
La substitution est faite en utilisant la fonction PHP
str_replace,
et est très simple. Basiquement, si un nom de variable est entouré
de signes pourcentage (%), il sera remplacé par la valeur passée
dans le tableau associatif. Si un tableau est passé en paramètre à la méthode,
array('nomvar' => 'valeur');
,
toutes les occurrences de %nomvar% seront remplacées par leur valeur.
De plus, si les valeurs passées sont des objets,
les méthodes de substitution chercheront dans l'objet
une méthode nommée "__toString()()"
et si elle est trouvée, elle sera utilisée pour convertir l'objet en chaîne.
Si un tableau de chaînes est passé, elles seront jointes par des virgules.
Appel de
PEAR_ErrorStack::setMessageCallback(),
et paramétrer une fonction
ou méthode générant un message d'erreur personnalisé.
Ceci est probablement la meilleur option dans la majorité
des situations complexes, dans la mesure où cela autorise les utilisateurs
à remplacer ou même étendre la fonction de callback existante en utilisant
PEAR_ErrorStack::getMessageCallback().
Par exemple:
<?php
require_once 'PEAR/ErrorStack.php';
class foo
{
var $_oldcallback;
function callback(&$stack, $err)
{
$message = call_user_func_array($this->_oldcallback, array(&$stack, $err));
$message .= "File " . $err['context']['file'];
return $message;
}
}
$a = new foo;
$stack = &PEAR_ErrorStack::singleton('otherpackage');
$a->_oldcallback = $stack->getMessageCallback('otherpackage');
$stack->setMessageCallback(array(&$a, 'callback'));
?>
Il existe plusieurs scénarios dans lesquels le contrôle
d'une fine granularité sur la levée d'une erreur est absolument nécessaire.
Une fonction générique de callback de gestion des erreurs implique
que chaque erreur levée sera gérée dans la même fonction de rappel.
Même si PEAR_ErrorStack est conçu pour opérer avec des fonctions de
rappel indépendantes pour chaque paquet, une gestion générique est possible
à travers la méthode
PEAR_ErrorStack::staticPushCallback().
Ce n'est pas différent du mode PEAR_ERROR_CALLBACK de gestion d'erreurs PEAR_Error.
La puissance réelle de PEAR_ErrorStack vient du mécanisme de callback lui-même.
Le mécanisme de callback PEAR_Error n'a pas vraiment d'effet sur le message d'erreur
- tous les traitements d'erreurs doivent être effectuées dans la méthode ou la fonction
de rappel elle-même. Le mécanisme de rappel PEAR_ErrorStack peut influencer l'erreur
à travers l'utilisation de trois constantes :
PEAR_ERRORSTACK_IGNORE informe la pile qu'elle doit ignorer l'erreur,
comme si elle n'était jamais arrivée. L'erreur ne sera ni loguée, ni mise sur la pile.
Elle pourra cependant être retournée par
PEAR_ErrorStack::push().
PEAR_ERRORSTACK_PUSH informe la pile qu'elle doit empiler l'erreur
sur la pile des erreurs, mais ne pas la loguer.
PEAR_ERRORSTACK_LOG informe la pile qu'elle ne doit pas empiler l'erreur
mais seulement la loguer.
<?php
define('ERROR_CODE_ONE',1);
define('ERROR_CODE_TWO',2);
define('ERROR_CODE_THREE',3);
require_once 'PEAR/ErrorStack.php';
require_once 'Log.php';
function somecallback($err)
{
switch($err['code']){
case ERROR_CODE_ONE:
return PEAR_ERRORSTACK_IGNORE;
break;
case ERROR_CODE_TWO:
return PEAR_ERRORSTACK_PUSH;
break;
case ERROR_CODE_THREE:
return PEAR_ERRORSTACK_LOG;
break;
} // switch
}
$log = &Log::factory('display');
$stack = &PEAR_ErrorStack::singleton('monpackage');
$stack->setLogger($log);
$stack->pushCallback('somecallback');
$stack->push(ERROR_CODE_ONE);
$stack->push(ERROR_CODE_TWO);
$stack->push(ERROR_CODE_THREE);
var_dump(PEAR_ErrorStack::staticGetErrors());
// simule PEAR_ERROR_CALLBACK avec des callbacks spécifiques pour
// monpackage.
// Chaque autre paquet loguera simplement l'erreur,
// seul les erreurs mypackage sont poussées sur la pile de manière
// conditionnelle
class myclass {
function acallback($err)
{
return PEAR_ERRORSTACK_LOG;
}
}
$stack2 = PEAR_ErrorStack::singleton('anotherpackage');
$stack3 = &PEAR_ErrorStack::singleton('thirdpackage');
PEAR_ErrorStack::setDefaultCallback(array('myclass', 'acallback'));
?>
Reconditionner les erreurs d'un package à un autre
L'usage le plus évident d'une fonction de callback d'erreurs implique
un scénario commun à beaucoup d'applications niveau utilisateur qui
emploient des paquets systèmes.
Si vous écrivez un Système de gestion de contenu (CMS) en utilisant
le paquet PEAR DB, c'est généralement une mauvaise idée d'afficher
des erreurs du niveau base de données quand un utilisateur clique sur
un lien pour ajouter un message à un forum.
PEAR_ErrorStack peut être utilisé pour reconditionner cette erreur comme
une erreur propre à votre paquet.
<?php
define('MON_PACKAGE_ERROR_DBDOWN',1);
require_once 'PEAR/ErrorStack.php';
function repackage($err)
{
if ($err['package'] == 'DB') {
$mystack = &PEAR_ErrorStack::singleton('monpackage');
$mystack->push(MON_PACKAGE_ERROR_DBDOWN, 'error', array('olderror' => $err));
// ignore l'erreur DB ,
// mais la sauvegarde dans l'erreur monpackage afin d'être loguée
return PEAR_ERRORSTACK_IGNORE;
}
}
?>
émulation de l'opérateur @
Une des forces de PEAR_Error difficile à utiliser est la méthode
PEAR::expectError().
Il est possible de rendre silencieuses les erreurs régulières de PHP
en utilisant l'opérateur @ de la façon suivante :
<?php
@file_get_contents();
?>
émuler ce comportement à l'aide de PEAR_ErrorStack est simple.
<?php
define('ERROR_CODE_SOMETHING', 1);
require_once 'PEAR/ErrorStack.php';
function silence($err)
{
// ignore toutes les erreurs
return PEAR_ERRORSTACK_IGNORE;
}
$stack = &PEAR_ErrorStack::singleton('monpackage');
$stack->pushCallback('silence');
$stack->push(ERROR_CODE_SOMETHING);
?>
PEAR_ErrorStack peut passer un cran au dessus
et seulement loguer les erreurs ou seulement les placer
sur la pile d'erreurs en utilisant les deux autres constantes.
Finalement, des erreurs particulières peuvent être levées
et toutes les autres ignorées.
<?php
define('SOMEPACKAGE_ERROR_THING', 1);
require_once 'PEAR/ErrorStack.php';
function silenceSome($err)
{
if ($err['package'] != 'somepackage') {
// ignore toutes les erreurs d'autres packages
return PEAR_ERROR_IGNORE;
}
if ($err['code'] != SOMEPACKAGE_ERROR_THING) {
// ignore tous les autres codes d'erreurs
return PEAR_ERRORSTACK_IGNORE;
}
}
$stack = &PEAR_ErrorStack::singleton('monpackage');
$stack->pushCallback('silenceSome');
$stack->push(ERROR_CODE_SOMETHING);
?>
Compatibilité descendante avec PEAR_Error,
et ascendante avec les exceptions PHP5
PEAR_ErrorStack peut être aussi programmée pour
générer automatiquement un objet PEAR_Error en utilisant
PEAR::raiseError(),
en passant simplement le paramètre de compatibilité Pear_Error à TRUE
de la manière suivante :
PEAR_ErrorStack peut se coordonner avec la nouvelle classe PEAR_Exception
pour la convertion en exception avec ce code :
Vous pouvez définir le nom de l'exception qui sera retournée
en utilisant le code suivant :
<?php
require_once 'PEAR/ErrorStack.php';
require_once 'PEAR/Exception.php';
$stack = &PEAR_ErrorStack::singleton('mypackage');
$stack->push(1, 'erreur de test');
throw new PEAR_Exception('ne fonctionne pas', $stack->pop());
?>