Ostatnio mnie ostro zjechaliście. Dzięki za komentarze, szczególnie te negatywne (yyy wszystkie?). Wszystkie starannie przeczytałem. Poczytałem co nieco i zdecydowałem się uderzyć z tematem jeszcze raz. Tym razem uwzględniając zadania takie jak „a co jak będę miał kilka serwerów: testowy, produkcyjny, itp.”, „a co jak chcę połączyć się z dwiema bazami na raz”?
Punktem wyjścia stał się Twittee, czyli kontener stworzony w 2009 r. przez Fabiena Potenciera zajmujący 140 znaków (tyle żeby całość dała się przesłać w postaci pojedynczej wiadomości w serwisie Twitter). Podstawą kontenera jest magia __set()
i __get()
, czyli to co Zyx lubi najbardziej Całość została przeze mnie mocno zmodyfikowana. Dodałem np. rzucanie wyjątkami jeżeli wymagana wartość nie została ustawiona plus obsługę domknięć w przypadku gdy ustawiona wartość jest funkcją anonimową.
Parę linijek dotyczących ustawienia PDO wcisnąłem do funkcji anonimowej plus dodałem możliwość trzymania pojedynczej instancji PDO w razie potrzeby (zwrócę potem uwagę na static
w domknięciu). Obiekt PDO „nie wie”, że jest w kontenerze i dobrze. Istotą DI jest to żeby klas nie trzeba było specjalnie dostosowywać do współpracy z kontenerem.
Obsługę wyjątków w całości zrzucam na klientów nie mieszając kompetencji kontenera, który ma ustawiać/zwracać zmienne/fabrykować obiekty.
class DBContainer {
protected $values = array();
public function __construct() {
$this->loadDefaults();
}
protected function loadDefaults() {
$this->pdo_driver = 'mysql';
$this->pdo_host = 'localhost';
$this->pdo_dbname = 'nazwabazy';
$this->pdo_user = 'user';
$this->pdo_pass = 'haslo';
$this->pdo_charset = 'SET NAMES utf8';
$this->pdo_persist = false;
$this->pdo_getpdo = function(DBContainer $cont) {
// static w kontekscie funkcji anonimowej
static $persistentPDO;
$pdoCreator = function() use ($cont) {
if (!extension_loaded('PDO')) throw new Exception('Brak modulu PDO');
$pdo = new PDO(
$cont->pdo_driver . ':host=' . $cont->pdo_host . ';dbname=' . $cont->pdo_dbname,
$cont->pdo_user,
$cont->pdo_pass,
array(PDO::MYSQL_ATTR_INIT_COMMAND => $cont->pdo_charset));
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $pdo;
};
if ($cont->pdo_persist && $persistentPDO instanceof PDO) {
return $persistentPDO;
}
elseif ($cont->pdo_persist) {
$persistentPDO = $pdoCreator();
return $persistentPDO;
}
else {
return $pdoCreator();
}
};
public function __set($key,$val) {
$this->values[$key] = $val;
}
public function __get($key) {
if (!isset($this->values[$key])) {
throw new Exception("Wartosc {$key} nie istnieje");
}
if ($this->values[$key] instanceof Closure) {
return $this->values[$key]($this);
}
else {
return $this->values[$key];
}
}
}
Przykłady użycia:
$c = new DBContainer();
// PDO na domyslnych ustawieniach
$pdo = $c->pdo_getpdo;
// przestawiam baze danych
$c->pdo_dbname = 'testowa baza';
$nowePDOdlaBazyTestowa = $c->pdo_getpdo;
// znowu przestawiam baze danych, przestawiam na zapis PDO na stale
$c->pdo_dbname = 'baza produkcyjna';
$c->pdo_persist = true;
// sprawdzam czy na pewno obiekty PDO sa tej samej instancji
echo spl_object_hash($c->pdo_getpdo) . '<br>' . spl_object_hash($c->pdo_getpdo);
// zwroci taki sam hash
Na raz następny pokażę jak można fajnie korzystać z tego dla obiektów korzystających z pdo wewnętrznie.
Osoby nielubiące magii uprasza się o powstrzymanie od wylewania żalu. Po to zrobili __get()
, __set()
i dynamiczne typy zmiennych żeby z nich korzystać. Dobra dokumentacja wg mnie załatwia sprawę.