[php5] observer design pattern

Description

Suite à cette source :
http://www.phpcs.com/codes/PROGRAMMATION-EVENEMENTIELLE-PHP_41477.aspx
que j'ai trouvée très intéressante, j'ai poussé un peu les recherches.
J'ai appliqué l'observer design pattern (une recherche sur google vous en expliquera les tenants et les aboutissants) afin d'obtenir un package simple et facile à mettre en oeuvre permettant d'exécuter en cascade toute une série de traitements lors du déclenchement d'un évènement (côté serveur, on parle ici de programmation évènementielle côté serveur).

Il y a 2 fichiers, un pour les versions de PHP5 >= 5.1 (redéfinissant quand même certaines classes SPL au cas où), et une pour PHP5 < 5.1.
Les différences résident dans le typage forcé de l'appel de certaines méthodes, et dans l'ajout d'une petite exception dans la méthode SplSubject::notify().
J'ai aussi dans les deux versions modifié la méthode SplObjectStorage::rewind(), simplement parce sur PHp5.0.4 ça ne passait pas. Bref...vous rentrerez dans les détails si vous le voulez.

Le principe :
on a des observateurs, et un sujet observé. On lie au sujet observé des observateurs via un collecteur d'objets (ça, c'est pour facilité la mise ne place) : SplObjecStorage.
A chaque changement d'état important, les observateurs liés sont alertés via un appel à SplSubject::notify().
Ils réagissent en fonction de ce qu'ils doivent faire, simplement.

J'ai repris l'analogie de la voiture et du dépassement de la vitesse autorisée.
L'avantage ici est de pouvoir déclencher autant de traitements qu'on le veut sur 1 seul changement d'état. CX'est un peu le principe des triggers en SQL.

Source / Exemple :


