Bases de données Objet singleton pour la connexion 01:08:10 Programmation Web 2012-2013
Problème posé Développement d'un site nécessitant une BD : Connexion en début de chaque page PHP Requête(s) Déconnexion en fin de programme PHP Développement objet d'un site avec une BD : Connexion dès que la BD est utilisée Requête(s) à divers endroits dans les diverses méthodes des diverses classes Déconnexion quand la BD n’est plus utile Quand doit-on se connecter, se déconnecter ? 01:08:10 Programmation Web 2012-2013
Solution : patron de conception Singleton Singleton : objet à instance unique Instance unique pour gérer une connexion BD unique Réalisation : 1 attribut statique Instance 1 point d'accès méthode statique limiter l'accès au constructeur privé / protégé interdire le clonage 01:08:10 Programmation Web 2012-2013
Singleton : Illustration du fonctionnement <?php class Singleton { private static $_instance = null ; public $attr = null ; public static function getInstance() { if (!self::$_instance instanceof self) { self::$_instance = new self() ; } return self::$_instance ; } } $i1 = Singleton::getInstance() ; $i2 = Singleton::getInstance() ; $i3 = Singleton::getInstance() ; $i1->attr = "ours" ; $i3->attr = 42 ; echo $i2->attr ; Classe Singleton getInstance() $_instance null $i1 Instance Singleton $attr $i2 $i3 "ours" 42 null 01:08:10 Programmation Web 2012-2013
Solution : patron de conception Singleton Adaptation à la connexion BD : Connexion 1 attribut statique instance 1 attribut de l’instance Ressource BD 1 point d'accès Méthode statique requete constructeur privé établit la connexion à la BD destructeur termine la connexion à la BD mise hors service de la méthode __clone private ET throw new Exception("…") ; 01:08:10 Programmation Web 2012-2013
Durée de vie de l'objet Connexion Nature de l'objet membre statique d'une classe ≈ variable globale Construction créé au premier appel de Connexion::requete() Destruction variable globale : durée de vie = le programme détruit automatiquement en fin de script Bilan : connexion automatique lors de la première requête déconnexion automatique à la fin du script facile, propre et transparent pour le développeur 01:08:10 Programmation Web 2012-2013
Raffinement possible : Débogage possibilité de collecter des messages informatifs pour se rendre compte de ce qu'il se passe et dépister les erreurs : connexion… requête… Cadre Web : ne pas perturber la page : commentaires HTML doit pouvoir être désactivé contenu des images Implémentation : attribut booléen statique et méthodes statiques utilisation d’un singleton de collecte d’informations 01:08:10 Programmation Web 2012-2013
Une implémentation class Connexion { /// Singleton permettant d'effectuer une connexion unique à la BD class Connexion { // Paramètres de connexion à la base de données private static $_host = null ; /// Hôte MySQL private static $_user = null ; /// Utilisateur MySQL private static $_passwd = null ; /// Mot de passe MySQL private static $_base = null ; /// Base de données de travail // Gestion de l'instance unique private static $_instance = null ; /// Objet Connexion // Traces d'exécution private static $_traces = true ; /// Débogage actif ou non // Attribut de l'objet : ressource de la connexion à la base de données private $_ressourceBD = null ; /// Connexion à la base 01:08:10 Programmation Web 2012-2013
Une implémentation /// Constructeur privé private function __construct() { self::msg("Construction de l'objet Connexion...") ; // Vérifier la présence des paramètres de connexion if ( is_null(self::$_host) || is_null(self::$_user) || is_null(self::$_passwd) || is_null(self::$_base)) throw new Exception("Connexion impossible : les paramètres de connexion sont absents") ; // Etablir la connexion if (! ($tmp = @mysql_connect(self::$_host, self::$_user, self::$_passwd))) throw new Exception("Connexion impossible à la base de données") ; $this->_ressourceBD = $tmp ; 01:08:10 Programmation Web 2012-2013
Une implémentation // Sélectionner la base de donnees if (! @mysql_select_db(self::$_base, $this->_ressourceBD)) throw new Exception("Sélection de la base impossible : " .mysql_error($this->_ressourceBD)) ; // Mise en place de la table de caractères mysql_query("SET CHARACTER SET 'utf8'", $this->_ressourceBD) ; self::msg("Construction terminée") ; } 01:08:10 Programmation Web 2012-2013
Une implémentation /// Destructeur public function __destruct() { self::msg("Demande de destruction de l'objet Connexion...") ; // S'il y a une connexion établie... if (!is_null($this->_ressourceBD)) { // ... il faut se déconnecter self::msg("Demande de déconnexion...") ; mysql_close($this->_ressourceBD) ; $this->_ressourceBD = null ; self::$_instance = null ; self::msg("Déconnexion effectuée") ; } self::msg("Destruction terminée") ; // Affichage des traces si le Débogage est actif if (self::$_traces) { echo Traceur::affiche() ; } 01:08:10 Programmation Web 2012-2013
Une implémentation /// Accesseur à l'instance qui sera créée si nécessaire private function donneInstance() { self::msg("Recherche de l'instance...") ; // Existe-t-il une instance de Connexion ? if (is_null(self::$_instance)) { // NON : il faut en créer une self::$_instance = new Connexion() ; } self::msg("Instance trouvée") ; return self::$_instance ; } /// Accesseur à la connexion à la base de données private static function donneRessourceBD() { self::msg("Recherche de la ressource BD...") ; return self::donneInstance()->_ressourceBD ; 01:08:10 Programmation Web 2012-2013
Une implémentation /// Fixer les paramètres de connexion public static function parametres($host, $user, $passwd, $base, $traces) { self::debogage($traces) ; self::msg( "Demande de positionnement des paramètres de connexion...") ; self::$_host = $host ; self::$_user = $user ; self::$_passwd = $passwd ; self::$_base = $base ; self::msg("Positionnement des paramètres de connexion terminé") ; } 01:08:10 Programmation Web 2012-2013
Une implémentation /// Effectuer une requête et retourner le résultat public static function requete($req) { self::msg("Demande d'exécution de la requête:\n$req") ; $res = @mysql_query($req, self::donneRessourceBD()) ; if ($res === false) { throw new Exception("Erreur pour la requête '{$req}' : " .mysql_error(self::donneRessourceBD())) ; } self::msg("Requête effectuée") ; return $res ; } 01:08:10 Programmation Web 2012-2013
Une implémentation public static function protectionChaine($chaine) { /// Protéger une chaîne de caractères avant de l'intégrer dans une requête public static function protectionChaine($chaine) { self::msg("Protection de la chaine '{$chaine}'") ; return mysql_real_escape_string($chaine, self::donneRessourceBD()) ; } /// Retouner le nombre d'enregistrements affectes public static function nombreLignesAffectees() { return mysql_affected_rows(self::donneRessourceBD()) ; /// Interdire le clonage private function __clone() { throw new Exception("Clonage de ".__CLASS__." interdit !") ; 01:08:10 Programmation Web 2012-2013
Une implémentation /// Collecte de messages de contrôle static function msg($message) { if (self::$_traces) Traceur::trace($message) ; } /// Mise en marche ou arrêt des messages de contrôle static function debogage($etat) { self::$_traces = (bool) $etat ; 01:08:10 Programmation Web 2012-2013
Une implémentation - Débogage /// Singleton permettant de collecter des messages informatifs class Traceur { // Gestion de l'instance unique private static $_instance = null ; /// Objet Traceur // Atttributs de l'objet private $_messages = array() ; /// Tableau des messages private $_temps = null ; /// Instant de création /// Constructeur privé private function __construct() { $this->_temps = microtime(true) ; } /// Interdire le clonage private function __clone() { throw new Exception("Clonage de ".__CLASS__." interdit !") ; 01:08:10 Programmation Web 2012-2013
Une implémentation - Débogage /// Accesseur à l'instance qui sera créée si nécessaire private static function donneInstance() { if (is_null(self::$_instance)) { self::$_instance = new self() ; } return self::$_instance ; } /// Méthode statique de collecte de messages public static function trace($msg) { $instance = self::donneInstance() ; $instance->messages[] = $instance->duree() . " secondes : " .$msg ; 01:08:10 Programmation Web 2012-2013
Une implémentation - Débogage /// Calcul du temps écoulé depuis la création du traceur private function duree() { return number_format(microtime(true) - $this->_temps, 4) ; } /// Méthode statique d'affichage des messages collectés public static function affiche($avant = "<!--", $apres = "-->") { $messages = self::donneInstance()->messages ; $traces = null ; if (count($messages)) { $traces .= "{$avant}\n" ; foreach ($messages as $m) { $traces .= "{$m}\n" ; } $traces .= "{$apres}\n" ; } return $traces ; } } 01:08:10 Programmation Web 2012-2013
Simple, non ? Utilisation require_once "connexion.mysql.template.class.php" ; Connexion::parametres('serveur', 'utilisateur', 'motdepasse', 'basededonees', true) ; // Exemple d'utilisation : $res = Connexion::requete(<<<SQL SELECT un_champ FROM une_table ORBER BY 1 SQL ) ; while ($ligne = mysql_fetch_assoc($res)) { /* ... */ $ligne['un_champ'] /* ... */ ; } Simple, non ? 01:08:10 Programmation Web 2012-2013