Vous pouvez me suivre sur : twitter linkedin rss feed google buzz

Mise en pratique du Design Pattern "Chain of responsibility"

Mise en pratique du design pattern "Chain of responsibility" pour le webgame eXodus
10- 07- 2009

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.
Bien qu'il existe différents composants disponibles répondant unitairement à ces besoins, le développeur doit souvent mixer les solutions, comme par exemple sur détection d'un comportement utilisateur invasif et non-autorisé alors il faut avertir l'administrateur par e-mail, tracer les informations dans un log et générer une exception pour stopper le traitement.


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".



Vous avez aimé cet article ? Alors partagez le :
  • Email
  • Twitter
  • Facebook
  • delicious
  • Digg it
  • LinkedIn
  • Technorati
  • StumbleUpon
  • Reddit
  • NewsVine
  • Google
  • YahooMyWeb
  1. derekpm
    12/07/2009 15:42:16
    Rather interesting. Has few times re-read for this purpose to remember. Thanks for interesting article. Waiting for trackback
  2. Siko
    16/09/2009 13:37:13
    Article très intéressant je en connaissait pas ce pattern qu'est la chaine de responsabilité. Mais petite question, a quel moment renseigne t'on l'erreur ? et comment sait il de quel niveau elle est ?
  3. sbareau
    16/09/2009 15:24:11
    L'erreur est et son niveau de criticité sont renseignés lors de la détection et en appelant l'objet façade: le WarningProcessor. On substitue l'appel à une exception (ou à son traitement standard d'erreur) à la chaîne de responsabilité. Un exemple est fourni avec les sources : http://www.zen-in-progress.com/wp-content/uploads/php/Chain_of_responsibility/Chain_of_responsibility.zip
Ajouter un commentaire