Présentation
Le design pattern "Chain of Responsibility" est souvent peu connu et pourtant il peut être très utile dans la gestion des logs et erreurs.
Généralement, la liste des choix disponibles pour le traitement des problèmes se limite à :
- générer des logs
- envoyer un e-mail à l'administrateur
- générer une exception.
Le design pattern "Chain of Responsibility" permet de réduire cette complexité en créer une seule entité chargée de gérer tous les niveaux d'alerte et d'affecter ceux-ci au niveau de responsabilité correspondant.
Mise en pratique
Concrètement, le design pattern "Chain of Responsibility" consiste à :
- créer une liste chaînée d'objet, chacun ayant un seuil de déclenchement et ayant des actions propres,
- déléguer la connaissance du point d'entrée de la chaîne de responsabilité à un objet façade.
Pour notre gestionnaire d'alerte, le critère de déclenchement sera la criticité de l'alerte:
- MIN : correspondant à des alertes mineures
- MAJ : correspondant à des alertes majeures
- FATAL : correspondant à des alertes fatales.
Les éléments de la chaîne de responsabilité
Pour garder un cohérence dans le traitement, chaque élément de la chaîne de responsabilité héritera d'un même objet.
Cette objet sera déclaré en abstract pour ne pas pouvoir être instancié.
abstract class Warning
{
// criteria of criticity
const MIN = 1;
const MAJ = 2;
const FATAL = 3;
// trigger threshold
protected $threshold;
// next element in the chain of responsability
protected $next;
// specific method calling by this element
protected $action;
abstract public function __construct();
// setter to defined the next element in the chain of responsability
public function setNext( Warning $l )
{
$this->next = $l;
return $this;
}
//
// Compare the threshold with the criticy and trigger the specific action of this element
// Finally, call the next element in the chain
//
// parameter
// - criticity : current criticity of the warning
// - class : class of the calling object
// - method : method of the calling object
// - line : line of the calling object
// - message : specific message describing the warning
// - code : specific code refering to the warning (optional)
public function __warning( $criticity, $message, $class, $method, $line )
{
if( $criticity >= $this->threshold )
{
$method = $this->action;
$this->$method( $criticity, $message, $class, $method, $line );
}
if( false === is_null( $this->next ) )
{
$this->next->__warning( $criticity, $message, $class, $method, $line );
}
}
}
?>
Le 1er élément de la chaîne de responsabilité va être en charge de loguer toutes les alertes quelque soit le niveau de criticité.
< ?php
class WarningLogger extends Warning
{
private $logFile;
public function __construct( $threshold, $logFile )
{
$this->threshold = $threshold;
$this->action = "__log";
$this->logFile = $logFile;
}
private function __log( $criticity, $message, $class, $method, $line )
{
$message = sprintf("[%s] | %s | %s | %s | %s - [%s]\n",date("d/m/Y h:m:s"), $criticity, $class, $method, $line, $message );
$h = @fopen($this->logFile,'a');
if( false === $h ) WarningProcessor::getInstance()->__warning(Warning::FATAL,'Impossible to open log file : ['.$this->logFile.'] ['.$message.']', $class, $method, $line);
if( false === @fwrite($h, $mess)) WarningProcessor::getInstance()->__warning(Warning::FATAL,'Impossible to write log in file : '.$this->logFile.'] ['.$message.']', $class, $method, $line);
fclose($h);
}
}
?>
L'objet WarningLogger reçoit en paramètre lors de son instanciation :
- $threshold : qui aura la valeur Warning::MIN pour être déclenché quelque soit le niveau de criticité
- $logFile : correspondant au nom du fichier de log.
Le 2ème et 3ème élement vont respectivement envoyé un e-mail à l'administrateur du site et lancer une exception.
< ?php
class WarningMailer extends Warning
{
private $email;
public function __construct($threshold, $email)
{
$this->threshold = $threshold;
$this->action = '__mail';
$this->email = $email;
}
protected function __mail( $criticity, $message, $class, $method, $line )
{
$message = sprintf("TIME = [%s]\nCRITICITY = [%-5s]\nCLASS = [%s]\nMETHOD = [%s]\nLINE = [%s]\nMESSAGE = [%s]\n",time(),$criticity, $class, $method, $line,$message);
$b = mail($this->email,'Warning Mail',$message);
if( false === $b ) WarningProcessor::getInstance()->__warning(Warning::FATAL,'Impossible to mail warning.', $class, $method, $line);
}
}
?>
< ?php
class WarningThrowner extends Warning
{
public function __construct($threshold)
{
$this->threshold = $threshold;
$this->action = '__throw';
}
protected function __throw( $criticity, $message, $class, $method, $line )
{
$message = sprintf("TIME = [%s]\nCRITICITY = [%-5s]\nCLASS = [%s]\nMETHOD = [%s]\nLINE = [%s]\nMESSAGE = [%s]\n",time(),$criticity, $class, $method, $line,$message);
throw new Exception( $message );
}
}
?>
La façade de la chaîne de responsabilité
Après avoir vu les différents éléments qui vont consituer ma chaîne de responsabilité, je vais maintenant m'appliquer à mettre en place une façade.
< ?php
/*
** Creation of the Chain of responsability
** Unique Entry of each call
*/
class WarningProcessor
{
private $chain;
static private $instance;
public function __construct( )
{
// creation of log manger with priority mask and log filename
$logger = new WarningLogger( Warning::MIN, $GLOBALS['logFile'] );
// creation of mail manager with priority mask and administrator e-mail
$mailer = new WarningMailer( Warning::MAJ, $GLOBALS['warningMail'] );
// creation of exception manager
$throwner = new WarningThrowner( Warning::FATAL) ;
// mail priority is under exception priority
$mailer->setNext($throwner);
// log priority is under mail priority
$logger->setNext($mailer);
// Warningchainor begins chain of responsability with the smallest priority
$this->chain = $logger;
self::$instance = $this;
}
static function getInstance( )
{
if( empty( self::$instance ) ) self::$instance = new WarningProcessor( );
return self::$instance;
}
public function __warning( $criticity, $message, $class, $method, $line )
{
// call the first element in the chain of responsability
$this->chain->__warning( $criticity, $message, $class, $method, $line );
}
}
?>
La fonction __construct instancie les différents éléments de la chaîne en passant en paramètres les seuils de déclenchements et les paramètres spécifiques à chaque élement : le nom du fichier de log et l'e-mail de l'administrateur du site.
Ensuite, le constructeur relie les éléments entre eux pour véritablement constituer la chaîne. A noter qu'ici, j'ai respecté l'ordre croissant des seuils mais que de toute façon à chaque déclenchement de la chaîne, celle-ci est toujours parcouru entièrement et que chaque élement va déterminer individuellement s'il a une action à effectuer.
La fonction getInstance est là car veux en plus que ma façade soit un singleton, encore un autre Design Pattern.
La fonction __warning sert d'appel pour enclencher la chaîne.
Conclusion
La mise en place du Design Pattern "Chaîne des responsabilité" me permet de centraliser et d'abstraire la gestion des erreurs en laissant au développeur le choix du niveau de criticité des alertes.
De plus, il permet de mettre en pratique 3 design patterns à la fois :
Pour finir, l'ensemble des sources avec un fichier exemple sera bientôt disponible dans la section "Ressource".















