APC Wrapper Class

19 December, 2007 (06:33) | PHP

Recently i’ve been working with some simple Alternative PHP Cache (APC) caching as part of the projects i’m working on, and i’ve come up with this simple wrapper script which allows me to run my framework on any installation of PHP5 weather it has APC enabled or not, and I don’t have to make configuration changes to do so.

More after the jump…

This class is written in a way that allows easy modification of the code to change to another caching engine if you so choose, but this could be extended by abstracting the fetch, store and delete methods and adding a Factory pattern to read a config file and load the correct caching object (well, it sounded simpler in my head.. ).

Here’s the code, CacheHandler.class.php

  1. /**
  2.  * CacheHandler.class.php
  3.  *
  4.  * @author James McLean August, 2007.
  5.  *
  6.  */
  7.  
  8. class CacheHandler {
  9.  
  10.     private $use_cache = TRUE;
  11.  
  12.     public function __construct() {
  13.  
  14.         $methods[] = ‘apc_fetch’;
  15.         $methods[] = ‘apc_store’;
  16.         $methods[] = ‘apc_delete’;
  17.  
  18.         for($i = 0; $i < sizeof($methods); $i++) {
  19.             if(!function_exists($methods[$i])) {
  20.                 $this->use_cache = FALSE;
  21.             }
  22.         }
  23.  
  24.     }   
  25.  
  26.     function fetch($key = NULL) {
  27.  
  28.         if($key === NULL) {
  29.             throw new Exception(‘Key Cannot be null!’);
  30.         }
  31.  
  32.         if($this->use_cache === FALSE) {
  33.             return false;
  34.         }
  35.  
  36.         $data = apc_fetch($key);
  37.  
  38.         if($data instanceof ArrayObject) {
  39.             return $data->getArrayCopy();
  40.         } else {
  41.             return $data;
  42.         }
  43.     }
  44.  
  45.     function store($key = NULL,$data = NULL,$ttl = 300) {
  46.  
  47.         if($key === NULL) {
  48.             throw new Exception(‘Key Cannot be null!’);
  49.         }
  50.  
  51.         if($data === NULL) {
  52.             throw new Exception(‘Data Cannot be null!’);
  53.         }        
  54.  
  55.         if($this->use_cache === FALSE) {
  56.             return false;
  57.         }
  58.  
  59.         if(is_array($data)) {
  60.             return apc_store($key,new ArrayObject($data),$ttl);
  61.         } else {
  62.             return apc_store($key,$data,$ttl);
  63.         }
  64.  
  65.     }
  66.  
  67.     function delete($key = NULL) {
  68.  
  69.         if($key === NULL) {
  70.             throw new Exception(‘Key Cannot be null!’);
  71.         }        
  72.  
  73.         if($this->use_cache === FALSE) {
  74.             return false;
  75.         }        
  76.  
  77.         return apc_delete($key);
  78.     }    
  79.  
  80. }

Here’s a usage example from BaseQuery.class.php class (not public code, yet) of my ORM, it’s only a small portion of the code - it’s just to demonstrate the usage of the APC wrapper class above.

  1. // get the SQL
  2. $sql = $this->getSelectSql();
  3.  
  4. // do the actual query
  5. $queryData = $this->dbConn->query($sql);
  6.  
  7. // give the cache key a useful name that’s easily re-generated for
  8. // future cache-queries
  9. $class = get_class($this);
  10. $cacheKey = $class . ‘-select-’ . md5($sql);
  11. $cacheHandler = new CacheHandler();
  12.  
  13. $returnData = $cacheHandler->fetch($cacheKey);
  14.  
  15. if($returnData === FALSE) {
  16.     // data not found in the cache, get it from the database.
  17.  
  18.     $row = $queryData->fetchObject($class);
  19.  
  20.     // null the (now useless) dbConnection, it cant be saved into the APC cache
  21.     $row->dbConn = NULL;
  22.     $returnData[] = $row;
  23.  
  24.     // dont cache sessions
  25.     if(!$row instanceof SessionQuery) {
  26.         $cacheHandler->store($cacheKey, $returnData);
  27.     }
  28. }
  29.  
  30. return $returnData;

In hindsight I may be caching at the wrong point, it seems PDO would have already queried the database at the ->query($sql); point in the code - perhaps I should be caching that resultset instead of the object PDO gives back from ->fetchObject()…

Anyway, hopefully someone gets some use out of this code! Let me know if you do, or if you find any bugs or have comments or suggestions.

Cheers!

Comments

Comment from Richard Harrison
Date: December 19, 2007, 8:02 am

You might get some further inspiration from the Zend_Cache module - it does what you’re talking about with multiple backends for different cache stores (file, APC, ZendPlatform etc).

http://framework.zend.com/manual/en/zend.cache.html

The “frontend” options are pretty interesting too.

Comment from David Coallier
Date: December 19, 2007, 10:20 am

Hrm.. that’s bad, your cache handler is completely dependant of APC, bad architecture.

Next, why do you have an array of methods in the constructor ? That’s quite ugly.

If you can’t have null values in the store method why do you default them as null (store/fetch/delete)? Heard of is_null() ? :)

function_exists() is slow don’t overuse it…

The if ($data instanceof ArrayObject) handling shouldn’t be done there… this method is made to fetch a cached value, not find it’s type.

$0.02

Comment from Dave Marshall
Date: December 19, 2007, 6:10 pm

Like the Zend_Cache module, my own cache library has a choice of backends. For just a little code, you could make a filesystem failover if APC is not available.

Comment from Adam
Date: January 9, 2008, 11:02 pm

I concur. The Zend_Cache module is truly awesome.