Gérer la connexion à la base de données avec le patron de conception Singleton

En programmation orientée objet, le motif Singleton permet d’être sûr qu’une classe ne sera instanciée qu’une seule fois. À chaque fois que l’on fera appel à cette classe, nous récupérerons l’unique instance de cette dernière.

Dans le cadre d’une connexion à la base de données, ce motif est très pratique, car nous sommes certains de récupérer la connexion courante et de ne pas en créer une à chaque fois.

Principe du motif Singleton

Le Singleton est l’un des patrons de conception que l’on apprend en général au début de son apprentissage de la programmation orientée objet, car il est très facile à comprendre.

Le principe est le suivant :

  • Une classe Singleton aura une instance unique quoiqu’il arrive.
  • Quand on appellera cette classe, nous recevrons à chaque fois cette unique instance.
  • Il n’est pas possible d’instancier manuellement ni de cloner une classe Singleton.

En programmation cela se traduit par :

  • Le constructeur est privé afin que l’on ne puisse pas instancier l’objet.
  • Le cloneur est privé afin que l’on ne puisse pas cloner l’objet.
  • L’unique instance de la classe est contenue dans la classe elle même.
  • Quand on demande à récupérer l’instance de la classe, elle est créée si elle n’existe pas, puis elle est renvoyée.

Exemple en PHP :

class Singleton {
 
 /**
   * @var Singleton
   * @access private
   * @static
   */
   private static $instance = NULL;
 
  /**
   * Le constructeur est privé pour qu'on ne puisse
   * pas instancier la classe depuis l'extérieur
   *
   * @access private
   * @param void
   * @return void
   */
   private function __construct()
   {
     // constructeur privé
   }
 
  /**
   * Le cloneur est privé pour qu'on ne puisse pas cloner l'objet
   *
   * @access private
   * @param void
   * @return void
   */
   private function __clone()
   {
     // cloneur privé
   }
 
  /**
   * Méthode qui crée l'unique instance de la classe
   * si elle n'existe pas puis la renvoie.
   *
   * @access public
   * @static
   * @param void
   * @return Singleton object
   */
   public static function getInstance()
   {
     if(self::$instance === NULL) {
       self::$instance = new Singleton();
     }
 
     return self::$instance;
   }
 
  /**
   * Méthode qui détruit l'unique instance de la classe.
   *
   * @access public
   * @static
   * @param void
   * @return void
   */
   public static function killInstance()
   {
     self::$instance = NULL;
   }
}

Voici comment on utilisera cette classe :

// on crée ou récupère l'objet Singleton
$singleton = Singleton::getInstance();
 
// $singleton est un objet représentant l'unique instance de la classe Singleton.
 
// on détruit l'objet Singleton
$singleton = Singleton::killInstance();

On imagine bien le côté pratique pour la gestion d’une connexion à une base de données. Quand on demande l’instance de l’objet on vérifie si l’objet mysqli ou PDO existe, si ce n’est pas le cas on le crée en se connectant à la base de données et enfin on renvoi l’objet (et donc l’unique connexion). Si on souhaite couper la connexion explicitement il suffit d’appeler la méthode killInstance().

Néanmoins il existe une limite avec ce système : si le site utilise plusieurs bases de données et que l’on souhaite les utiliser au cours d’un même script on ne pourra pas le faire. Pour pallier à ce problème, il existe une variante du Singleton qu’on appelle le Multiton.

Variante : le Multiton

On ne peut pas dire que le Multiton soit un patron de conception différent du Singleton, c’est en fait une variante qui permet tout simplement de travailler avec plusieurs instances uniques d’un objet Singleton.

Dans le cadre d’une utilisation avec une base de données cela permet de gérer plusieurs connexions tout en étant sûr que chaque connexion sera elle même unique quoiqu’il arrive.

Ce qui donne en PHP :

class Multiton {
 
 /**
   * @var Multiton
   * @access private
   * @static
   */
   private static $instance = Array();
 
  /**
   * Le constructeur est privé pour qu'on ne puisse
   * pas instancier la classe depuis l'extérieur
   *
   * @access private
   * @param void
   * @return void
   */
   private function __construct()
   {
     // constructeur privé
   }
 
