PDO poprzez Dependency Injection Container [cz. 1/2]

Ostat­nio mnie ostro zjecha­li­ście. Dzięki za komen­ta­rze, szcze­gól­nie te nega­tywne (yyy wszyst­kie?). Wszyst­kie staran­nie prze­czy­ta­łem. Poczy­ta­łem co nieco i zdecy­do­wa­łem się uderzyć z tema­tem jesz­cze raz. Tym razem uwzględ­nia­jąc zada­nia takie jak „a co jak będę miał kilka serwe­rów: testowy, produk­cyjny, itp.”, „a co jak chcę połą­czyć się z dwiema bazami na raz”?

Punk­tem wyjścia stał się Twit­tee, czyli konte­ner stwo­rzony w 2009 r. przez Fabiena Poten­ciera zajmu­jący 140 znaków (tyle żeby całość dała się prze­słać w postaci poje­dyn­czej wiado­mo­ści w serwi­sie Twit­ter). Podstawą konte­nera jest magia __set()__get(), czyli to co Zyx lubi najbar­dziej :-) Całość została przeze mnie mocno zmody­fi­ko­wana. Doda­łem np. rzuca­nie wyjąt­kami jeżeli wyma­gana wartość nie została usta­wiona plus obsługę domknięć w przy­padku gdy usta­wiona wartość jest funk­cją anonimową.

Parę lini­jek doty­czą­cych usta­wie­nia PDO wcisną­łem do funk­cji anoni­mo­wej plus doda­łem możli­wość trzy­ma­nia poje­dyn­czej instan­cji PDO w razie potrzeby (zwrócę potem uwagę na static w domknię­ciu). Obiekt PDO „nie wie”, że jest w konte­ne­rze i dobrze. Istotą DI jest to żeby klas nie trzeba było specjal­nie dosto­so­wy­wać do współ­pracy z kontenerem.

Obsługę wyjąt­ków w cało­ści zrzu­cam na klien­tów nie miesza­jąc kompe­ten­cji konte­nera, 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];
        }
    }
}

Przy­kł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 korzy­stać z tego dla obiek­tów korzy­sta­ją­cych z pdo wewnętrznie.

Osoby nielu­biące magii upra­sza się o powstrzy­ma­nie od wyle­wa­nia żalu. Po to zrobili __get(), __set() i dyna­miczne typy zmien­nych żeby z nich korzy­stać. Dobra doku­men­ta­cja wg mnie zała­twia sprawę.

Podobne wpisy:

  1. PDO poprzez Depen­dency Injec­tion Conta­iner [cz. 2/2]
  2. A Ty w jaki sposób łączysz się z bazą danych?
  3. MySQL, PDO i proce­dury składowane
  4. Weź mi zrób proce­sor tekstu cz. 2 / 2

