AdodbCache
From PRADO Wiki
Note: This article assumes that you already have the Adodb module!
Now that you have your Adodb module set up and working, you'll want to increase your application performance with a TCache implementation. You could use TDbCache, but this will require you to create a new database connection (as opposed to the Adodb database connection you already have) via PDO. Enter AdodbCache.
Adodb extends from TCache and works very similarly to TDbCache, only it piggybacks on top of your Adodb module for it's database connectivity.
Using AdodbCache has significantly improved several of my applications performance, especially when running in Performance mode.
It's pretty easy to get set up. Only three steps are required:
1. Create a table in your database to hold cache information. 2. Copy the module/class to your project. 3. Configure it within application.xml.
Contents |
Step 1: Create a cache table
At the time of writing I have used AdodbCache on MySQL and Oracle. Below are the SQL statements I used for each. Just about any database should work with minor tweaks. Basically you need 3 columns: itemkey (char PK), value (BLOB), expire (int).
You can change the table name to something else if you'd like.
--MySQL CREATE TABLE pradocache ( itemkey varchar(128) NOT NULL DEFAULT '', value longblob, expire int(11) DEFAULT NULL, PRIMARY KEY (itemkey) ) TYPE=InnoDB; --Oracle CREATE TABLE PRADOCACHE( itemkey CHAR(128) NOT NULL, value CLOB NOT NULL, expire INTEGER NOT NULL ); ALTER TABLE PRADOCACHE ADD ( CONSTRAINT PK_PRADOCACHE PRIMARY KEY (itemkey) );
Step 2: Copy AdodbCache to your application directory
In my application I have a "modules" directory under my "protected" directory. For this article, we'll place the following file there (your Adodb module will likely be in the same spot).
AdodbCache.php
<?php /** * AdodbCache class * * Emulates TDbCache, but uses an existing Adodb module instead of PDO. * Adodb Module: http://www.pradosoft.com/wiki/index.php/ADOdb_module_tutorial * * AdodbCache work very similarly to TDbCache. The most important difference is that * it WILL NOT automatically create your cache database. You must create it manually * in the following format (only tested against MySQL and Oracle): * * --MySQL * CREATE TABLE pradocache ( * itemkey varchar(128) NOT NULL default '', * value longblob, * expire int(11) default NULL, * PRIMARY KEY (itemkey) * ) TYPE=InnoDB; * * --Oracle * CREATE TABLE PRADO_CACHE( * itemkey CHAR(128) NOT NULL, * value CLOB, * expire INTEGER NOT NULL * ); * ALTER TABLE PRADO_CACHE ADD ( * CONSTRAINT PK_PRADO_CACHE * PRIMARY KEY * (itemkey) * ); * * Note: No SQL Binding is used purposefully. Oracle seems to have problems with it. * * @author Bart Lewis <bartlewis@gmail.com> */ class AdodbCache extends TCache{ /** * @var Adodb module instance */ private $_db = null; /** * @var string name of the DB cache table */ private $_cacheTableName = 'pradocache'; /** * @var string id of Adodb module */ private $_adodbModuleId = null; /** * @var bool is Adodb Driver Oracle based */ private $_isOracleDb = false; /** * @var bool Is the Prado version greater than 3.1.1? */ private $_isPradoVersionGreaterThan311; /** * Initializes this module. * Verifies parameters are set correctly and cachec table exists. * * @param TXmlElement configuration for this module, can be null * @throws TConfigurationException if AdodbModuleId is invalid, * or cache table specified by CacheTableName does not exist. */ public function init($config){ if (!$this->AdodbModuleId){ throw new TConfigurationException(__CLASS__.' missing required param: AdodbModuleId'); } if (!$this->db instanceof Adodb){ throw new TConfigurationException(__CLASS__.' param AdodbModuleId is not a valid Adodb module.'); } if ($this->getApplication()->getMode()==TApplicationMode::Debug){ $tables = $this->db->MetaTables('TABLES'); if (array_search($this->CacheTableName, $tables) === false) { throw new TConfigurationException( __CLASS__.' cannot find DB table "'.$this->CacheTableName.'" to store cached data.' ); } } $this->_isOracleDb = ($this->db->Driver=='oci8' || $this->db->Driver=='oci8po'); $this->_isPradoVersionGreaterThan311 = version_compare(Prado::getVersion(), '3.1.1', '>'); //Perform garbage collection 1 out of every 100 times. if (rand(1,100)==100) $this->db->Execute('DELETE FROM '.$this->CacheTableName.' WHERE expire<>0 AND expire<'.time()); parent::init($config); } /** * @return Adodb the DB connection instance */ protected function getdb(){ if ($this->_db==null){ $this->_db = $this->getApplication()->getModule($this->AdodbModuleId); } return $this->_db; } /** * Wrapper for ADODB->qstr() * * @param string * @return string quoted for SQL */ private function qstr($value){ return $this->db->qstr($value, get_magic_quotes_gpc()); } /** * @return string the name of the DB table to store cache content. Defaults to 'pradocache'. */ public function getCacheTableName(){ return $this->_cacheTableName; } /** * @param string the name of the DB table to store cache content */ public function setCacheTableName($value){ $this->_cacheTableName = TPropertyValue::ensureString($value); } /** * @return string the id of the Adodb module to use */ public function getAdodbModuleId(){ return $this->_adodbModuleId; } /** * @param string the id of the Adodb module to use */ public function setAdodbModuleId($value){ $this->_adodbModuleId = TPropertyValue::ensureString($value); } /** * Retrieves a value from cache with a specified key. * This is the implementation of the method declared in the parent class. * @param string a unique key identifying the cached value * @return string the value stored in cache, false if the value is not in the cache or expired. */ protected function getValue($key){ $sql = 'SELECT value FROM '.$this->CacheTableName.' WHERE itemkey='.$this->qstr($key).' AND (expire=0 OR expire>'.time().')'; $value = $this->db->GetOne($sql); if (!$value) $value = false; return $value; } /** * Stores a value identified by a key in cache. * This is the implementation of the method declared in the parent class. * * @param string the key identifying the value to be cached * @param string the value to be cached * @param integer the number of seconds in which the cached value will expire. 0 means never expire. * @return boolean true if the value is successfully stored into cache, false otherwise */ protected function setValue($key, $value, $expire){ $this->deleteValue($key); return $this->addValue($key, $value, $expire); } /** * Stores a value identified by a key into cache if the cache does not contain this key. * This is the implementation of the method declared in the parent class. * * Note: In Prado 3.1.2 this implementation stopped working. This was because Prado changed * the way "addValue" works within TDbCache in revision 2337. So, we added the same * change (time()+$expire) here. * @link http://trac.pradosoft.com/prado/changeset/2337 * * @param string the key identifying the value to be cached * @param string the value to be cached * @param integer the number of seconds in which the cached value will expire. 0 means never expire. * @return boolean true if the value is successfully stored into cache, false otherwise */ protected function addValue($key, $value, $expire){ if ($this->_isPradoVersionGreaterThan311) $expire = ($expire<=0) ? 0 : time()+$expire; $sql = 'INSERT INTO '.$this->CacheTableName.' (itemkey, value, expire) '; if ($this->_isOracleDb) { $sql .= 'VALUES('.$this->qstr($key).', empty_clob(), '.$expire.')'; } else{ $sql .= 'VALUES('.$this->qstr($key).', null, '.$expire.')'; } try{ $this->db->Execute($sql); $this->db->UpdateClob($this->CacheTableName, 'value', $value, 'itemkey='.$this->qstr($key)); return true; } catch(Exception $e){ return false; } } /** * Deletes a value with the specified key from cache * This is the implementation of the method declared in the parent class. * @param string the key of the value to be deleted * @return boolean if no error happens during deletion */ protected function deleteValue($key){ $sql = 'DELETE FROM '.$this->CacheTableName.' WHERE itemkey = '.$this->qstr($key); $this->db->Execute($sql); return true; } /** * Deletes all values from cache. * Be careful of performing this operation if the cache is shared by multiple applications. */ public function flush(){ $this->db->Execute('DELETE FROM '.$this->CacheTableName); return true; } /** * Ensure we actually have some value to store before storing it. */ public function set($id, $value, $expire=0, $dependency=null){ if (empty($value)) return false; else return parent::set($id, $value, $expire, $dependency); } /** * Ensure we actually have some value to store before storing it. */ public function add($id, $value, $expire=0, $dependency=null){ if (empty($value)) return false; else return parent::add($id, $value, $expire, $dependency); } } ?>
Step 3: Configure in application.xml
First, ensure that you have a namespace pointed to "Application.modules.*". Next, add a module tag with an id of "Cache" and a class of "AdodbCache". If you changed your cache table name (Step 1) to something other than "pradocache", you need to specify that new name under the CacheTableName property. If you are using the default table name, "pradocache", this attribute can be omitted. Finally, add the AdodbModuleId attribute and give it a value that matches the ID of your Adodb Module tag. This is the module that AdodbCache will use for it's database interactivity.
<?xml version="1.0" encoding="iso-8859-1"?> <application id="myAppId" Mode="Debug"> <paths> <using namespace="Application.modules.*" /> ... </paths> <modules> <module id="MyAdodbModule" class="adodb" Driver="mysql" Host="localhost" Username="usernamehere" Password="passwordhere" Database="databasenamehere" /> <module id="MyCache" class="AdodbCache" CacheTableName="pradocache" AdodbModuleId="MyAdodbModule" /> </modules> ... <services> <service id="page" class="TPageService"> <pages StatePersisterClass="System.Web.UI.TCachePageStatePersister" StatePersister.CacheModuleID="MyCache" StatePersister.CacheTimeout="3600" /> </service> ... </services> ... </application>
More speed!
At this point you've done a lot and you'll see some great benefits. But you can get even better performance by adding a StatePersisterClass to your page service. Here's how to to: Look at the above XML and take note of the pages tag. Details on this can be found here.
The important bit to note here though is that we want the StatePersister.CacheModuleID to point to our AdodbCache. Simply enter in the ID you used in your AdodbCache module tag and your done!
Thats it
You should now start noticing a significant performance boost in your application. Especially when running your application in Performance mode.
Originally written by Fragmaster B.
Please feel free to contact me with any comments/questions.

