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ę.
Podobne wpisy:

O autorze
7 Comments
No widzisz, klucz to uczyć się na błędach. Dalej nie jest idealnie, ale lepiej niż ostatnio.
Naprawdę chyba bardziej automagicznego badziewia, szczególnie w przypadku końcowego wykorzystania, nie dało się napisać.
1. Wartości domyślne w klasie: przecież nigdy nikt nie da takich ustawień bazy danych. Takie coś musi być ustawiane spoza klasy.
2. Poziom magiczności jest tak duży, że sam za 2 tygodnie nie będziesz wiedział co się w tym kodzie dzieje.
PS. __get() i __set() trzeba umieć używać. Tutaj nie ma dla nich miejsca, bo kompletnie niczego nie zyskujesz korzystając z nich. Chyba, że argumentem jest ilość znaków w kodzie.
@Crozin
ad 1.: Można wywalić dane na temat bazy, a zostawić tylko metodę fabrykującą
pdo_getpdo. No problemo.ad 2.: Dobra dokumentacja wg mnie załatwia sprawę.
Podstawy nie wymyśliłem sam. Podałem źródło do doświadczonego programisty. Zresztą to zobacz jego GitHub. Jeżeli on tego używa to mi to wystarczy.
W dzieciństwie usłyszałem bardzo mądre zdanie, które zmieniło moje spojrzenie na świat w wielu aspektach: „Tylko dlatego, że jakaś bzdura została powiedziana przez kogoś mądrego, nie czyni tej bzdury mniejszą”.
Kod (chociaż nie dotyczy to tylko programowania) powinien bronić się sam, a źródło jego pochodzenia jest nieistotne.
Twittee należy traktować jako ciekawostkę albo co najwyżej jako punkt wyjścia do wytłumaczenia komuś czym jest DIC. W **żadnym** wypadku nie nadaje się to do faktycznego użytku.
Napisałeś, że dobra dokumentacja załatwia sprawę. Bezsprzecznie, dobra dokumentacja to podstawa, ale spróbuj ten kod dobrze udokumentować (chociażby „metodę fabrykującą pdo_getpdo”, która ma dziwną nazwę i metodą nie jest). Już tutaj pojawią się problemy, które de facto sam wytworzyłeś. Aż boję się pomyśleć bo by było gdybyś w taki sposób napisał bibliotekę z 25.000 linii kodu.
Spójrzmy jeszcze raz na listę WTF-ów tego kodu:
1. Niby jest możliwość skorzystania z dowolnego sterownika PDO (swoją drogą poza sprawdzeniem czy samo PDO jest wypadałoby dodać sprawdzenie czy jest i sterownik) ale jednak jest tam na sztywno, bez żadnego warunku sprawdzającego wstawka z MySQL.
2. Jakieś wartości (właściwość „values”), które nie wiadomo czym są. Sama biblioteka stosuje jakieś dziwne prefiksy „pdo_”.
3. IDE nie podpowie mi absolutnie niczego. Nawet jeżeli dodasz tam komentarze phpDoca niewiele to pomoże, bo co tam niby udokumentujesz? Bez patrzenia w dokumentację albo źródła za dwa tygodnie za cholerę nie będę wiedzieć jak z tego korzystać.
4. Nakładasz kolejną warstwę abstrakcji na PDO nie wnosząc w nie niczego, po co?
A teraz spójrz na jakiś sensowniejszy publiczny interfejs dla tej klasy. Interfejs, bo pokazywanie implementacji nie jest nawet konieczne byś domyślił się działania kodu:
__construct()
addConnection(String name, PDO connection)
getConnection(String name)
hasConnection(String name)
createConnection(String dns, array attributes)
Ewentualnie można dodać, chociaż jest to raczej niepotrzebna metoda:
createConnection(String driver, String host, String user, String password, String database, int port, array attributes)
I bez dobrej dokumentacji czy przeglądania kodu doskonale wiesz czego się po tym spodziewać. A jeżeli zapomnę jak się czegoś używało Ctrl + Spacja w IDE ładnie wyświetli mi listę dostępnych metod. Wszystko jest jawne, proste, szybkie.
@Crozin: spokojnie, nie ma co się ciskać. Powstanie jeszcze kilka wersji takiego kodu i w końcu wrócimy do punktu wyjścia — użycia czystego PDO, albo przeniesienia się na abstrakcję bazy danych zaimplementowaną w jakimś frameworku. Ja przerabiałem już te wszystkie możliwości i powiem krótko — nie ma sensu. ;]
Jednym zdaniem: nie widzę tutaj powodu, aby stosować funkcje anonimowe — nic w tym wypadku nie ułatwiają, a tylko wprowadzają niepotrzebnie dziwnie posługiwanie się połączeniem z bazą.
Pingback: PDO poprzez Dependency Injection Container [cz. 2/2] – /home/Śpiechu->Blog