Introduction a l'utilisation de PEAR_ErrorStack
Cette classe est disponible en tant que partie du package PEAR. Fonctionnalités incluse :
Complètement documentée et fournie avec des tests unitaires complets
étonnamment rapide - noie complètement PEAR_Error
Erreurs spécifiques au package
Niveaux d'erreurs (notice/avertissement/erreur/exception)
Les données contextuelles de l'erreur sont sauvées indépendamment du message d'erreur
Erreurs en cascade - les erreurs parentes peuvent être spécifiées.
Génération dynamique des messages d'erreur, autorisant la génération de messages d'erreur multiples et distincts à partir du même objet erreur
Des fonctions de rappel (callbacks) sophistiquées sont disponibles, permettant la génération des messages d'erreur, de contextes d'erreurs, voir : Error Context Display, Custom Error Message Generation, et controlling error generation
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.
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.
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,
<?php
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.
<?php
array('nomvar' => array('premier', 'second', 'tiers'));
// Deviendra : 'premier, second, tiers'
?>
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'));
?>
étendre PEAR_ErrorStack avec votre propre classe et remplacer PEAR_ErrorStack::getErrorMessageTemplate() ou PEAR_ErrorStack::getErrorMessage(). Afin de garantir que cette classe sera utilisée par d'autres pacquets / applications, utilisez ce code juste après la déclaration de la classe :
<?php
PEAR_ErrorStack::singleton('monpackage', false, null, 'MyPEAR_ErrorStack');
?>
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'));
?>
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;
}
}
?>
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);
?>
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 :
<?php
require_once 'PEAR/ErrorStack.php';
$stack = &PEAR_ErrorStack::singleton('monpackage', false, null, 'PEAR_ErrorStack', true);
?>
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());
?>