  /**
   * Le cloneur est privé pour qu'on ne puisse pas cloner l'objet
   *
   * @access private
   * @param void
   * @return void
   */
   private function __clone()
   {
     // cloneur privé
   }
 
  /**
   * Méthode qui crée une instance unique si elle n'existe pas puis la renvoie.
   *
   * @access public
   * @static
   * @param string $id
   * @return Multiton object
   */
   public static function getInstance($id)
   {
     if(!isset(self::$instance[$id])) {
       self::$instance[$id] = new Multiton();
     }
 
     return self::$instance[$id];
   }
 
  /**
   * Méthode qui détruit l'instance.
   *
   * @access public
   * @static
   * @param string $id
   * @return void
   */
   public static function killInstance($id)
   {
     unset(self::$instance[$id]);
   }
}

Voici comment on utilisera cette classe :

// on crée ou récupère une instance
$un_id = Singleton::getInstance('un_id');
 
// $un_id est un objet representant une instance unique de la classe.
 
// on crée ou récupère une autre instance
$un_autre_id = Singleton::getInstance('un_autre_id');
 
// $un_autre_id est un objet representant une autre instance unique de la classe.
 
// on détruit une des instances
$singleton = Singleton::killInstance('un_id');

Cas pratique pour la connexion à une base de données

Nous allons maintenant voir comment implémenter ce motif avec le cas pratique de la gestion de la connexion à une ou plusieurs bases de données. Pour l’exemple j’utilise l’API PDO pour gérer ma connexion.

/**
 * Db class
 */
class Db {
 
 /**
   * @var Db
   * @access private
   * @static
   */
 private static $instance = Array();
 
  /**
   * Le constructeur est privé pour qu'on ne puisse
   * pas instancier la classe depuis l'extérieur
   *
   * @access private
   * @param void
   * @return void
   */
   private function __construct()
   {
     // constructeur privé
   }
 
  /**
   * Le cloneur est privé pour qu'on ne puisse pas cloner l'objet
   *
   * @access private
   * @param void
   * @return void
   */
   private function __clone()
   {
     // cloneur privé
   }
 
  /**
   * Méthode qui crée une instance unique si elle n'existe pas puis la renvoie.
   *
   * @access public
   * @static
   * @param string $id
   * @return Db object
   */
 public static function getInstance($id = 'default')
 {
  if (!isset(self::$instance[$id]))
  {
   $config = Config::getInstance();
   $db_config = $config->get('database', $id);
 
   if ($db_config === FALSE) {
 
    throw new Exception(__CLASS__.': Database configuration no found: "'.$id.'".');
 
   } else {
 
    $dsn = $databases[$id]['driver'].':host='.$databases[$id]['host'].';dbname='.$databases[$id]['name'];
    $user = $databases[$id]['user'];
    $password = $databases[$id]['password'];
 
    try {
      self::$instance[$id] = new PDO($dsn, $user, $password, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'"));
      self::$instance[$id]->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
     } catch (PDOException $e) {
      die('Erreur de connexion à la base de données.
Vous pouvez essayer de rafraîchir la page.
'.$e->getMessage());
     }
 
   }
   }
 
  return self::$instance[$id];
 }
 
  /**
   * Méthode qui détruit l'instance.
   *
   * @access public
   * @static
   * @param string $id
   * @return void
   */
   public static function killInstance($id = 'default')
   {
     unset(self::$instance[$id]);
   }
}

Et voici comment on l’utilise :

// on se connecte ou on récupère la connexion à la base de données "default"
$db = Db::getInstance();
 
// on se connecte ou on récupère la connexion à la base de données "chat"
$db_chat = Db::getInstance('chat');
 
// on ferme explicitement la connexion à la base de données "default"
Db::killInstance();
 
// on ferme explicitement la connexion à la base de données "chat"
Db::killInstance('chat');

Vous aurez remarqué que je fais appel à un objet $config pour récupérer la configuration de la base de données. Cet objet est un simple Singleton qui me permet d’accéder à la configuration de mon site depuis mes scripts sans utiliser de constantes ou de variables globales.

Pour voir si vous avez bien compris le principe vous pourriez essayer d’implémenter vous même la classe Singleton Config.

Got something to say? Go for it!

This site uses KeywordLuv. Enter YourName@YourKeywords in the Name field to take advantage.