Dans cet article, nous allons aborder le pattern observateur.
Ce pattern permet de définir une relation ente objet de type un-à-plusieurs. Il permet d’informer une liste d’observateurs d’un changement d’état de l’objet dont ils dépendent.
Nous allons nous servir de deux interfaces fournit dans la SPL.
Le résultat que nous voulons obtenir est très simple, nous avons un objet “Personne” représentant un utilisateur de notre application et nous voulons mettre en place un système permettant d’enregistrer chaque connexion de l’utilisateur dans notre base de données.
Développement sans le pattern observateur
Voici la classe utilisateur:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php
class Personne
{
public function connexion($login, $password)
{
//Vérification des identifiants
//Traitement de la connexion...
}
}
?> |
La solution la plus simple et la plus rapide pour enregistrer la connexion de l’utilisateur dans notre base de données est de rajouter le traitement pour l’enregistrement directement dans la méthode “connexion” comme ceci:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php
class Personne
{
public function connexion($login, $password)
{
//Vérification des identifiants
//Traitement de la connexion...
$pdo = new PDO('mysql:host=localhost;dbname=database', 'root', '');
$pdo->prepare('INSERT INTO log_connexion VALUES(:login, :date)');
$pdo->execute(array(
':login' => $login,
':date' => date('Y-m-d H:i:s')
));
}
}
?> |
Cette façon de faire fonctionne, cependant elle n’est pas optimisée et surtout très couplée. En effet, le traitement de la connexion n’a rien à voir avec l’enregistrement de la connexion dans la base de données. Nous pourrions rajouter une méthode dans cette classe chargé d’enregistrer la connexion de l’utilisateur et ainsi sortir le traitement d’enregistrement de la méthode “connexion”.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?php
class Personne
{
public function connexion($login, $password)
{
//Vérification des identifiants
//Traitement de la connexion...
$this->enregistrement($login);
}
public function enregistrement($login)
{
$pdo = new PDO('mysql:host=localhost;dbname=database', 'root', '');
$pdo->prepare('INSERT INTO log_connexion VALUES(:login, :date)');
$pdo->execute(array(
':login' => $login,
':date' => date('Y-m-d H:i:s')
));
}
}
?> |
Dans cet exemple, le code est un peu mieux, mais pas encore au point. La méthode “connexion” fait un appel direct à une méthode contenant un traitement spécifique. Ceci dit, ce code fonctionne et ne modifie que très peu la méthode de connexion. Et si nous voulions par la suite rajouter une méthode permettant d’informer l’utilisateur de sa connexion en cas de vol de ses identifiants, comment ferions-nous ?
Une nouvelle fois, nous pourrions rajouter une méthode “avertir” dans la classe “Personne” contenant le traitement (une notification par mail) et pour finir, rajouter un appel à cette méthode dans la méthode “connexion”.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?php
class Personne
{
public function connexion($login, $password)
{
//Vérification des identifiants
//Traitement de la connexion...
$this->enregistrement($login);
$this->avertir();
}
public function enregistrement($login)
{
$pdo = new PDO('mysql:host=localhost;dbname=database', 'root', '');
$pdo->prepare('INSERT INTO log_connexion VALUES(:login, :date)');
$pdo->execute(array(
':login' => $login,
':date' => date('Y-m-d H:i:s')
));
}
public function avertir()
{
mail('email@domain.tld', 'Alerte de connexion', 'Message');
}
}
?> |
En agissant ainsi, il est évident qu’à chaque action voulue au changement d’état de la méthode “connexion”, nous devrions rajouter autant d’appels et de méthodes nécessaire au nombre d’actions voulues. Voyons un peu ce que nous pourrions faire avec l’aide du pattern observateur…
Avec le pattern !
Pour commencer, nous allons sortir les méthodes “enregistrement” et “avertir” de la classe “Personne”. Le but est simple, nous ne voulons pas que la classe “Personne” ait connaissance du traitement de ces deux méthodes, car cela ne la concerne tout simplement pas !
Nous nous retrouvons donc avec trois classes, la classe “Personne”, la classes “Mail” ainsi que la classe “Bdd”. Dans la classe “Personne”, nous allons implémenter l’interface “SplSubject” qui nous oblige à écrire trois méthodes:
- public function attach(SplObserver $observer);
- public function detach(SplObserver $observer);
- public function notify();
La méthode “attach” va nous permettre d’enregistrer un observateur, la méthode “detach” de supprimer un des observateurs et enfin, la méthode “notify” va permettre de notifier l’ensemble des observateurs d’un changement d’état sans se soucier de ce qui se passe ensuite. Nous sauvegarderons l’ensemble des observateurs dans un tableau.
Voici le nouveau code:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<?php
class Personne implements SplSubject
{
private $_observers = array();
private $_login;
public function connexion($login, $password)
{
//Vérification des identifiants
//Traitement de la connexion...
$this->_login = $login;
$this->notify();
}
public function getLogin()
{
return $this->_login;
}
public function attach(SplObserver $observer)
{
$this->_observers[spl_object_hash($observer)] = $observers;
}
public function detach(SplObserver $observer)
{
unset($this->_observers[spl_object_hash($observer)]);
}
public function notify()
{
foreach ($this->_observers as $object)
{
$object->update($this);
}
}
}
?> |
Nous implémentons donc les 3 méthodes décrites par l’interface “SplSubject” et pour finir, nous rajoutons un appel direct à “notify” qui va se charger d’informer tout les observateurs de cette classes. Seulement pour que les observateurs soit capable de réagir au changement d’état de la classe “Personne”, il devront obligatoirement implémenter la méthode “update”. Pour cela, nous allons implémenter la seconde interface de la SPL, “SplObserver”.
Classe Bdd:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php
class Bdd implements SplObserver
{
public function update(SplSubject $subject)
{
$pdo = new PDO('mysql:host=localhost;dbname=database', 'root', '');
$pdo->prepare('INSERT INTO log_connexion VALUES(:login, :date)');
$pdo->execute(array(
':login' => $login,
':date' => date('Y-m-d H:i:s')
));
}
}
?> |
La classe Mail:
|
1 2 3 4 5 6 7 8 9 10 11 |
<?php
class Mail implements SplObserver
{
public function udpate(SplSubject $subject)
{
mail($subject->getLogin(), 'Alerte de connexion', 'Message');
}
}
?> |
Et enfin pour finir, l’utilisation de tout ça:
|
1 2 3 4 5 6 7 8 9 10 |
<?php
$personne = new Personne();
$personne->attach(new Bdd());
$personne->attach(new Mail());
$personne->connexion('mail@domaine.tld', 'password');
?> |
Voici le résultat final:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<?php
class Personne implements SplSubject
{
private $_observers = array();
private $_login;
public function connexion($login, $password)
{
//Vérification des identifiants
//Traitement de la connexion...
$this->_login = $login;
$this->notify();
}
public function getLogin()
{
return $this->_login;
}
public function attach(SplObserver $observer)
{
$this->_observers[spl_object_hash($observer)] = $observers;
}
public function detach(SplObserver $observer)
{
unset($this->_observers[spl_object_hash($observer)]);
}
public function notify()
{
foreach ($this->_observers as $object)
{
$object->update($this);
}
}
}
class Bdd implements SplObserver
{
public function update(SplSubject $subject)
{
$pdo = new PDO('mysql:host=localhost;dbname=database', 'root', '');
$pdo->prepare('INSERT INTO log_connexion VALUES(:login, :date)');
$pdo->execute(array(
':login' => $login,
':date' => date('Y-m-d H:i:s')
));
}
}
class Mail implements SplObserver
{
public function udpate(SplSubject $subject)
{
mail($subject->getLogin(), 'Alerte de connexion', 'Message');
}
}
$personne = new Personne();
//Attachement des deux observateurs
$personne->attach(new Bdd());
$personne->attach(new Mail());
//Connexion de la personne
$personne->connexion('mail@domaine.tld', 'password');
?> |
Et voila, notre code est découplé, nous n’avons plus besoin de retourner dans la classe “Personne” pour rajouter du traitement lié à son changement d”état et elle n’a plus aucune connaissances des traitements des différentes classes qui viennent se liées à elle !