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