7 Comments

  • 17 kwietnia 2011 - 15:04 | Permalink

    No widzisz, klucz to uczyć się na błędach. Dalej nie jest ideal­nie, ale lepiej niż ostatnio.

  • Crozin
    17 kwietnia 2011 - 16:01 | Permalink

    Naprawdę chyba bardziej auto­ma­gicz­nego badzie­wia, szcze­gól­nie w przy­padku końco­wego wyko­rzy­sta­nia, nie dało się napisać.

    1. Warto­ści domyślne w klasie: prze­cież nigdy nikt nie da takich usta­wień bazy danych. Takie coś musi być usta­wiane spoza klasy.
    2. Poziom magicz­no­ści jest tak duży, że sam za 2 tygo­dnie nie będziesz wiedział co się w tym kodzie dzieje.

    PS. __get() i __set() trzeba umieć używać. Tutaj nie ma dla nich miej­sca, bo komplet­nie niczego nie zysku­jesz korzy­sta­jąc z nich. Chyba, że argu­men­tem jest ilość znaków w kodzie.

  • Śpiechu
    17 kwietnia 2011 - 18:06 | Permalink

    @Crozin
    ad 1.: Można wywa­lić dane na temat bazy, a zosta­wić tylko metodę fabry­ku­jącą pdo_getpdo. No problemo.
    ad 2.: Dobra doku­men­ta­cja wg mnie zała­twia sprawę.

    Podstawy nie wymy­śli­łem sam. Poda­łem źródło do doświad­czo­nego progra­mi­sty. Zresztą to zobacz jego GitHub. Jeżeli on tego używa to mi to wystarczy.

  • Crozin
    17 kwietnia 2011 - 19:03 | Permalink

    W dzie­ciń­stwie usły­sza­łem bardzo mądre zdanie, które zmie­niło moje spoj­rze­nie na świat w wielu aspek­tach: „Tylko dlatego, że jakaś bzdura została powie­dziana przez kogoś mądrego, nie czyni tej bzdury mniejszą”.

    Kod (chociaż nie doty­czy to tylko progra­mo­wa­nia) powi­nien bronić się sam, a źródło jego pocho­dze­nia jest nieistotne.

    Twit­tee należy trak­to­wać jako cieka­wostkę albo co najwy­żej jako punkt wyjścia do wytłu­ma­cze­nia komuś czym jest DIC. W **żadnym** wypadku nie nadaje się to do faktycz­nego użytku.

    Napi­sa­łeś, że dobra doku­men­ta­cja zała­twia sprawę. Bezsprzecz­nie, dobra doku­men­ta­cja to podstawa, ale spró­buj ten kod dobrze udoku­men­to­wać (chociażby „metodę fabry­ku­jącą pdo_getpdo”, która ma dziwną nazwę i metodą nie jest). Już tutaj poja­wią się problemy, które de facto sam wytwo­rzy­łeś. Aż boję się pomy­śleć bo by było gdybyś w taki sposób napi­sał biblio­tekę z 25.000 linii kodu.

    Spójrzmy jesz­cze raz na listę WTF-ów tego kodu:

    1. Niby jest możli­wość skorzy­sta­nia z dowol­nego sterow­nika PDO (swoją drogą poza spraw­dze­niem czy samo PDO jest wypa­da­łoby dodać spraw­dze­nie czy jest i sterow­nik) ale jednak jest tam na sztywno, bez żadnego warunku spraw­dza­ją­cego wstawka z MySQL.
    2. Jakieś warto­ści (właści­wość „values”), które nie wiadomo czym są. Sama biblio­teka stosuje jakieś dziwne prefiksy „pdo_”.
    3. IDE nie podpo­wie mi abso­lut­nie niczego. Nawet jeżeli dodasz tam komen­ta­rze phpDoca niewiele to pomoże, bo co tam niby udoku­men­tu­jesz? Bez patrze­nia w doku­men­ta­cję albo źródła za dwa tygo­dnie za cholerę nie będę wiedzieć jak z tego korzy­stać.
    4. Nakła­dasz kolejną warstwę abstrak­cji na PDO nie wnosząc w nie niczego, po co?

    A teraz spójrz na jakiś sensow­niej­szy publiczny inter­fejs dla tej klasy. Inter­fejs, bo poka­zy­wa­nie imple­men­ta­cji nie jest nawet konieczne byś domy­ślił się dzia­ła­nia kodu:

    __construct()
    addConnection(String name, PDO connec­tion)
    getConnection(String name)
    hasConnection(String name)
    createConnection(String dns, array attributes)

    Ewen­tu­al­nie można dodać, chociaż jest to raczej niepo­trzebna metoda:
    createConnection(String driver, String host, String user, String password, String data­base, int port, array attributes)

    I bez dobrej doku­men­ta­cji czy prze­glą­da­nia kodu dosko­nale wiesz czego się po tym spodzie­wać. A jeżeli zapo­mnę jak się czegoś używało Ctrl + Spacja w IDE ładnie wyświe­tli mi listę dostęp­nych metod. Wszystko jest jawne, proste, szybkie.

  • 17 kwietnia 2011 - 21:01 | Permalink

    @Crozin: spokoj­nie, nie ma co się ciskać. Powsta­nie jesz­cze kilka wersji takiego kodu i w końcu wrócimy do punktu wyjścia — użycia czystego PDO, albo prze­nie­sie­nia się na abstrak­cję bazy danych zaim­ple­men­to­waną w jakimś frame­worku. Ja prze­ra­bia­łem już te wszyst­kie możli­wo­ści i powiem krótko — nie ma sensu. ;]

  • 19 kwietnia 2011 - 07:11 | Permalink

    Jednym zdaniem: nie widzę tutaj powodu, aby stoso­wać funk­cje anoni­mowe — nic w tym wypadku nie ułatwiają, a tylko wpro­wa­dzają niepo­trzeb­nie dziw­nie posłu­gi­wa­nie się połą­cze­niem z bazą.

  • Pingback: PDO poprzez Dependency Injection Container [cz. 2/2] – /home/Śpiechu->Blog

  • Dodaj komentarz

    Twój adres e-mail nie zostanie opublikowany.

    Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <p> <pre lang="" line="" escaped=""> <q cite=""> <strike> <strong>