Zend Framework składa się z wielu komponentów, a jednym z nich jest ten odpowiedzialny za komunikację z bazą danych – Zend_Db.
Jednakże, celem tego wpisu nie jest opisywanie Zend_Db
– to zostało zrobione już w dokumentacji. Celem tego wpisu jest wyrażenie mojej opinii na temat tego co mi się w Zend_Db podoba, a co nie – jest to oczywiście wpis czysto subiektywny. Traktuje ten wpis także jako przyczynek do dyskusji na temat, czy Zend_Db
jest naprawdę tak złym komponentem, za jakiego ma go wielu programistów PHP – m.in patrz komentarze do wpisu u batmana. Chciałbym się także odnieść do podanych tam „zarzutów”. Niektóre z przedstawionych przeze mnie zalet bądź wad mogą Wam się wydawać śmieszne bądź banalne, ale kilka takich rzeczy połączonych ze sobą wpływa na wygodę korzystania z danego rozwiązania. Tak więc zaczynamy!
Zalety
Oparty głównie na PDO
Coraz więcej frameworków idzie tą drogą, co uważam za niewątpliwy plus. PDO de facto jest standardem w PHP i bardzo dobrze, że twórcy zdecydowanej większości frameworków się tego trzymają. Dzięki temu, kiedy przychodzi potrzeba napisania bardziej zaawansowanego zapytania możemy skorzystać ze znanych nam metod i oczekiwać tego samego, czego oczekiwaliśmy korzystając z czystego PDO.
Wygoda podczas stosowania klauzuli WHERE
Kwestia oczywiście w pełni subiektywna. Mam na myśli korzystanie głównie z metody fetchAll
i automatycznego bindowania parametrów jako odpowiedni typ danych. Przykład:
$model->fetchAll(array(
'item_id = ?' => 12, //bind as PDO::PARAM_INT, cast it using (string)12 to automatically bind as PDO::PARAM_STR
'name = ?' => 'foobar', //PDO::PARAM_STR
'category_id IN (?)' => arrray(1, 2, 4, 7) // bind each array element separately as PDO::PARAM_INT -> gives you: category_id IN (?, ?, ?, ?)
));
Stosunkowo mała liczba plików
Zend_Db w obecnej wersji frameworka (1.11.5) waży ok. 600KB i mieści się w 53 plikach – z czego 17 z nich to puste klasy wyjątków Exception.php
. Dla porównania – Doctrine2 DBAL (czyli sama warstwa abstrakcji bazy danych) to 144 pliki, w tym 10 od wyjątków. Oczywiście, w obu przypadkach można te liczby jeszcze bardziej zmniejszyć, wyrzucając niepotrzebne adaptery.
ActiveRecord
Podoba mi się fakt, że mogę zdefiniować własne metody odnoszące się bezpośrednio do rekordu
O ile twórcy ZF piszą o Zend_Db_Table_Row
jako o implementacji Row Data Gateway, jednakże z punktu widzenia używającego go programisty niedaleko mu do ActiveRecord. Mam na myśli możliwość definiowania własnych metod odnoszących się do konkretnego rekordu. Banalny przykład:
// /application/models/Product.php
class Model_Product extends Zend_Db_Table_Abstract
{
protected $_name = 'product';
protected $_rowClass= 'Model_ProductRow'; //use Model_ProductRow instead Zend_Db_Table_Row
}
// application/models/ProductRow.php
class Model_ProductRow extends Zend_Db_Table_Row_Abstract
{
public function getPriceWithTax()
{
return $this->price * 0.23; //where price is column in database defining price without VAT tax rate
}
}
To samo tyczy się obiektów kolekcji – w przypadku Zend Frameworka dziedziczących po klasie Zend_Db_Table_Rowset
. Mogę ustawić własną klasę, gdzie zaimplementuje potrzebne mi metody kolekcji.
Wspracie dla interfejsów ArrayAccess oraz Iterable w przypadku obiektów i kolekcji
Kolejna rzecz, która wpływa na wygodę. Mogę odnosić się do właściwości obiektu używając notacji tablicowej. Mogę także iterować po obiekcie kolekcji używając foreach
, bez wykonywania za każdym razem toArray()
.
Zend_Db_Select == QueryBuilder
O ile na początku korzystanie z Zend_Db_Select sprawiało mi sporą trudność (moim kolegom z zespołu także), tak po pewnym czasie jest to dla nas natywny sposób pisania bardziej skomplikowanych zapytań podczas korzystania z ZF. Właśnie dzięki jego obiektowej naturze i wykorzystaniu fluent interface mogę dowolnie łączyć warunki zapytania bądź zmieniać je korzystając z metody reset()
. Przykład:
class Model_Foo extends Zend_Db_Table_Abstract
{
private $_name = 'foo';
// use fluent interface and add condition
public function fetchWithLeftJoinWhereBarIsntNull()
{
$select = $this->_getBaseSelect();
$select->where('bar IS NOT NULL');
return parent::fetchAll();
}
// user reset() and remove LEFT JOIN
public function fetchWithoutLeftJoin()
{
$select = $this->_getBaseSelect();
$select->reset(Zend_Db_Select::LEFT_JOIN);
return parent::fetchAll($select);
}
private function _getBaseSelect()
{
$select = $this->select();
$select->from('table1')
->setIntegrityCheck(false) // allow to join another tables
->joinInner('table2', 'table1.id = table2.table1_id')
->joinLeft('table3', 'table1.id = table3.table1_id');
return $select;
}
}
Wady
Brak nazwanych relacji
O używaniu relacji w Zend Framework pisał już batman we wpisie Zend_Db i relacje. Jednak mi nie odpowiada fakt, że korzystając domyślnej metody opisywanej praktycznie wszędzie muszę odnosić się do rekordów podrzędnych po nazwie klasy, a nie nazwie zdefiniowanej relacji. Chciałbym, abym mógł napisać tak:
$params = $userRow->findDependentRowset('UserParamsRelationWithCustomName'); //instead of specifying model class
Nazwa klasy zawsze może się zmienić, a to ułatwia refactoring. Dodatkowo, chciałbym móc w ramach relacji zdefiniować dodatkowe parametry dotyczące połączenia – nie tylko kolumnę, po jakiej ma się ono odbyć, ale także warunki do kolumn dodatkowych. Istnieje co prawda Zend_Db_Table_Definition
, jednak rozwiązanie to ma dwie wady. Po pierwsze wymaga osobnej definicji dla każdej klasy modelu, co jest niewygodne. Po drugie, nadal nie pozwala na definiowanie dodatkowych warunków dla połączeń.
Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...
Zwiń
Czytaj na blogu autora...