Une fois élagué de ce qui ne nous intéresse pas dans le cas présent, le code de cette classe ressemble à ceci ;
<?php
class mysql
{
protected $link = null;
protected $server = '';
protected $user = '';
protected $password = '';
protected static $instance = null;
public function __contruct()
{
$this->server = _DB_SERVER_;
$this->user = _DB_USER_;
$this->password = _DB_PASSWD_;
$this->connect();
}
public function __destruct()
{
if ($this->link)
{
@mysql_close($this->link);
}
}
public function connect()
{
if (!($this->link = @mysql_connect($this->server, $this->user, $this->password)))
{
die('Link to database cannot be established.');
}
}
public function getInstance()
{
if (self::$instance === null)
{
self::$instance = new self();
}
return self::$instance;
}
}
?>
Voyons maintenant le code qui va mettre en lumière le problème :
<?php
$singleton = mysql::getInstance();
class foo
{
function bar()
{
$instance = new mysql();
$instance->query(...);
}
}
$foo = new foo();
$foo->bar();
$singleton->query(...);
?>
A votre avis, quel est le problème qui peut se cacher dans ce code, pour le moins trivial à première vue ?
Une idée ?
Comme c'est bientôt Noël, je vais vous le dire.
Lorsque la variable $instance
est créée dans la méthode foo::bar()
, sa portée est celle de la méthode.
Cela veut dire que lorsque la méthode est terminée, PHP fait appel au destructeur mysql::__destruct()
pour faire le ménage.
Or, le destructeur ferme la connexion au serveur de base de données.
Par conséquent, lorsque la méthode mysql::query()
est appelée par $singleton
, la connexion avec le serveur n'existe plus, et $singleton
se trouve dans l'impossibilité de répondre.
Les origines de ce problème sont doubles, mais elles ont pour point commun d'être de la faute du concepteur de la classe mysql
, je ne pourrais donc pas pour cette fois incriminer PHP.
Tout d'abord, le constructeur d'un singleton ne doit pas être public, car s'il l'est, comme nous venons de le voir, son intégrité ne peut être garantie au cours du temps.
Ensuite, mysql_connect()
met en cache les connexions qu'elle ouvre avec un serveur de base de données.
Ainsi, pour un serveur, un utilisateur et un mot de passe, mysql_connect()
n'ouvre par défaut pour toute la durée du script qu'une seule connexion.
En conséquence, la méthode mysql::connect()
appelée par mysql::__construct()
pour créer $instance
renvoit la même ressource de connexion que celle créé lors de la création de $singleton
.
A partir de ce moment, nous avons donc un singleton et une instance qui se partage une même donnée en mémoire, d'ou notre problème.
La solution ? Forcer mysql_connect()
a créer une nouvelle connexion avec le serveur de base de données en lui passant true
comme quatrième argument.
Comme nous venons de le voir, la conception d'un singleton n'est pas forcément si trivial que cela, et il faut avoir une bonne connaissance du fonctionnement du modèle objet de PHP et surtout comprendre ce que l'on fait pour le faire correctement.
Maintenant, j'espère que les concepteurs de la classe prise en exemple me lisent, se reconnaitront et corrigeront leurs erreurs.
Mais... je ne sais pourquoi, j'ai comme un doute.
5 réactions
1 De Mickaël - 23/12/2009, 20:43
"Closed as this is not a bug"
^_^
2 De metagoto - 26/12/2009, 02:37
Si on accepte (intellectuellement parlant) de recourir à un singleton pour gérer la connexion à la base de données, alors il me parait ridicule de vouloir utiliser mysql_connect() avec true en 4iem argument.
Par chance, j'ai comme l'impression que la singletonite aigüe qui a frappé la communauté php depuis 2005 est en train de s'estomper... C'est pas trop tôt!
3 De mageekguy - 28/12/2009, 11:37
@metagoto:
Absolument d'accord avec toi, l'utilisation du quatrième argument n'est effectivement nécéssaire que si on veut absolument garder le constructeur public, ce qui est complétement paradoxal dans le cas du singleton.
Ce n'est pas pour rien que le titre du billet contient "ce qu'il ne faut pas faire"...
4 De Julien Breux - 07/04/2010, 15:26
Je suis super d'accord, je pense que si on appel ça des motifs de conception c'est qu'il y'a une problématique bien précise... et combien de Multi-Singleton on voit (ben ouai, on commence par un singleton (parce que ça fait joliment pro) et on finit par un fatroygleton-multiple :p)
J'aime beaucoup cette petite rubrique :p
5 De bouks - 12/03/2013, 23:57
Il est à mon avis bon de préciser que fermer la connexion n'a à priori aucun intérêt car php la ferme tout seul à la fin du script, et ce depuis pas mal de temps déjà.
Je ne sais plus sur quelle page mais c'est indiqué dans la doc php.