Besoin d'un avis sur singleton de connexion

Répondre


Cette question est un moyen d’empêcher des soumissions automatisées de formulaires par des robots.
Smileys
:D :) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen: =D> #-o =P~ :^o :non: :priere: 8-|
Voir plus de smileys
  Revue du sujet
 

  Étendre la vue Revue du sujet : Besoin d'un avis sur singleton de connexion

Re: Besoin d'un avis sur singleton de connexion

par xTG » 25 sept. 2012, 16:44

Hum ouais tu marques un point moogli...

Re: Besoin d'un avis sur singleton de connexion

par moogli » 25 sept. 2012, 16:01

Certains le font pour des classes "static" n'ayant pas vocation à être instanciées.
Il s'agit de classe dite utilitaires, regroupant des fonctions qui n'ont pas toujours de relantion (par exemple en java la classe Math contient des méthodes pour réaliser des calculs plus ou moins complexe).
Question subsidiaire, une méthode static à elle accès au constructeur private de sa classe ?
Oui, c'est d'ailleurs ce que j'ai fait dans mon exemple, et xTG aussi d'ailleur ?

C'est possible ça de mettre le constructeur en privé ? O_o
Je serai l'interpréteur que je balancerai une erreur moi...
heu wé sinon y'a pas de singleton qui tiens, et ceci quelque soit le langage. Le but c'est d'éviter d'avoir une instanciation de la classe en dehors d'elle même.

