Singleton de connexion PDO

Download Report

Transcript Singleton de connexion PDO

Bases de données
Objet singleton pour la connexion
Jérôme CUTRONA
[email protected]
12:43:14
Programmation Web 2013-2014
1
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 chaque page PHP
 Développement objet d'un site avec une BD :



Connexions à divers endroits dans les méthodes
Requête(s) à divers endroits dans les méthodes
Déconnexion en fin de page PHP
 Quand doit-on se connecter, se déconnecter ?
12:43:14
Programmation Web 2013-2014
2
Solution : Singleton
 Singleton : objet à instance unique
 Réalisation :

1 attribut statique
 Singleton

1 point d'accès
 méthode statique

limiter l'accès au constructeur
 privé / protégé

12:43:14
interdire le clonage
Programmation Web 2013-2014
3
Solution : Singleton
 Adaptation à la connexion BD PDO : myPDO

1 attribut statique
 mypdo

1 attribut statique
 Ressource BD = objet PDO

1 point d'accès
 Méthode statique get()

constructeur privé
 établit la connexion à la BD

destructeur
 termine la connexion à la BD

mise hors service de la méthode __clone
 throw new Exception("…") ;
12:43:14
Programmation Web 2013-2014
4
Durée de vie de l'objet myPDO
 Nature de l'objet

membre statique d'une classe : variable globale
 Construction

créé au premier appel de myPDO::get()
 Destruction


variable globale : durée de vie = le script
détruit en fin de script
 Bilan :



12:43:14
connexion lors de la première requête
déconnexion à la fin du script
fonctionnement propre, transparent pour l'utilisateur
Programmation Web 2013-2014
5
Une implémentation
<?php
/// Classe permettant de faire une connexion unique
et automatique à la BD
final class myPDO
{
/// Singleton
private static $mypdo = null ;
/// Message de debogage
private static $debug = true ;
/// Data Source Name
private static $dsn
= null ;
/// Utilisateur
private static $user = null ;
/// Mot de passe
private static $pass = null ;
/// Connexion à la base
private
$pdo
= null ;
12:43:14
Programmation Web 2013-2014
6
Une implémentation
/// Constructeur privé
private function __construct() {
self::msg("Demande construction PDO...") ;
if (
is_null(self::$dsn)
|| is_null(self::$user)
|| is_null(self::$pass))
throw new Exception("Construction impossible
: les paramètres de connexion sont absents") ;
// Etablir la connexion
$this->pdo = new PDO(self::$dsn, self::$user,
self::$pass) ;
// Mise en place du mode "Exception" pour les
erreurs PDO
$this->pdo->setAttribute(PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION) ;
self::msg("Construction PDO terminée") ;
}
12:43:14
Programmation Web 2013-2014
7
Une implémentation
/// Destructeur
public function __destruct() {
self::msg("Demande de destruction PDO...") ;
// S'il y a une connexion établie...
if (!is_null($this->pdo))
{
// ... il faut se deconnecter
self::msg("Demande de déconnexion...") ;
$this->pdo
= null ;
self::$mypdo = null ;
self::msg("Deconnexion effectuée") ;
}
self::msg("Destruction PDO terminée") ;
}
12:43:14
Programmation Web 2013-2014
8
Une implémentation
/// Récupérer le singleton
public static function donneInstance() {
self::msg("Recherche de l'instance...") ;
// Une instance est-elle disponible ?
if (!isset(self::$mypdo))
self::$mypdo = new myPDO() ;
self::msg("Instance trouvée") ;
return self::$mypdo->pdo ;
}
/// Fixer les paramètres de connexion
public static function parametres($_dsn,
$_user, $_pass) {
self::$dsn = $_dsn ;
self::$user = $_user ;
self::$pass = $_pass ;
}
12:43:14
Programmation Web 2013-2014
9
Une implémentation
/// Interdit le clonage du singleton
public function __clone() {
throw new Exception("Clonage de
".__CLASS__." interdit !") ;
}
12:43:14
Programmation Web 2013-2014
10
Utilisation
require_once "connexion.pdo.template.class.php" ;
// Paramétrage du singleton
myPDO::parametres('oci:dbname=bd11',
'scott', 'tiger') ;
// Connexion automatique
$pdostat = myPDO::donneInstance()->query(
"SELECT * FROM Images") ;
while (($ligne = $pdostat->fetch())
!== false)
{
echo $ligne['id']."<br>\n" ;
}
// Déconnexion automatique en fin de script
Simple, non ?
12:43:14
Programmation Web 2013-2014
11
Raffinement possible : Débogage
 Possibilité d'afficher des messages informatifs
pour se rendre compte de ce qu'il se passe et
dépister les erreurs :


connexion…
requête…
 Cadre Web :

comment afficher des informations qui ne perturbent
pas l'affichage de la page
commentaires HTML <!-- … -->

doit pouvoir être désactivé  contenus non HTML

 Implémentation :

12:43:14
attribut booléen statique et méthodes statiques
Programmation Web 2013-2014
12
Raffinement possible : Débogage
 Problème majeur, les méthodes de requête,
préparation, etc sont des méthodes de l'objet
PDO et non de myPDO
 Solution basique


la méthode get retourne l'objet myPDO et non PDO
l'objet myPDO doit implémenter toutes les méthodes
de PDO qui consistent à lancer celles de PDO
 Solution "avancée"


12:43:14
la méthode get() retourne l'objet myPDO et non PDO
myPDO doit implémenter __call
Programmation Web 2013-2014
13
Une implémentation v2
/// Récupérer le singleton
public static function donneInstance() {
self::msg("Recherche de l'instance...") ;
// Une instance est-elle disponible ?
if (!isset(self::$mypdo))
self::$mypdo = new myPDO() ;
self::msg("Instance trouvée") ;
// return self::$mypdo->pdo ;
return self::$mypdo ;
}
12:43:14
Programmation Web 2013-2014
14
Une implémentation v2
/// Surcharge de toutes les méthodes indisponibles de myPDO pour
pouvoir appeler celles de PDO
public function __call($methodName/** Nom de
$methodArguments /** Tableau des
la méthode */,
paramètres */
) {
// La méthode appelée fait-elle partie de la classe PDO
if (!method_exists($this->pdo, $methodName))
throw new Exception("PDO::$methodName n'exis
te pas") ;
// Message de debogage
self::msg("PDO::$methodName (".implode($methodAr
guments, ", ").")") ;
// Appel de la méthode avec l'objet PDO
$result = call_user_func_array(
array($this->pdo, $methodName),
$methodArguments) ;
return $result ;
}
12:43:14
Programmation Web 2013-2014
15
Une implémentation v2
/// Affichage de messages de contrôle
public static function msg($m /** Le message */) {
if (self::$debug)
Traceur::trace($m) ;
}
/// Mise en marche du debogage
public static function debug_on() {
self::$debug = true ;
}
/// Arrêt du debogage
public static function debug_off() {
self::$debug = false ;
}
12:43:14
Programmation Web 2013-2014
16
Une implémentation v2 - 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 !") ;
}
12:43:14
Programmation Web 2013-2014
17
Une implémentation v2 - 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 ;
}
12:43:14
Programmation Web 2013-2014
18
Une implémentation v2 - 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 ;
} }
12:43:14
Programmation Web 2013-2014
19
Une implémentation v2 - 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 ;
} }
12:43:14
Programmation Web 2013-2014
20
Une implémentation v2 - Débogage
 Utilisation de la classe Traceur dans la classe
