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.