Le constructeur étant privé, tu ne peux pas faire un new truc() dans le code sinon erreur.
Par contre dans la classe elle même tu peux faire un new truc() (sachant qu'une méthode privée ne peux être utilisée que par elle même :) ).

Dans le style, pour php 5.4 j'ai trouvé un trait qui fait un singleton générique. par exemple http://phpmaster.com/using-traits-in-php-5-4/
Je vais reprendre ton code pour l'étudier à la loupe. Je comprends le général mais j'ai besoin de comprendre tous les détails maintenant !
je crois qu'il faudrait que tu regarde un cours sur le singleton :)

Mais la tu entre dans un cas très particulier vue que normalement le singleton c'est sur lui même.

La tu veux plutôt gérer un pool de connexion, ce qui te permettrai d'avoir des connexion à plusieurs sgbd, ou plusieurs / instance / schéma différent).

@+

Re: Besoin d'un avis sur singleton de connexion

par Mazarini » 25 sept. 2012, 15:54

...
C'est possible ça de mettre le constructeur en privé ? O_o
...
Certains le font pour des classes "static" n'ayant pas vocation à être instanciées.

Question subsidiaire, une méthode static à elle accès au constructeur private de sa classe ?

Re: Besoin d'un avis sur singleton de connexion

par JulieDev » 25 sept. 2012, 14:45

Merci beaucoup à tous !!!!!!
effectivement ma prochaine question ressemblait à : je ne comprends plus pourquoi $_id est public...
Je vais reprendre ton code pour l'étudier à la loupe. Je comprends le général mais j'ai besoin de comprendre tous les détails maintenant !

Re: Besoin d'un avis sur singleton de connexion

par xTG » 25 sept. 2012, 14:35

heu les gars y a quand même un constructeur privé donc $mysql1 = new singletonConnexionBD(); devrait retourner une erreur
Ah en effet je n'avais pas vu ce petit private...
C'est possible ça de mettre le constructeur en privé ? O_o
Je serai l'interpréteur que je balancerai une erreur moi...
Du coup sa class était entièrement inutilisable car aucune instanciation n'était possible.

Re: Besoin d'un avis sur singleton de connexion

par moogli » 25 sept. 2012, 14:27

salut,

heu les gars y a quand même un constructeur privé donc $mysql1 = new singletonConnexionBD(); devrait retourner une erreur


ton dernier code n'est pas bon il ne fonctionnera jamais vu que la classe n'est jamais instanciée (et que toute la chose est lancée dans le constructeur).
tu ne permet pas le passage de paramètre à la classe donc tu ne pourra jamais fournir les identifiants / mot de passe.

Ton premier est mieux, effectivement les setter sont inutile et j'ajouterais que le copier collé c'est la mort du développeur. en clair tu as des messages d'erreurs identique quelque soit ce que tu fait (le setter utilisé).

displayErrorAndExit() n'existe pas, c'est définie autre part je suppose ? (general_functions.php ?)

$_id devrait être privée et et non public.

vu que tu utilise php5 pourquoi ne pas utiliser PDO ?

catch (Exception $e) {
$e->getMessage();
}

Inutile car ne fait rien => affiche l'erreur, ou uilise ta fonction perso.
 $c = __CLASS__;
            self::$_instanceConnexion = new $c($hostname, $username, $userpwd, $dbname, $createBase, $configDir);
heu normalement tu connais le nom de ta classe hein, ça c'est bon pour de l'instanciation à la volée à partir d'une interface (pattern factory par exemple).
J'avais aussi retourné une instance de connexion et un objet de la classe car il y avait du code déjà mis en place ne demandant qu'une connexion et non un objet.
Après ça dépend si tu peux refaire l'appli complète ou pas :d

Perso je préfère avoir un fichier de conf, qui se charge dans la base (ficher XML ou texte) y a surement d'autre possibilité comme utiliser un objet qui gère les fichiers de se style et qui fournit les infos sous une forme ou une autre (un objet ou un tableau).


Les setter / getter sont normalement utilisé pour le "dialogue" de l'objet avec l'extérieur, souvent ce n'est pas utilisé en interne, donc effectivement setter / getter en private moyen bof.

au final je pense que ta classe peux ressembler à ceci :
<?php

//require_once 'general_functions.php';
function displayErrorAndExit($msg){
	echo '<p style="border: 1px solid red;">'.$msg.'</p>';
}

function createDbTables(){
	$db = singletonConnexionBD::getConnexionDatabase();
	mysql_query('create table test1( id int auto_increment primary key, truc text);', $db);
	mysql_query('create table test2( id int auto_increment primary key, truc text);', $db);
	mysql_query('create table test3( id int auto_increment primary key, truc text);', $db);
	mysql_query('create table test4( id int auto_increment primary key, truc text);', $db);
	mysql_query('create table test5( id int auto_increment primary key, truc text);', $db);
	mysql_query('create table test6( id int auto_increment primary key, truc text);', $db);
	mysql_query('create table test7( id int auto_increment primary key, truc text);', $db);
	mysql_query('create table test8( id int auto_increment primary key, truc text);', $db);
	mysql_query('create table test9( id int auto_increment primary key, truc text);', $db);
	mysql_query('create table test10( id int auto_increment primary key, truc text);', $db);
}
class singletonConnexionBD {

	// identifiant connexion
	private static $_id;
	//serveur
	private $_hostname = "localhost";
	// Login
	private $_username = "root";
	// Password
	private $_userpwd = "cdi2012";
	// Nom BD
	private $_dbname = "ajeslic_nov";
	//
	private $_createBase = true;
	//
	private $_configDir = "";
	

	/**
	 * Ouverture de la connexion et sélection de la base
	 * + création de la base et des tables (initilisation quoi)
	 */
	private function connectToDB() {
		// Open a connection to a MySQL server.
		self::$_id  = mysql_connect($this->_hostname, $this->_username, $this->_userpwd);

		if (self::$_id === false) {
			displayErrorAndExit('Impossible de se connecter au serveur MySQL : ' . mysql_error());
		} else {
			// Select the MySQL database
			$db = mysql_select_db($this->_dbname, self::$_id );
			// la selection de la base n'est pas possible, problème de droit ? ou base inexistante ?
			if($db === false){
				// on tente de créer la base
				if ($this->_createBase === true){
					$ret = mysql_query('drop database '. $this->_dbname. ' if exists', self::$_id );
					$query = 'CREATE DATABASE ' . $this->_dbname;
					$ret = mysql_query($query, self::$_id );
					if ($ret === FALSE) {
						displayErrorAndExit('Impossible de creer la base de donnees : ' . mysql_error());
					} else {
						// create all database tables
						$db = mysql_select_db($this->_dbname, self::$_id );
						if($db === false){
							displayErrorAndExit('Grrrrrrrrrrrrooooooooooooosssssssseeeeeeeeeuuuuuuuuuu Erreur : '. mysql_error());
						}
						else {
							createDbTables($this->_configDir);
						}
					}
				}
				else {
					displayErrorAndExit('Impossible de selectionner la base de donnees : ' . mysql_error());
				}
			}
		}
	}

	/**
	 * constructeur, privée car singleton
	 * Vide car inutile dans ce context (ce n'est pas un objet complet à proprement parler).
	 */
	private function __construct() {
		$this->connectToDB();
	}

	/**
	 * Récupérer la connexion MySQL existante
	 */
	public static function getConnexionDatabase() {
		if (is_null(self::$_id)) {
			try {
				new singletonConnexionBD();
			} catch (Exception $e) {
				displayErrorAndExit('Grosssssseeeuuuuuuuu errrrreeeeuuurrrrrr : ',$e->getMessage());
			}
		}
		return self::$_id;
	}

	/**
	 * Fermeture de la connexion
	 */
	public static function close() {
		if (self::$_id !== null)
			mysql_close(self::$_id);
	}
	/**
	 * Gestin du clone de la classe
	 */
	public function __clone() {
		trigger_error('Le clônage n\'est pas autorisé.', E_USER_ERROR);
	}
}

// test de la chose !
echo '<pre>';
$db = singletonConnexionBD::getConnexionDatabase();
var_dump($db);
$ret = mysql_query('select version() as v', $db);
$d = mysql_fetch_assoc($ret);
echo 'version : '.$d['v'];


$xxx = singletonConnexionBD::getConnexionDatabase();
var_dump($xxx);
$r = mysql_query("show tables;", $db);
while($data = mysql_fetch_row($r)){
	echo $data[0].'<br />';
	
}
echo '</pre>';
singletonConnexionBD::close();
?>
résultat

Code : Tout sélectionner

resource(3, mysql link) version : 5.5.27 resource(3, mysql link) test1 test10 test2 test3 test4 test5 test6 test7 test8 test9
on a toujours la même ressource qui est servie et les requêtes fonctionnes ;)

