Witam,
Od pewnego czasu programiści PHP naśladują programistów Javy, sam zresztą to robię. Efektem tego jest przenoszenie wzorców projektowych z Javy do PHP. Na przykład
Zend Framework a w szczególności jego kod odpowiedzialny za bazy danych – Zend_Db (działający prawie jak
ORM) przypomina w pewien sposób to, za co świat Javy pokochał
Hibernate.
Konsekwencją użycia ORM w projekcie jest to, że spada nam złożoność zapytań do bazy danych, natomiast zwiększa się ich ilość. Hibernate potrafi keszować obiekty. Zend Framework niestety nie. Jednak wczoraj tego bardzo potrzebowałem, renderowanie prostej strony WWW wymagało 500 zapytań do bazy, grrr, z czego 450 było powtórzeniem poprzednich zapytań. O ile keszowanie obiektów było by bardzo skomplikowane, to w miarę prosto można było keszować wyniki zapytań SQL w obrębie jednego żądania HTTP.
Jak się do tego zabrać? Zauważyłem, że wszystkie zapytania do bazy danych produkowane przez Zend Framework w pewnym momencie trafią do metody
query obiektu Zend_Db_Adapter_*, fajnie:). Naszą klasę keszującą trzeba odziedziczyć po tej klasie. Oczywiście można to zrobić na wiele innych sposobów, i bardzo ładnie wpasować w to wzorzec projektowy dekorator, ale ja nie miałem czasu. Modyfikujemy metodę query, tak by zapamiętywała swoje wyniki i trzymała w keszu. U mnie działa coś takiego (wstydził bym się tego kodu, gdyby nie to że robi coś na prawdę przydatnego):
class JakubiakDbAdapterPdoPgsql extends Zend_Db_Adapter_Pdo_Pgsql{
public function query($sql, $bind = array()){
$sql = $sql . ""; // a sprobuj no bez tego
$hash = $this->_calculateHash($sql,$bind);
// czy uda nam sie pobrac z kesza
if($this->_isCacheEnabled) {
if($this->_allowCache($sql,$bind)) {
if(isset($this->_cachedResults[$hash])) {
return $this->_cachedResults[$hash];
}
}
}
$res = parent::query($sql, $bind);
// dopisanie do kesza
if($this->_isCacheEnabled) {
$this->_cachedResults[$hash] = $res;
}
return $res;
}
// pozwalam na keszowanie tylko zapytan rozpoczynajacych sie od select
private function _allowCache($sql,$bind) {
return preg_match("/^select/i",$sql));
}
private function _calculateHash($sql,$bind) {
return md5($sql.var_export($bind,true));
}
[...]
}
Ten kod omal nie zadziała. Jednak trzeba zrobić jeszcze parę myków. Przeciążyć konstruktor naszego adaptera.
public function __construct($config) {
parent::__construct($config);
$this->getConnection()->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('JakubiakDbStatement', array($this)));
}
I dopisać taką oto koszmarną klasę:
class JakubiakDbStatement extends PDOStatement {
$_cache = null;
public function fetchAll($how, $class_name=null, $ctor_args=null) {
if(!empty($this->_cache)) {
return $this->_cache;
}
$this->_cache = parent::fetchAll($how);
return $this->_cache;
}
}
Ten kod, ma prawo nie działać i zawiera błędy. Jednak u mnie działa. I to dobrze działa. Zamiast 500 zapytań do bazy mam ich 50. Serwer jest mi za to wdzięczny, nie trzeba już oliwić wentylatorka od chłodzenia;).
PS. do testów użyłem starej wersji Zend_Framework – 0.9.1.