Simple caching patterns for PHP
17 August 2011
Having built a few projects in Django I've sufficiently adjusted to working with a well developed framework and well designed programming language (Python) that there are several features I really miss when returning to PHP and using light weight framework Eurogamer is built on again.
One of those things was being able to use decorators to implement object/fragment/view caching on top of memcache which lets you build a very terse syntax for optimising you entire web application.
PHP doesn't have decorators and the accepted PHP decorator pattern is little better than subclassing in terms of convenience. The beauty of decorators is that they let component developers interact directly with caching with very little footprint, and in my experience, the easier and more transparent you make caching to developers, the more effectively it's deployed.
So I thought perhaps I could use PHP reflection API to fake decorators for the specific task of caching. The results of my labour are available as this EggCup PHP caching class on Github.
It's not a great deal of code, but I don't think it's an obvious solution to the problem. Usage is very simple (assuming memcache instance running on localhost):
In the component:
class MyExistingClass {
/**
* cache-me
* cache-expiry: 60
*/
public function getSomeDataFromDB( $arg1, $arg2 ) {
// take a long time to generate some data using $args1 and $args2
// as determining vars.
return $someData;
}
}
Invocation:
$cachedclass = new Eggcup(
new MyExistingClass(),
array( array( "host" => "127.0.0.1", "port" => "11216" ) )
);
// this return value will be cached for 60s
// first call will use DB
$cachedclass->getSomeDataFromDB( 1, 2 );
// second call will use memcache
$cachedclass->getSomeDataFromDB( 1, 2 );
// different args so will use DB again (cache key auto constructed from
// args)
$cachedclass->getSomeDataFromDB( 3, 4 );
sleep( 61 );
// will us DB again
$cachedclass->getSomeDataFromDB( 1, 2 );
Yeah that's right, the doc comments for the function control the behaviour of the cache for the method. A component developer can SUGGEST, via comments, how his code should be cached, with the person invoking the component having final say over whether the caching hints are honoured or not. It's a nice arrangement and makes reverse engineering a doddle (the cache info is there right in front of you).
Cache-invalidation is also supported, but memcache's poor support for lists makes it pretty clumsy. I'll be trying this out on a Redis backed cache very soon.
You may have some questions...
Does it break APC? Not sure. You might expect run time reflection to mess with PHP's byte code cache. I'll do some tests...
Is there a lot of overhead? There's a bit, but it's less than running a bunch of slow SQL queries. It's always cheaper to make your app servers do the work!
Does it work from any scope?
No. At object level scope it doesn't work because $this isn't wrapped by EggCup. i.e. you can't do $this->getSomeDataFromDB and expect it to be cached. This is generally not a problem because good caching should always be at the highest level you can afford.
Related tags: cache, caching, decorators, memcache, php