Problème de ce code ?

si tu colle un close au milieux de tous ça tu risque de ré employer un lien de base qui n'existe plus.

Après pour faire bien il faudrait que toute les requêtes passent par cet objet, de façon à ce qu'en cas de query tu test si la connexion est toujours bonne et relance si besoin.

@+

Re: Besoin d'un avis sur singleton de connexion

par Mazarini » 25 sept. 2012, 13:51

Une remarque qui ne concerne l'aspect singleton.
   private function setHostname($hostname) {
        if (!isset($hostname)) { // test si la variable est initialisée
            displayErrorAndExit('Impossible de se connecter au serveur MySQL : ' . mysql_error());
        } else {
            $this->_hostname = $hostname;
        }
    }
Pour les setter :
!isset() sera toujours faux. !empty() éventuellement (sauf pour le mot de passe)
mysql_error() ne contient rien.

Re: Besoin d'un avis sur singleton de connexion

par xTG » 25 sept. 2012, 13:23

Ce singleton n'est pas correct non.
En fait un singleton est censé toujours n'avoir qu'une seule instance.
Or avec ton code vu que le getter ne créé pas le singleton on passe obligatoirement par un appel du constructeur et il est donc possible d'ouvrir deux connexions, mais le singleton n'en sauvegardera qu'une (l'autre étant déréférencée).
Exemple (sinplifié dans les arguments) :
$mysql1 = new singletonConnexionBD();
$singleton = singletonConnexionBD::getConnexionDatabase(); // référence $mysql1
$mysql2 = new singletonConnexionBD();
$singleton = singletonConnexionBD::getConnexionDatabase(); // référence $mysql2
// Mais ici on peut utiliser aussi bien $mysql1 que $mysql2
// Seul le singleton retourne uniquement $mysql2
Un singleton n'est pas souvent appelé par un constructeur, on préfère utiliser le getter static pour éviter la multi-instanciation.
De ce fait ton getter doit être de la forme :
public static function getConnexionDatabase() {
  if( !empty(self::$_id) )
    return self::$_id;
  else
    return new singletonConnexionBD(); // renseignera self::$_id
}