myPDO : affichage des traces à la destruction
/// Destructeur de myPDO
public function __destruct() {
self::msg("Demande de destruction PDO...") ;
// S'il y a une connexion établie...
if (!is_null($this->pdo))
{
// ... il faut se deconnecter
self::msg("Demande de déconnexion...") ;
$this->pdo
= null ;
self::$mypdo = null ;
self::msg("Deconnexion effectuée") ;
}
self::msg("Destruction PDO terminée") ;
echo Traceur::affiche() ;
}
12:43:14
Programmation Web 2013-2014
21
Raffinement possible : Débogage (suite)
 La solution est partielle :



Les messages d'information concernent uniquement
les méthodes de PDO
Une requête préparée retourne un objet
PDOStatement qui effectuera les méthodes
bindValue, execute, fetch, …
Comment tracer TOUTES les actions effectuées sur
la base de données ?
 Implémenter un objet myPDOStatement
12:43:14
Programmation Web 2013-2014
22
Une implémentation V3
/// Encapsulation de PDOStatement
final class myPDOStatement {
/// L'objet PDOStatement
private $pdoStatement ;
/// Constructeur
public function __construct($_pdoStatement
/** L'objet PDOStatement */) {
myPDO::msg("Construction PDOStatement") ;
$this->pdoStatement = $_pdoStatement ;
}
/// Destructeur
public function __destruct() {
myPDO::msg("Destruction PDOStatement") ;
$this->pdoStatement = null ;
}
12:43:14
Programmation Web 2013-2014
23
Une implémentation V3
/// Surcharge de toutes les méthodes indisponibles de
myPDOStatement pour pouvoir appeler celles de PDOStatement
public function __call($methodName/** Nom
$methodArguments /** Tableau
de la méthode */,
) {
des paramètres */
// La méthode appelée fait-elle partie de la classe PDOStatement
if (!method_exists($this->pdoStatement, $methodName))
throw new Exception("PDOStatement::$methodName n'e
xiste pas") ;
// Message de debogage
myPDO::msg("PDOStatement::".$methodName.
" (".var_export($methodArguments, true).")") ;
// Appel de la méthode avec l'objet PDOStatement
return call_user_func_array(
array($this->pdoStatement, $methodName),
$methodArguments) ;
}
12:43:14
Programmation Web 2013-2014
24
Une implémentation V3.1
 Utilisation
de myPDOStatement
// Selon
le nom de la méthode au lieu de
switch ($methodName)
{
PDOStatement
?
// Cas 'prepare' ou 'query' => fetchNamed
public function __call($methodName/** Nom de
case "prepare" :
/** Tableau des
case$methodArguments
"query" :
la méthode */,
paramètres */
) {
// La méthode appelée fait-elle partie de la classe PDO
$result->setFetchMode(PDO::FETCH_NAMED) ;
if (!method_exists($this->pdo,
$methodName))
// Retourne un objet myPDOStatement
throw return
new Exception("PDO::$methodName
new myPDOStatement($result) ; n'exis
te pas") ;// Dans tous les autres cas
// Message
de debogage
default
:
// Retourne le résultat
self::msg("PDO::$methodName
(".implode($methodAr
$result
;
guments, ", return
").")")
;
} de la méthode avec l'objet PDO
// Appel
$result = call_user_func_array(
array($this->pdo, $methodName),
$methodArguments) ;
return $result ;
}
12:43:14
Programmation Web 2013-2014
25
Aide au débogage, suite (et fin ?)
error_reporting(E_ALL) ;
/** Mise en place d'une capture des
exceptions non attrapées */
function exceptionHandler($exception
/** L'Exception non attrapée */) {
echo "<pre>\n" ;
echo $exception->getMessage()."\n" ;
echo "Trace d'execution :\n" ;
echo $exception->getTraceAsString() ;
echo "</pre>\n" ;
}
set_exception_handler('exceptionHandler') ;
12:43:14
Programmation Web 2013-2014
26