<?php
/**

  • defining abstract implementations
  • /
if (!interface_exists ('Countable')) { /**
  • Interface Countable
  • @author: php
*
  • /
interface Countable { public function count(); } } if (!interface_exists ('SplObserver')) { /**
  • Interface SplObserver
  • @author: php
*
  • /
interface SplObserver { public function update(SplSubject $subject); } } if (!interface_exists ('SplSubject')) { /**
  • Interface SplSubject
  • @author: php
*
  • /
interface SplSubject { public function attach(SplObserver $observer); public function detach(SplObserver $observer); public function notify(); } } if (!class_exists ('SplObjectStorage')) { /**
  • Interface SplObjectStorage
  • @author: php
*
  • /
class SplObjectStorage implements Iterator, Countable { /**
  • Array of objects
*
  • @var array
  • /
private $storage = array(); /**
  • Array of objects index
*
  • @var int
  • /
private $index = 0; /**
  • public function rewind
  • Rewind array pointer
  • @Return void
  • /
public function rewind() { rewind($this->storage); } /**
  • public function valid
  • Checks if current loop iteration is valid
  • @Return boolean
  • /
public function valid() { return key($this->storage) !== false; } /**
  • public function key
  • Returns current key
  • @Return int
  • /
public function key() { return $this->index; } /**
  • public function current
  • Returns current array entry
  • @Return object
  • /
public function current() { return current($this->storage); } /**
  • public function next
  • Iterates to the next entry
  • @Return void
  • /
public public function next() { next($this->storage); $this->index++; } /**
  • public function count
  • Returns the number of entries in the array of objects
  • @Return int
  • /
public function count() { return count($this->storage); } /**
  • public function contains
  • Checks if an object is in the collector array
  • @Param object $obj : object to check
  • @Return boolean
  • /
public function contains($obj) { if (is_object($obj)) { foreach($this->storage as $object) { if ($object === $obj) { return true; } } } return false; } /**
  • public function attach
  • Attaches an object to the collector array
  • @Param object $obj : object to attach
  • @Return void
  • /
public function attach($obj) { if (is_object($obj) && !$this->contains($obj)) { $this->storage[] = $obj; } } /**
  • public function detach
  • Detaches an object to the collector array
  • @Param object $obj : object to detach
  • @Return void
  • /
public function detach($obj) { if (is_object($obj)) { foreach($this->storage as $idx => $object) { if ($object === $obj) { unset($this->storage[$idx]); $this->rewind(); return; } } } } } } /**
  • starting concrete implementations
  • /
/**
  • class car implements SplSubject
  • @author: Johan Barbier <johan.barbier@gmail.com>
*
  • /
class car implements SplSubject { /**
  • Name of the car
*
  • @var string
  • /
private $sName; /**
  • State of the car
*
  • @var int
  • /
private $iState = 0; /**
  • Speed of the car
*
  • @var int
  • /
private $iSpeed = 0; /**
  • Collector array of observers
*
  • @var SplObserver
  • /
private $aObservers; /**
  • public function __construct
  • constructor
  • Initializes car's nname
*
  • @param string $sName
  • /
public function __construct ($sName) { $this -> sName = $sName; $this -> aObservers = new SplObjectStorage; } /**
  • public function start
  • Starts the car and notify observers
  • @Return void
  • /
public function start () { $this -> iState = 1; $this -> notify(); } /**
  • public function stop
  • Stops the car and notify observers
  • @Return void
  • /
public function stop () { $this -> iState = 0; $this -> iSpeed = 0; $this -> notify(); } /**
  • public function accelerate
  • Accelerates the car and notify the observers
  • @Param int $iAcceleration : how many Kmh you want to accelerate
  • @Return void
  • /
public function accelerate ($iAcceleration) { if (0 === $this -> iState) { throw new Exception ('You must start the car before accelerating...'); } if (!is_int ($iAcceleration) || $iAcceleration < 0) { throw new Exception ('Wrong value for car::accelerate()'); } $this -> iSpeed += $iAcceleration; $this -> notify(); } /**
  • public function attach
  • Attaches an observer
  • @Param SplObserver $observer : the observer to attach
  • @Return boolean
  • /
public function attach(SplObserver $observer) { if(!$this->aObservers->contains($observer)) { $this->aObservers->attach($observer); } return true; } /**
  • public function detach
  • Detaches an observer
  • @Param SplObserver $observer : the observer to detach
  • @Return boolean
  • /
public function detach(SplObserver $observer) { if(!$this->aObservers->contains($observer)) { return false; } $this->aObservers->detach($observer); return true; } /**
  • public function notify
  • Notify all the observers
  • @Return void
  • /
public function notify() { foreach($this->aObservers as $observer) { $observer->update($this); } } /**
  • public function __get
  • Get properties value
  • @Param string $sProp : property's name
  • @Return mixed
  • /
public function __get ($sProp) { switch ($sProp) { case 'STATE' : return $this -> iState; break; case 'SPEED' : return $this -> iSpeed; break; case 'NAME' : return $this -> sName; break; default : throw new Exception ($sProp.' cannot be read'); } } /**
  • public function __set
  • Set properties value
  • @Param string $sProp : property's name
  • @Param mixed $mVal : property's value
  • @Return void
  • /
public function __set ($sProp, $mVal) { throw new Exception ($sProp.' cannot be set'); } } /**
  • class carStateObserver implements SplObserve
  • @author Johan Barbier <johan.barbier@gmail.com>
*
  • /
class carStateObserver implements SplObserver { /**
  • Stored subject's state
*
  • @var int
  • /
private $iSubjectState; /**
  • public function update
  • updater. Echoes subject's state (if the car is stopped or started)
  • @param SplSubject $subject
  • @return void
  • /
public function update(SplSubject $subject) { switch ($subject -> STATE) { case 0 : if (is_null ($this -> iSubjectState)) { echo $subject -> NAME.' has not been started<br />'; } else { echo $subject -> NAME.' has been stopped<br />'; } $this -> iSubjectState = 0; break; case 1: if (1 !== $this -> iSubjectState) { echo $subject -> NAME.' has been started<br />'; $this -> iSubjectState = 1; } break; default : throw new Exception ('Unexpected error in carStateObserver::update()'); } } } /**
  • class carSpeedObserver implements SplObserver
  • @author Johan Barbier <johan.barbier@gmail.com>
*
  • /
class carSpeedObserver implements SplObserver { /**
  • public function update
  • updater. Echoes subject's speed
  • @param SplSubject $subject
  • @return void
  • /
public function update(SplSubject $subject) { if (0 !== $subject -> STATE) { echo $subject -> NAME.' speed is '.$subject -> SPEED.'Kmh<br />'; } } } /**
  • class carOverspeedObserver implements SplObserver
  • @author Johan Barbier <johan.barbier@gmail.com>
*
  • /
class carOverspeedObserver implements SplObserver { /**
  • public function update
  • updater. Catches the subject if it is overspeeding!
  • @param SplSubject $subject
  • @return void
  • /
public function update(SplSubject $subject) { if ($subject -> SPEED > 130) { throw new Exception ('Speed limit is 130! You lost your driver license !'); } } } try { $oCar = new car ('AUDI A4'); $oObs = new carStateObserver; $oObs2 = new carSpeedObserver; $oObs3 = new carOverspeedObserver; $oCar -> attach($oObs); $oCar -> attach($oObs2); $oCar -> attach($oObs3); $oCar -> start(); $oCar -> accelerate(20); $oCar -> accelerate(30); $oCar -> stop(); $oCar -> start(); $oCar -> accelerate(50); $oCar -> accelerate(70); $oCar -> accelerate(100); $oCar -> accelerate(150); } catch (Exception $e) { echo $e -> getMessage (); } ?>

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.