Re: Besoin d'un avis sur singleton de connexion

par JulieDev » 25 sept. 2012, 10:58

En fait pour être plus claire #-o je voulais savoir si le code suivant était correct :
<?php

require_once 'general_functions.php';

class singletonConnexionBD {

// Connexion definie par:
// identifiant connexion
    public static $_id;
//serveur
    private $_hostname = "localhost";
// Login
    private $_username = "root";
// Password
    private $_userpwd = "";
// Nom BD
    private $_dbname = "ajeslic_nov";
//
    private $_createBase = FALSE;
//
    private $_configDir = "";
// Instance static de Connexion
    private static $_instanceConnexion;

// Ouverture de la connexion et sélection de la base
    private function connectToDB($hostname, $username, $userpwd, $dbname, $createBase = FALSE, $configDir="") {
// Open a connection to a MySQL server.
        $connection = mysql_connect($hostname, $username, $userpwd);
//echo'connexion ok';
        if ($connection == FALSE) {
            displayErrorAndExit('Impossible de se connecter au serveur MySQL : ' . mysql_error());
        }
// Select the MySQL database
        $db = mysql_select_db($dbname, $connection);
        if ($db == FALSE AND $createBase == TRUE) {
// create the database if needed when asked
            $query = 'CREATE DATABASE ' . $dbname;
            if (mysql_query($query, $connection) == FALSE) {
                displayErrorAndExit('Impossible de cr�er la base de donn�es : ' . mysql_error());
            } else {
// create all database tables
                createDbTables($configDir);
                $db = mysql_select_db($dbname, $connection);
            }
        } elseif ($db == FALSE and $createBase == FALSE) {
// return an error when the database does not exist if it's not asked to create it
            displayErrorAndExit('Impossible de s�lectionner la base de donn�es : ' . mysql_error());
        }
        return $connection;
    }

// Constructeur de la classe Connexion
    private function __construct($hostname, $username, $userpwd, $dbname, $createBase = FALSE, $configDir="") {
//Methodes permettant l'acces en ecriture des variables
        $this->setHostname($hostname);
        $this->setUsername($username);
        $this->setUserpwd($userpwd);
        $this->setDbname($dbname);
        $this->setCreateBase($createBase);
        $this->setConfigdir($configDir);
//Tentative de connexion à la base
        try {
            self::$_id = $this->connectToDB($this->_hostname, $this->_username, $this->_userpwd, $this->_dbname, $this->_createBase, $this->_configDir);
        } catch (Exception $e) {
            $e->getMessage();
        }
    }

    public static function getConnexionDatabase() {
        return self::$_id;
    }


  public static function close($id) {
        if (isset($id) && !empty($id)) {
            mysql_close($id);
            self::$_id = null;
   }}

    public function __clone() {
        trigger_error('Le clônage n\'est pas autorisé.', E_USER_ERROR);
    }
}
?>

Re: Besoin d'un avis sur singleton de connexion

par JulieDev » 25 sept. 2012, 10:52

J'avais aussi retourné une instance de connexion et un objet de la classe car il y avait du code déjà mis en place ne demandant qu'une connexion et non un objet.

Besoin d'un avis sur singleton de connexion

par JulieDev » 25 sept. 2012, 10:45

Bonjour,
je prépare actuellement ma soutenance et lors de mon stage j'avais fait le singleton de connexion ci-dessous.
Seulement je pense que les setter privés n'avaient pas leur utilité puisqu'on les appelle directement dans cette classe. Pourriez-vous me le confirmer ?
Un grand merci pour votre réponse !
<?php

require_once 'general_functions.php';

class singletonConnexionBD {

// Connexion definie par:
// identifiant connexion
    public static $_id;
//serveur
    private $_hostname = "localhost";
// Login
    private $_username = "root";
// Password
    private $_userpwd = "";
// Nom BD
    private $_dbname = "ajeslic_nov";
//
    private $_createBase = FALSE;
//
    private $_configDir = "";
// Instance static de Connexion
    private static $_instanceConnexion;

// Setter

