Klasa Zend_Db_Table jest zorientowanym obiektowo interfejsem pomiędzy naszym kodem, a tabelami w bazie danych. Widać więc, że ułatwia nam pracę tym bardziej, że dostarcza ona metody do wielu podstawowych operacji na danych przechowywanych w bazie. Dodatkową zaletą jest fakt, że użytkownik nie musi ingerować w poszczególne zapytania. Przygotowaniem odpowiednich zapytań dostosowanych do użytkowanej bazy danych zajmie się bowiem Zend Framework.
Pracując z Zend_Db_Table można na początku popełnić jeden banalny błąd, który będzie dość trudno znaleźć w środowisku deweloperskim.
Łączenie z bazą danych wielu developerów koduje w odpowiedniej metodzie w bootstrapie i zapisując handler połączenia z bazą w obiekcie rejestru (Zend_Registry).
protected function _initDatabase() { $config = new Zend_Config_Ini(APPLICATION_PATH . '/configs/application.ini', 'production'); $dbAdapter = Zend_Db::factory('Pdo_Mysql', $config->resources->db->params->toArray() ); Zend_Registry::set( 'dbHandler', $dbAdapter ); }
W dokumentacji ZF można znaleźć zapis sugerujący, że po nawiązaniu połączenia z bazą wystarczy na obiekcie Zend_Db_Table wywołać statyczną metodę definiującą domyślny adapter bazy:
Zend_Db_Table::setDefaultAdapter($dbAdapter);
Okazuje się to być jednak mylną ścieżką, ale o tym później.
Gdy już mamy stworzony mechanizm łączenia z bazą danych, kolejnym krokiem jest stworzenie odpowiedniej klasy dla naszych obiektów, a następnie przygotowujemy klasę odpowiedzialną za zmapowanie naszej klasy z tabelą bazy danych.
Klasa dla naszych obiektów:
class MyObject { private $id; private $name; // konstruktor, gettery i settery }
Klasa mapująca obiekty klasy MyObject na odpowiednią tabelę w bazie danych (np. “objects”):
class MyObjectTable extends Zend_Db_Table_Abstract { protected $_name = 'objects'; protected $_primary = 'objectId'; }
Gdy mamy już stworzone te dwie klasy, możemy przystąpić do oprogramowywania odpowiednich metod operujących na bazie danych. Poniżej przedstawiona została metoda pobierająca obiekt klasy MyObject o zadanym id:
public static function getObjectById( $id ) { $table = new MyObjectTable(); $select = $table->select()->where( 'objectId = ?', $id ); $result = $table->fetchRow( $select )->toArray(); //tworzenie obiektu klasy MyObject i zwrócenie jego instancji }
W powyższej metodzie pominąłem już tworzenie obiektu i jego zwracanie, ponieważ chcę się głównie skupić na kwestiach związanych z użytkowaniem Zend_Db_Table.
Wszystko wydaje się pięknie, nie widać żadnych problemów, kod działa prawidłowo – więc co może być nie tak. Otóż przy takim podejściu i ustawieniu domyślnego adaptera dla obiekt Zend_Db_Table w metodzie nawiązującej połączenie z bazą w bootstrapie, wywołanie każdego zapytania będzie skutkowało nawiązaniem nowego połączenia z bazą danych.
W prostej aplikacji, gdy do wygenerowania strony potrzebujemy kilku, kilkunastu zapytań – nie zauważymy żadnych nieprawidłowości. Problem może pojawić się w środowisku produkcyjnym, gdy stronę zacznie odwiedzać wielu użytkowników. W pewnym momencie otrzymamy bowiem błąd zgłoszony przez serwer bazy danych “Too many open connections”.
Jak więc temu zapobiec? Rozwiązanie jest dość proste. Przy tworzeniu klasy mapującej nasz obiekt na tabelę należy wywołać konstruktor klasy nadrzędnej (Zend_Db_Table_Abstract) z odpowiednimi parametrami konfiguracyjnymi. Należy przekazać handler do aktywnego połączenia z bazą lub klucz rejestru jeżeli przechowujemy handler w rejestrze. I należy to właśnie zrobić w tej klasie, a nie w bootstrapie po nawiązaniu połączenia z bazą danych.
Ja zastosowałem rozwiązanie, w którym stworzyłem własną klasę dziedziczącą po Zend_Db_Table_Abstract, która dba o przekazanie handlera bazy. Natomiast wszystkie klasy mapujące dziedziczą po mojej klasie, a nie po Zend_Db_Table_Abstract.
class Table extends Zend_Db_Table_Abstract { public function __construct($config = array()) { $dbAdapter = Zend_Registry::get( 'dbHandler' ); $config = array( 'db' => $dbAdapter ); parent::__construct($config); } }
Można zastosować też rozwiązanie z przekazaniem klucza rejestru, pod którym przechowujemy aktualny handler do bazy:
class Table extends Zend_Db_Table_Abstract { public function __construct($config = array()) { $config = array( 'db' => 'dbHandler' ); parent::__construct($config); } }
A nie lepiej użyć konfiguracji w application.ini?