Mise à jour le 17/11/2021
Le design pattern Singleton et l'implémentation des services

Le design pattern Singleton et l'implémentation des services

Avec les frameworks modernes, on passe notre temps à manipuler des Singletons car tout ce qu'on nomme service en est en fait un.

Un singleton n'est rien d'autre qu'une classe que l'on ne peut instancier qu'une et une seule fois par exécution. Si l'on désire créer une nouvelle instance et qu'il en existe déjà une, c'est celle-ci qui sera utilisée, magique donc !

1. La classe Singleton

1.1 Schema DML


1.2 Exemple de la classe Singleton

Voici une façon d'écrire une classe qui implémente le design pattern Singleton :

<?php
class Singleton
{
    private static $mySelf = null;

    private function __construct(){} // Empêche toute création d'instance.
    private function __clone(){} // Empêche toute copie d'instance.

    public static function getInstance()
    {
        if (self::$mySelf === null) {
            self::$mySelf = new Singleton();
        }
        
        return self::$mySelf;
    }
}

1.3 Exemple d'utilisation (attention, ça se complique !)

A l'usage, lorsque l'on cherche à créer deux instances, voici ce qu'il se passe:

<?php
$firstSingleton = Singleton::getInstance();
$secondSingleton = Singleton::getInstance();

1.4 Pourquoi l'usage de self et du static ?

Puisque que la classe n'est pas instanciée au moment de l'appel à getInstance, $this n'existe pas encore. Et comme $this n'existe pas, le seul moyen de faire appel à l'attribut mySelf est de passer par du static ::.

2. La classe SingletonMaker

2.1 Introduction

C'est tout de même très étrange comme façon de créer une instance, c'est un peu trop fantastique ce qu'il se passe, non ? On dirait que l'on hack la façon dont on doit utiliser les classes, notamment, il me semble vraiment étrange pour une classe de s'autocréer.
On pourrait éventuellement passer par un créateur de Singleton pour gérer tout ceci de façon plus claire, je vous propose donc de créer le SingletonMaker.

2.2 Exemple de la classe SingletonMaker

A noter que la classe aurait pu s'appeler SingletonFactory, mais elle s'appelle SingletonMaker pour éviter l'ambiguité avec le design pattern Factory, dont le code ci dessous n'est pas représentatif.

class Singleton
{

}

class SingletonMaker
{
    private static $singleton = null;

    public static function getSingleton()
    {
        if (self::$singleton === null) {
            self::$singleton = new Singleton();
        }

        return self::$singleton;
    }
}

C'est déjà un peu plus normal ce qu'il se passe ici, on passe par une classe qui nous retourne le Singleton en le créant si besoin.

2.3 Exemple d'utilisation

Rien de très différent de la fois d'avant.

<?php
$firstSingleton = SingletonMaker::getSingleton();
$secondSingleton = SingletonMaker::getSingleton();

3. Notre gestionnaire de services - la classe ServiceManager

Quelque part, nous venons de créer un gestionnaire de services, si on renomme un peu les choses, voici ce qu'on pourrait obtenir :

3.1 Proposition d'implémentation du ServiceManager

En renommant Singleton en MyFirstService, en créant une seconde classe MySecondService et en renommant le SingletonMaker par ServiceManager, on pourrait écrire ceci :

class MyFirstService
{

}

class MySecondService
{

}

class ServiceManager
{
    private static $services = [];

    public static function get(string $serviceClassName)
    {
        if (!isset(self::$services[$serviceClassName])) {
            self::$services[$serviceClassName] = new $serviceClassName();
        }

        return self::$services[serviceClassName];
    }
}


A l'usage, voici comment on récupérerait nos services :

$myFirstService = ServiceManager::get('MyFirstService');
$mySecondService = ServiceManager::get('MySecondService');

3.2 Appelation de services plus abstraites

Et eventuellement faire une appelation de services différente que l'on recalculerait à la volée.

Ex : 'myFirstService' comme nom du service et new ucfirst("myFirstService")() pour créer l'instance (pour rappel le ucfirst met en majuscule la première lettre d'une chaine de caractères).

class MyFirstService
{

}

class ServiceManager
{
    private static $services = [];

    public static function get(string $serviceName)
    {
        $serviceClassName = ucfirst($serviceName);

        if (!isset(self::$services[$serviceName])) {
            self::$services[$serviceName] = new $serviceClassName();
        }

        return self::$services[$serviceName];
    }
}


A l'usage, on pourrait l'appeler comme ceci :

$myService = ServiceManager::get('myFirstService');

3.2.1 Apparté

Derrière, on peut appeler ça un containeur de services, puis déclarer dans un fichier de configuration yml, xml ou ce que l'on souhaite le nom des services publics (haha).