   private function setHostname($hostname) {
        if (!isset($hostname)) { // test si la variable est initialisée
            displayErrorAndExit('Impossible de se connecter au serveur MySQL : ' . mysql_error());
        } else {
            $this->_hostname = $hostname;
        }
    }

    private function setUsername($username) {
        if (!isset($username)) {
            displayErrorAndExit('Impossible de se connecter au serveur MySQL : ' . mysql_error());
        } else {
            $this->_username = $username;
        }
    }

    private function setUserpwd($userpwd) {
        if (!isset($userpwd)) {
            displayErrorAndExit('Impossible de se connecter au serveur MySQL : ' . mysql_error());
        } else {
            $this->_userpwd = $userpwd;
        }
    }

    private function setDbname($dbname) {
        if (!isset($dbname)) {
            displayErrorAndExit('Impossible de se connecter au serveur MySQL : ' . mysql_error());
        } else {
            $this->_dbname = $dbname;
        }
    }

    private function setCreateBase($createBase) {
        if (!isset($createBase)) {
            displayErrorAndExit('Impossible de se connecter au serveur MySQL : ' . mysql_error());
        } else {
            $this->_createBase = $createBase;
        }
    }

    private function setConfigdir($configDir) {
        if (!isset($configDir)) {
            displayErrorAndExit('Impossible de se connecter au serveur MySQL : ' . mysql_error());
        } else {
            $this->_configDir = $configDir;
        }
    }

// Ouverture de la connexion et sélection de la base
    private function connectToDB($hostname, $username, $userpwd, $dbname, $createBase = FALSE, $configDir="") {
// Open a connection to a MySQL server.
        $connection = mysql_connect($hostname, $username, $userpwd);
//echo'connexion ok';
        if ($connection == FALSE) {
            displayErrorAndExit('Impossible de se connecter au serveur MySQL : ' . mysql_error());
        }
// Select the MySQL database
        $db = mysql_select_db($dbname, $connection);
        if ($db == FALSE AND $createBase == TRUE) {
// create the database if needed when asked
            $query = 'CREATE DATABASE ' . $dbname;
            if (mysql_query($query, $connection) == FALSE) {
                displayErrorAndExit('Impossible de cr�er la base de donn�es : ' . mysql_error());
            } else {
// create all database tables
                createDbTables($configDir);
                $db = mysql_select_db($dbname, $connection);
            }
        } elseif ($db == FALSE and $createBase == FALSE) {
// return an error when the database does not exist if it's not asked to create it
            displayErrorAndExit('Impossible de s�lectionner la base de donn�es : ' . mysql_error());
        }
        return $connection;
    }

// Constructeur de la classe Connexion
    private function __construct($hostname, $username, $userpwd, $dbname, $createBase = FALSE, $configDir="") {
//Methodes permettant l'acces en ecriture des variables
        $this->setHostname($hostname);
        $this->setUsername($username);
        $this->setUserpwd($userpwd);
        $this->setDbname($dbname);
        $this->setCreateBase($createBase);
        $this->setConfigdir($configDir);
//Tentative de connexion à la base
        try {
            self::$_id = $this->connectToDB($this->_hostname, $this->_username, $this->_userpwd, $this->_dbname, $this->_createBase, $this->_configDir);
        } catch (Exception $e) {
            $e->getMessage();
        }
    }

// Création d'une instance de connexion
    public static function getInstanceConnexion($hostname, $username, $userpwd, $dbname, $createBase = FALSE, $configDir="") {
        if (is_null(self::$_id)) {
            $c = __CLASS__;
            self::$_instanceConnexion = new $c($hostname, $username, $userpwd, $dbname, $createBase, $configDir);
        }
        return self::$_instanceConnexion;
    }

    public function getConnexionDatabase() {
        return self::$_id;
    }

////Destruction de l'instance de connexion
//    public function __destruct() {
//        if (isset(self::$_id) && !empty(self::$_id)) {
//            mysql_close(self::$_id);
//            is_null(self::$_id);
//        }
//    }

  public static function close($id) {
        if (isset($id) && !empty($id)) {
            mysql_close($id);
            self::$_id = null;
   }}

    public function __clone() {
        trigger_error('Le clônage n\'est pas autorisé.', E_USER_ERROR);
    }
}
?>