Newsgroup: it.comp.www.php
Titolo: Condividere connessione al db tra oggetti mediante Singleton

Sto testando l'ottima classe ultimatemysql (MySQL database access
wrapper) scaricata da:
 
http://www.phpclasses.org/package/3698-PHP-MySQL-database-access-wrapper.html
 
 
 
Senza entrare nel dettaglio dell'operativit� della classe, vorrei capire
se il mio design � sensato. Ho 3 classi, diciamo A, B e C. Tutte e tre
fanno operazioni sul db, ed in un singolo script posso istanziare
parecchi oggetti di tali classi.
Ho quindi definito, per ciascuna classe, un membro private static $db,
che referenzia il Singleton della connessione al DB. Semplificando
(questa porzione di codice � replicata in A, B e C):
 
class A {
 
  private static $db;      // Connessione db
 
  function __construct() {
    $this->db = Singleton::getInstance('MySQL');
    if (!$this->db->IsConnected()) {
      if (!$this->db->Open()) {
        $this->db->Kill();
      }
    }
  }
}
 
In questo modo faccio un unica connessione al DB e tutti pescano da li.
C'� qualche trappola in un design simile? � una prassi comune?
 
-- 
g4b0, linux user n. 369000
http://brosulo.net
 


Io non capisco perch� il $db � statico (e poi la sintassi sotto mi pare 
sbagliata se l'attributo � statico non � self:: ?), cmq non ci vedo 
niente di male nel concetto di fondo.
Puoi anche istanziare una variabile diversa dentro ogni metodo, 
logicamente non � che cambi niente. Oppure mettere il $db in una 
superclasse da cui le altre ereditano (questo � l'approccio + usato nei 
framework che ho visto).
 
-- 
Z.

 framework che ho visto).
 
Stesso problema di cui sopra.
 
Oppure ho capito male io?


 (questa porzione di codice é replicata in A, B e C):
 
Primo errore: usare un singleton. :)
Una connessione a DB non deve essere singleton, perche` il giorno che te 
ne serve un'altra sei fregato.
 
Io di solito uso dependency injection con default value:
 
class DBConnection { ... }
 
class DBTable {
 static protected $defaultdbconn = null;
 protected $dbconn = null;
 
 public static function setDefaultDb(DBConnection $db)
 {
  self::$defaultdbconn = $db;
 }
 
 public function setDb(DBConnection $db)
 {
  $this->dbconn = $db;
 }
 
 public function getDb()
 {
  if (!is_null($this->dbconn)) {
   return $this->dbconn;
 
  } elseif (!is_null(self::$defaultdbconn)) {
   return self::$defaultdbconn;
 
  } else {
    throw new Exception('manca la connessione al db');
  }
 
  public function __construct(DBConnection $db = null)
  {
    $this->dbconn = $db;
  }
}
 
class MiaTabella extends DBTable {
 public function getAll()
 {
  $db = $this->getDb();
  return $db->query(...);
 }
}
 
 
Nel bootstrap del sito:
 
$defaultdb = new DBConnection(parametri vari);
DBTable::setDefaultDb($defaultdb);
 
 
Nei controller;
 
$miatabella = new MiaTabella(); // usa il db di default
 
$altratabella = new MiaTabella(
 new DBConnection(parametri alternativi)
); // usa un db diverso 
 
 
Bye.

 Oppure ho capito male io?
 
Non so, forse a mia volta ti interpreto male :D
Se la classe di connessione al db � un singleton, di esso durante 
l'esecuzione ce � uno solo: se la istanzi 1000 volte non ne vengono 
create 1000, ma 1 la prima volta e basta, indipendentemente da come la 
istanzi.
 
-- 
Z.

 ne serve un'altra sei fregato.
 
In realtá non ho implementato il singleton direttamente nella classe del
MySQL, ma ho usato una classe statica che mi fornisce il singleton.
 
final class Singleton {
 
  private static $instances = array();
 
  public static function getInstance($class_name,$param=null) {
    if (!class_exists($class_name)) {
      trigger_error("$class_name . " non esiste!", E_USER_ERROR);
    }
 
    $class_name = strtolower($class_name);
    if (!array_key_exists($class_name, self::$instances)) {
      if ($param==null) {
        self::$instances[$class_name] = new $class_name;
      } else {
        $rc = new ReflectionClass($class_name);
        self::$instances[$class_name] = $rc->newInstanceArgs($param);
      }
    }
 
    return self::$instances[$class_name];
  }
}
 
Se avessi bisogno di una seconda connessione nulla mi vieta di
implementare getInstance2(), anche se é un po' una soluzione alla "gigi
lo zozzone". Ho appena inventato il doubleton :)
 
Scherzi a parte, il singleton é male a prescindere, oppure puó servire
in alcuni casi?
 
Non ho capito molto la tua soluzione, ora vado a studiarmi la dependency
injection. Provo con questo link, che ne dici?
 
http://www.potstuck.com/2009/01/08/php-dependency-injection/
 


 La connessione se è singleton sulla classe mysql una è e una rimane.
 
Si, ma la classe MySQL preferisco non modificarla, in quanto in effetti
potrebbe essere utile collegarsi a piú di un database. Per questo scopo
uso la classe Singleton, che ho postato in risposta ad Alessandro (con
la quale posso avere qualsiasi classe singletonabile).


 injection. Provo con questo link, che ne dici?
 
Il DI e` piu` semplice di quanto credi.
 
Semplicemente non fai istanziare un oggetto alla classe che lo usa, ma lo 
istanzi da fuori e glielo passi (vedi le ultime tre righe dell'esempio 
che ti ho fatto).
 
Quello e` DI.
 
L'unica cosa che ti interessa e` che le classi che passi abbiano una API 
definita. Per questo di solito si usa una Interface, una classe Abstract 
o comunque una classe base da cui derivare quelle che implementano 
quell'interfaccia (vedi i type hinting che ho messo, sempre nell'esempio)
 
 
Quello che forse ti ha confuso nel mio esempio e` il missiotto di static 
e non static. E` solo un trucco che uso per non usare un singleton, e per 
iniettare la dipendenza di default, che e` un fallback se non inietti la 
dipendenza ad ogni istanza.
 
La funzione getDb() serve appunto a implementare il fallback: se l'utente 
ha fornito un "oggetto database" durante l'istanziazione, uso quello. Se 
non l'ha fatto, ma ho un default impostato durante il bootstrap, uso 
quello. Altrimenti sollevo eccezione.
 
Leggi l'esempio che ti ho postato dal basso (strato applicazione) verso 
l'alto (strato "framework"), forse e` piu` chiaro.
 
E` il DIC (Dependency Injection Container) che e` complicato, ma per ora 
lascialo stare.
 
Bye.

 per classe contenuta.
 
E se anziché Registry la chiamassi SingletonGenerator? In effetti nella
stragrande maggioranza dei casi serve soltanto una connessione al DB, ed
in caso contrario ho comunque una via di fuga...
 
Non é sicuramente una soluzione elegante, ma nel caso servisse un
singleton potrebbe tornare utile...
