Charles-Édouard Coste

Faire simple, c'est compliqué. Mais c'est mon métier.

Vieil article à reprendre car incomplet. L'objectif est de présenter le patron de conception "Singleton" mais de nos jours, on privilégie l'utilisation de conteneurs de services plutôt que l'usage d'un singleton.

A bas les fonctions PHP ! Vivent les objets !

J'ai fait la connaissance des design patterns et plus particulièrement le Singleton lors de mon deuxième job d'été à l'INRA en 2006, alors que je n'étais qu'un petit étudiant de l'IUT.

A l'époque, on m'avait demandé de réaliser un système d'export de base de données MySQL en PHP. En ce temps là (et je crois que cela n'a pas beaucoup changé) la littérature sur le sujet nous proposait d'effectuer des requetes à grand coup de mysql_connect, de mysql_query et de mysql_fetch_array.

Or s'il y a bien une chose que l'on nous enseignait en cours, c'était de miser un maximum sur la généricité. Et il faut bien avouer que la dizaine de lignes nécessaire à chaque requête fait toujours mal aux yeux quand on lit le code. J'ai donc naturellement créé une classe d'accès aux données (mon premier contact avec une couche DAO) en y ajoutant une méthode publique pour chaque requête dont j'avais besoin (ce qui n'est pas bien, mais on y reviendra dans un prochain post)

Transactions, garbage collector et variables globales

Le soucis c'est que parfois, une transaction nécessite plusieurs requêtes. J'avais donc remanié ma classe pour qu'elle mette en attente celles-ci jusqu'à ce que je lui demande d'envoyer la sauce à l'aide d'un élégant $database->commit();

Le problème auquel je n'avais pas pensé, c'était "comment garder une référence sur cet objet quand on y accède d'un peu partout?".

Première solution : l'espoir

On peut espérer que si je fais $db = new Database(); alors il suffit de ne jamais réinitialiser $db et on pourra faire $db->commit(); n'importe où, n'importe quand. Ca, c'est quand on n'a pas suivi le cours sur la portée des variables.

Car si j'initalise $db dans une fonction A(), elle ne sera pas accessible depuis la fonction B().

Deuxième solution : les variables globales

Oui, c'est vrai... on peut définir $db à l'aide de l'instruction global $db = Database();

Si je fais ça dans la fonction A(), $db sera toujours définie dans la fonction B(). Ca, c'est la solution de ceux qui ont suivi le cours sur la portée des variables mais pas celui sur la sécurité.

Troisième solution : le passage de paramètre

Enfin une bonne solution! Fiable et efficace... je définie une fonction fn1($db) et une fonction fn2($db), j'initialise la variable $db à l'extérieur et je la passe à fn1 ou fn2 lorsque je les appelle.

$db = new Database();
fn1($db);
fn2($db);
$db->commit();

Une solution pas si mauvaise que ça, si l'on est sûr que fn1 ou fn2 auront besoin d'une requête. Si ce n'est pas le cas, on aura initialisé $db pour rien...

Quatrième et dernière solution: un Singleton

Voici un exemple:

class Database
{
    // instance de la classe
    private static $instance;

    // Un constructeur privé ; empêche la création directe d'objet
    private function __construct()
    {
        echo 'Je suis construit';
    }

    // La méthode singleton
    public static function singleton()
    {
        if (!isset(self::$instance)) {
            $c = __CLASS__;
            self::$instance = new $c;
        }
        return self::$instance;
    }
}

Il devient alors possible de faire appel à la même instance depuis n'importe où dans notre code:

fn1(); // dans fn1, on appelle Database::singleton()
fn2(); // dans fn2, on appelle Database::singleton()
$db = Database::singleton(); // ici aussi, on appelle Database::singleton()
$db->commit();

Attention. Cette façon de faire est démodée mais c'est ainsi que l'on utilise un singleton

Previous Post Next Post

Bienvenue sur mon blog !

J'y parle beaucoup technique de développement web, logiciels libres, et autres.

Ce site est entièrement consultable sans cookies et sans Javascript