Catégories: PHP | Langage | Article
Singletons et Multitons avec PHP 5
Un article de ToutProgrammer.com.
Les Design Patterns sont une solution de conception élégante et robuste qui évite d'avoir à réinventer la roue. Avec ses évolutions majeures telles que la visibilité des membres ou les constructeurs/destructeurs unifiés, PHP 5 autorise la conception par design pattern. Voyons comment mettre en œuvre les patterns simples que sont le singleton et le multiton.
Sommaire |
[modifier] Le singleton qu'est-ce que c'est ?
Le singleton est ce que l'on appelle un design pattern. Son but est d'éviter de multiplier les objets (et donc les ressources en mémoire) identiques. Autrement dit, il offre la possibilité d'utiliser un même (on parle souvent d'unicité) objet tout au long d'un programme, ce qui permet donc d'améliorer votre application.
[modifier] La théorie
Voici à quoi ressemble un singleton en PHP5:
<?php class Singleton { private function __construct() { echo 'construction du singleton<br/><br/>'; }//fin constructeur { { self::$_instance = new Singleton(); } return self::$_instance; }//fin fonction GetInstance }//fin classe Singleton ?>
Avant de passer à une explication, essayer ce script avec la classe Singleton citée ci-dessus:
Et observez le résultat affiché dans votre navigateur. Comme vous pouvez le voir, la variable $var est en fait toujours la même, elle n'est nullement recrée à chaque appel de la méthode GetInstance.
object(Singleton)#1 (0) {
}
object(Singleton)#1 (0) {
}
object(Singleton)#1 (0) {
}
.......
[modifier] Explications du fonctionnement
<?php ?>
On utilise la variable statique $_instance qui va stocker l'objet que l'on souhaite construire et ensuite utiliser. Cet attribut est définit comme privé et static.
<?php private function __construct() { echo 'construction du singleton<br/><br/>'; } ?>
Comme dans toute classe que vous avez pu créer, il y a un constructeur, il a été définit avec un accès privé car on ne veut pas que l'objet soit recréé à chaque fois, mais qu'il soit appelé uniquement par la méthode GetInstance si besoin est. Un constructeur est par définition public mais recrée à chaque fois un objet en mémoire, ce que nous ne voulons pas dans le cas du Singleton ;).
<?php { { self::$_instance = new Singleton(); } return self::$_instance; }//fin fonction GetInstance ?>
C'est ici que le Singleton montre sa puissance. La méthode GetInstance est déclarée statique, donc accessible sans qu'aucun objet soit instancié (Singleton::GetInstance() par exemple). Ensuite, on vérifie si l'attribut $_instance est déjà créé, si ce n'est pas le cas alors on fait appel au constructeur de la classe afin de créer un nouvel objet en mémoire. Etant donné que nous utilisons des accès statiques, donc sans création d'objet, il est impossible d'utiliser le mot clé $this, c'est pourquoi nous utilisons le mot clé selfqui permet d'accéder aux variables et méthodes statiques.
[modifier] La théorie c'est bien beau, mais ça sert à quoi ?
Justement voilà un exemple pratique qui je l'espère sera assez explicite. Lorsque vous faites interagir PHP avec un SGBD, vous serez peut-être obligé de faire plusieurs connexions à une base de données, ce qui vous ferait donc multiplier les ressources de connexion. Avec le Singleton nous réglons le problème une fois pour toute puisque dès qu'une ressource de connexion sera créée, toute autre tentative de création sera inutile et la ressource en cours vous sera renvoyée. Pour vous en rendre compte vous pouvez essayer ce petit script qui simule une connexion à une base de données MYSQL.
<?php //class.mysqlConnection.php class MysqlConnection { /***************************************************/ /* cette variable va nous servir pour le singleton */ /***************************************************/ //nom ou IP du serveur: private $_serveur; //login de connexion: private $_user; //mot de passe pour la connexion: private $_pass; //nom de la base de données: private $_db; /***************************************************/ /* <SINGLETON> */ /***************************************************/ { { echo "<b>l'instance n'existait pas</b><br/>"; self::$_instance = new MysqlConnection($serveur, $user, $pass, $bd); } else echo "<b>l'instance existe déjà</b><br/>"; return self::$_instance; }//fin getInstance /***************************************************/ /* </SINGLETON> */ /***************************************************/ //constructeur: private function __construct($serveur, $user, $pass, $db) { $this->_serveur = $serveur; $this->_user = $user; $this->_pass = $pass; $this->_db = $db; $this->_MysqlConnect(); }//fin mysqlConnection //permet la connexion et la sélection de la base de données: private function _MysqlConnect() { echo 'Connexion à la base de données :'.$this->_serveur.' '.$this->_user.' '.$this->_pass.' '.$this->_db.'<br/>'; }//fin fonction mysqlConnect() function __destruct() { echo "Destruction de l'objet MysqlConnection <br/>"; }//fin destructeur }//fin classe mysqlConnection ?>
Et ensuite testez cette classe avec une petite boucle for de ce type:
Vous devriez obtenir un résultat de ce style:
l'instance n'existait pas
Connexion à la base de données :le_serveur le_user le_pass la_db
object(MysqlConnection)#1 (4) {
["_serveur:private"]=>
string(10) "le_serveur"
["_user:private"]=>
string(7) "le_user"
["_pass:private"]=>
string(7) "le_pass"
["_db:private"]=>
string(5) "la_db"
}
l'instance existe déjà
object(MysqlConnection)#1 (4) {
["_serveur:private"]=>
string(10) "le_serveur"
["_user:private"]=>
string(7) "le_user"
["_pass:private"]=>
string(7) "le_pass"
["_db:private"]=>
string(5) "la_db"
}
....
Comme vous pourrez le constater, l'objet et dans ce cas la ressource de connexion à MYSQL n'a été créée qu'une seule fois puis ensuite réutilisée.
[modifier] Le Multiton : une variante du singleton
Le Multiton, comme le singleton, est une variante intéressante qui permet de n'instancier qu'une seule fois un même objet, tout en utilisant plusieurs objets du même type. Par exemple, imaginons que l'on ait besoin d'accéder à plusieurs bases distinctes. Si l'on accède plusieurs fois à la même base, il n'est pas nécessaire d'y ouvrir plusieurs connexions. Comment alors profiter des avantages du singleton pour pouvoir gérer plusieurs bases ? Il suffit, au lieu de stocker une seule instance de la connexion, de stocker plusieurs instances dans un tableau (associatif ou non), identifiables de manière unique. Pour identifier notre connexion, il suffit d'utiliser les paramètres de connexion à la base : serveur et base. Par exemple, en les concaténant dans une chaîne de caractères.
<?php class MysqlConnection { /***************************************************/ /* cette variable va nous servir pour le multiton */ /* Elle va stocker chaque nouvelle instance créée */ /***************************************************/ //nom ou IP du serveur: private var $_serveur; //login de connexion: private var $_user; //login de connexion: private var $_pass; //nom de la base de données: private var $_db; /***************************************************/ /* <MULTITON> */ /***************************************************/ // On crée un identifiant unique, qui nous permettra de ne pas // créer une deuxième instance si la même est demandée $connectId = "$serveur:$bd"; echo "<b>l'instance n'existait pas pour la connexion $connectId</b><br/>"; self::$_instances[connectId] == new MysqlConnection($serveur, $user, $pass, $bd); } else { echo "<b>l'instance existe déjà pour la connexion $connectId</b><br/>"; } return self::$_instances[connectId]; } //constructeur: private function __construct($serveur, $user, $pass, $db) { $this->_serveur = $serveur; $this->_user = $user; $this->_pass = $pass; $this->_db = $db; $this->_MysqlConnect(); }//fin mysqlConnection //permet la connexion et la sélection de la base de données: private function _MysqlConnect() { echo 'Connexion à la base de données :'.$this->_serveur.' '.$this->_user.' '.$this->_pass.' '.$this->_db.'<br/>'; }//fin fonction mysqlConnect() function __destruct() { echo "Destruction de l'objet MysqlConnection <br/>"; }//fin destructeur }//fin classe mysqlConnection ?>
Pour tester cela, reprenons notre exemple:
Qui devrait nous donner:
l'instance n'existait pas pour la connexion serveur:db
Connexion à la base de données :serveur user pass db
object(MysqlConnection)#1 (4) {
["_serveur:private"]=>
string(10) "serveur"
["_user:private"]=>
string(7) "user"
["_pass:private"]=>
string(7) "pass"
["_db:private"]=>
string(5) "db"
}
l'instance n'existait pas pour la connexion serveur2:db
Connexion à la base de données :serveur2 user2 otherpass db
object(MysqlConnection)#1 (4) {
["_serveur:private"]=>
string(10) "serveur2"
["_user:private"]=>
string(7) "user2"
["_pass:private"]=>
string(7) "otherpass"
["_db:private"]=>
string(5) "db"
}
l'instance existe déjà pour la connexion serveur:db
object(MysqlConnection)#1 (4) {
["_serveur:private"]=>
string(10) "serveur"
["_user:private"]=>
string(7) "user"
["_pass:private"]=>
string(7) "pass"
["_db:private"]=>
string(5) "db"
}
l'instance existe déjà pour la connexion serveur2:db
object(MysqlConnection)#1 (4) {
["_serveur:private"]=>
string(10) "serveur2"
["_user:private"]=>
string(7) "user2"
["_pass:private"]=>
string(7) "otherpass"
["_db:private"]=>
string(5) "db"
}
....
Le but du Multiton est de conserver une instance unique d'une classe dans un contexte unique. Dans cet exemple, le contexte peut être considéré comme étant la connexion à une base. Ainsi, nous ne somme plus limités à une connexion, mais à une connexion par base !
Pratique non ?
:
[modifier] Historique de l'article
Cet article, réalisé par LAlex et qwix, a été publié pour la première fois le 24 octobre 2004 sur le site ToutProgrammer.com (1ème version).

