12nov 2007
Un singleton en PHP
11:28 - Par Thierry Geindre - PHP - un commentaire
Nous parlerons ici de développement orienté objet avec PHP. En effet, un singleton est ce que l'on appel un design pattern (comprenez : motif de conception) qui n'est applicable qu'à des classes et objets. Nous verrons ce qu'est exactement un singleton puis nous verrons comment procéder en PHP5 pour le mettre en place. Enfin, nous étudierons comment procéder en PHP4, version du langage pour laquelle les choses sont un peu différentes.
Définition et intérêts du singleton
Un singleton est un motif de conception pour une classe. Il permet de limiter l'instanciation de la classe à un seul et unique objet. Il ne pourra donc pas être créé plus d'une seule instance de la classe en question.
Ce genre de classe pourra être utilisée, par exemple, pour les accès à une base de données. Elle permettra d'empêcher toute nouvelle connexion inutile au SGBD. D'autre part, les singleton peuvent aussi régler les problèmes de visibillité de certaines variables. En effet il n'est plus nécessaire de conserver et de transmettre un référence vers l'instance. Il est toujours possible via une méthode statique de récupérer la dernière instance créée, ou, s'il n'y a aucune instance créée, d'en créer une nouvelle.
Singleton en PHP5
PHP5 fournit tout les éléments dont nous aurons besoin pour la mise en œuvre d'un singleton. Nous utiliserons, entre autres, un attribut statique et la méthode __clone() qui permet de détecter la création d'une seconde (et plus) instance.
<?php class Singleton { // instance de la classe private static $instance; // On utilise un constructeur privé pour empêcher // la création d'instance directe private function __construct() { // Code à la construction... } // La méthode singleton // permet de récupérer/créer une instance public static function singleton() { if (!isset(self::$instance)) { $className = __CLASS__; self::$instance = new $className; } return self::$instance; } // Interdit le clonage de l'objet // pas plus d'une instance n'est permise public function __clone() { trigger_error('Le clônage n\'est pas autorisé.', E_USER_ERROR); } } ?>
Une fois qu'une classe est déclarée ainsi, il sera possible d'appeler à n'importe quel moment dans votre code l'instance correspondante (ou créée au besoin) via ce code :
$mySingleton = Singleton::singleton();
Il suffira donc de faire un appel statique sur la méthode singleton() de votre classe.
Je vous rappel que la méthode utilisée ici pour la mise en œuvre du singleton ne fonctionne qu'à partir de PHP5. Elle ne fonctionnera donc pas en PHP4 et pour cause : les attributs statiques n'existent pas, pas plus que la méthode __clone() (elle serait considérée comme une méthode classique).
Singleton en PHP4
Comme je le disais, les attributs statiques n'existent pas en PHP4. N'y la méthode __clone() d'ailleurs. Cependant nous allons pouvoir utiliser le mot clé static de PHP4 disponible dans une fonction ou une méthode (qui n'est rien d'autre qu'une fonction membre, donc une fonction...). Ce mot clé permet de déclarer des variables locales (dans la fonction uniquement) de façon durable. Un exemple sera plus parlant :
<?php function getI() { static $i = 0; return ++$i; } echo getI(); // Affiche 1 echo getI(); // Affiche 2 // on conserve la valeur de $i d'un appel à l'autre ?>
Il nous sera donc possible, dans notre classe, de conserver une référence vers l'instance. Le singleton va donc pouvoir être créé! Et voici comment :
<?php class Singleton { // Constructeur, impossible de le passer privé en PHP4 function Singleton( $fromGetInstance = false ) { if ( !$fromGetInstance ) die( "Erreur, instanciation directe non permise." ); // suite des instructions } // Méthode d'obtention de l'instance function &getInstance() { static $instance = null; if ( $instance == null ) $instance = new Singleton( true ); return $instance; } } ?>
Comme vous pouvez le voir, nous avons rajouté un paramètre au constructeur afin d'interdire la construction directe d'objet, c'est à dire sans passer par la méthode getInstance(). Cependant, il est toujours possible de créer une instance librement en passant au constructeur la valeur true. En PHP4, il faut ruser puisqu'il est impossible de déclarer une méthode, constructeur ou pas, privée.
Il existe une solution qui consiste à profiter du fait que PHP ne permet pas la déclaration de deux constantes ou fonctions portant le même nom. Personnellement je ne suis pas très satisfait par cette méthode. En effet elle impose de définir inutilement une fonction ou une constante qui pourrait éventuellement interférer avec le reste du script. Je vous propose toutefois de découvrir cette méthode dans un autre article traitant des singletons en PHP.
Et je vous propose ici la méthode que j'ai mis au point permettant d'interdire tout passage directe par le constructeur pour l'instanciation de la classe. J'avoue que le système que je vais vous expliquer est quelque peu alambiqué. Mais, ne l'oublions pas, nous sommes en PHP et les solutions simples n'existent pas toujours compte tenu des outils mis à notre disposition.
On sait qu'il est possible de définir plusieurs variables locales statiques dans une fonction ou méthode. En plus de notre référence vers l'instance, on pourra donc créer une autre variable statique dans la méthode getInstance(). D'autre part, il est possible de passer des paramètres à la méthode getInstance(), comme à tout autre méthode. Sachant cela, nous allons pouvoir définir deux comportements différents pour la méthode getInstance() en fonction de la valeur d'un paramètre.
Rajoutons un paramètre à la méthode getInstance(). Il s'agit d'un booléen permettant de modifier le comportement de la méthode. On déclarera donc notre méthode ainsi :
<?php function &getInstance($testConstruct = false) { // [...] } ?>
Le paramètre est optionnel et par défaut a pour valeur false. L'utilisation classique pour obtenir l'instance ne change donc pas. Par conséquent, la méthode getInstance() conservera presque le même comportement lorsque ce paramètre a pour valeur false. En revanche, s'il a pour valeur true, la méthode doit être en mesure de répondre si elle à bien demandé une nouvelle instance au moyen d'un booléen qu'elle renverra. Ainsi, le constructeur pourra appeler cette méthode pour vérifier que l'instanciation est autorisée, sans quoi il renverra une erreur.
Ce n'est peut être pas encore clair. Mais voici quand même le résultat de toutes ces explications :
<?php class Singleton { // Constructeur, impossible de le passer privé en PHP4 function Singleton() { if ( !Singleton::getInstance(true) ) die( "Erreur, instanciation directe non permise." ); // suite des instructions } // Méthode d'obtention de l'instance function &getInstance($testConstruct = false) { static $instance = null, $testInit=false; if(!$testConstruct) { if ( $instance == null ) { $testInit = true; $instance = new Singleton( true ); $testInit = false; } return $instance; } $dc = $testInit; return $dc; } } ?>
Lorsque l'on appel la méthode getInstance() pour la première fois, celle-ci change la valeur du booléen $testInit pour true puis créée une nouvelle instance de la classe singleton. Le constructeur de la classe est alors appelé, il appel lui même la méthode getInstance() avec le paramètre $testConstruct à true. Cette méthode va alors lui retourner la valeur de $testInit qui aura pour valeur true signifiant que l'instanciation de la classe à bien été demandée par la méthode getInstace(). Une fois l'instance créée, la méthode getInstance(), initialement appelée, remet le booléen $testInit à false. Ainsi tout appel au constructeur aboutira forcément sur un échec.
Vous pouvez tester cette classe avec le code suivant :
<?php // Nouvelle instance $a =& Singleton::getInstance(); $a->i = 10; // Récupération de l'instance $b =& Singleton::getInstance(); // Affiche bien 10 echo $b->i; // Affiche une erreur $c = new Singleton(); ?>
Conclusion
Cet article peut aussi être considéré comme une bonne raison de passer à la version supérieur de php, PHP5. Malheureusement, beaucoup d'hébergeurs tardent à le faire, laissant un PHP4 vieillissant, alors que l'on parle déjà de la version 6!
C'est d'ailleurs la raison pour laquelle j'insiste si fortement sur les singletons en PHP4. Car s'il est simple de créer de véritable singleton en PHP5, en PHP4 c'est un peu plus compliqué et je pense que vous l'aurez compris en lisant cet article. A propos, le système que j'ai mis au point vous est proposé tel quel et sans aucune garantie. Si vous souhaitez l'utiliser, c'est à vos risques. Toutefois je serais curieux d'avoir vos avis sur ce code, n'hésitez donc pas à le commenter.
un commentaire
Super explication pour PHP5 (pas lu PHP4 !) sa ma permis de bien comprendre l'utilisation possible!
bonne continuation!
Fil des commentaires de ce billet