Autor wpisu: Śpiechu, dodany: 16.04.2011 19:16, tagi: php, mysql
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ę.
Autor wpisu: sokzzuka, dodany: 16.04.2011 10:38, tagi: php
Jakiś czas temu, zainspirowany artykułem Giorgio Sironiego - „How to remove getters and setters” rozpocząłem dyskusję na forum goldenline na temat przydatności getterów i setterów. Dyskusja była dość burzliwa, jak to zawsze ma miejsce, gdy próbuje się naruszać dogmaty. Jednak wszystko przebiegło w na tyle przyjaznej atmosferze, że nie skończyło się na „flejmłorze” i padło kilka interesujących argumentów. Teraz nadszedł czas by z perspektywy czasu podsumować tą dyskusje i przedstawić wnioski, do jakich doszedłem po jej zakończeniu. Dla tych, którym się nie chce czytać artykułu, szybki szkic problematyki – Giorgio wysunął śmiałą tezę, że stosowanie „getterów i setterów” psuje enkapsulację, która jest jedną z podstaw programowania zorientowanego obiektowo. W swoim artykule opisuje on sposoby na całkowite pozbycie się tych często używanych artefaktów.
Jako, że zgadzam się z główną tezą artykułu Giorgia, w dalszej części artykułu zamierzam zaprezentować przypadki w których użycie getterów bądź setterów jest szkodliwe, wraz z uzasadnieniem i przykładowym rozwiązaniem problemu. Pokaże również, przypadki w których zastosowanie tych konstrukcji ma sens. Abyśmy się jednak dobrze zrozumieli, najpierw moja definicja – czym są gettery i settery.
Jako getter i setter, rozumiem metody klasy, zwykle nazwane wg wzoru „getFoo()”, „setFoo()”, które ”mapują się 1:1″ z odpowiadającymi im polami klasy. Przykład:
class Bar { private $_foo; public getFoo(){ return $this->_foo; } public setFoo($foo){ $this->_foo = $foo; } }
Przypadki problematyczne:
1.Setter, który wpływa na obiekty kolaborujące klasy.
Wyobraźmy sobie, że mamy sobie taką oto klasę do obsługi tabeli w bazie danych:
class Foo_Table { private $_db; public function setDb($db){ $this->_db = $db; } public function getDb(){ return $this->_db; } public function fetchAll(){ //kod } }
Przypomina ona z grubsza klase Zend_Db_Table_Abstract i jest to właściwe skojarzenie (różnica polega na obecności statycznego settera). . Zobaczmy teraz jakie problemy generuje zastosowanie settera:
//gdzies w kodzie tworzymy obiekt $fooTable $db1 = new PDO($dsn, $username, $passwd, $options); $fooTable = new Foo_Table; $fooTable->setDb($db1); //inne miejsce w kodzie $fooTable->fetchAll(); //zwraca array(1,2,3); //inne miejsce w kodzie $db2 = new PDO($dsn2, $username, $password,$options); $fooTable->setDb($db2); //inne miejsce w kodzie $fooTable->fetchAll(); //zwraca array(4,5,6) - inna baza danych
Jak widać na wyżej załączonym kodzie, z powodu zastosowania settera, można w międzyczasie zmienić obiekt adaptera bazy danych, z którego korzysta klasa tabeli. Konsekwencją tego jest mniejsza przewidywalność klasy – w drugim przypadku dostajemy inny wynik. W przypadku Zend_Db_Table_Abstract sytuację pogarsza fakt, zastosowania statycznego settera, co wielokrotnie zwiększa możliwości zmiany adaptera i nieprzewidywalność takiego kodu. Łatwo usunąć ten problem poprzez likwidację settera i zastosowanie wstrzykiwania obiektu połączenia z bazą danych poprzez konstruktor.
2. Rozpełzanie się kodu
Dziwna nazwa, ale już tłumacze o co chodzi. Zostawmy na chwilę poprzedni przykład z tabelą, żeby nie było, że przyczepiłem się do Zend_Db. Kolejnym przykładem będzie klasa reprezentująca użytkownika, oczywiście dla potrzeb przykładu została skrócona do minimum: