The last few weeks I've been poking around the internet  looking for some good information on creating a caching system using PHP.  I found that this little niche of PHP knowledge is either still in the dark ages or isn't something people have felt the need to blog about.  I'm guessing it's a bit of both.  Caching isn't really that sexy as far as programming goes.  It's fun to say "our software does extensive performance caching" in tech specs but the actual creation of that system often puts developers off as it requires a bit of forethought and planning to make a good one.  I recently undertook this task and thought I might share my experience.

First, I really wanted to have a system that didn't rely on one storage medium, didn't have to use one monolithic caching policy, and could cache just about anything using a common interface.  That way I could cache a whole page, an html fragment, a serialized object or a database result.  I could also put it where I wanted, be that the file system, the database, or to a web service.  This flexibility isn't truly necessary for any particular project, but becomes really nice when you have to change up what you cache frequently.  Let's take a quick look at what I came up with.

Creating Interfaces

First I broke the system up into three components.

  1. Cache Entity - This is the actual item that is slated to be cached.
  2. Cache Policy - This helps write a class that in turn defines rules for when a cache entity is valid and when it's ready to be discarded.
  3. Cacheable - This interface is applied to an item that can be cached.  It may or may not also be a cache entity.  The interface is designed to turn already implemented caching defined further up the inheritance chain using the instanceof keyword. Technically isn't necessary, but I liked the idea since it allows me to be very targeted in what I do and don't cache.

Implementation

After the interfaces are set up, we just have to implement our big ideas.  Here's a fun example of how that might go.

The Cache Entity

This class, derived from an interface, helps us define two things.  First it defines what is being cached.  Second, it defines where that item is being cached.  The later is hidden and should be opaque to the client programmer using this class.  The should never have to worry about if it's cached in the file system or in the database.

class StudentEntity implements CacheEntityInterface { public function SetCachePolicy($instanceOfACachePolicy) { // Set the cache policy that will be used to validate this entity }

public function CheckCache() { // Use the cache policy we set above to check to see if this // cache object is still good. } public function RenewCache() { // Renew the cache as if it were brand new. } public function InvalidateCache() { // Throw the cache away. } public function LoadCache($somethingThatTellsUsWhereToFindTheCacheEntity) { // Code that fetches us the cache Entity } public function GetCache() { // return the object that we've cached. } public function SetCache($objectToBeCached) { // Set up the object to be cached. } }

The Cache Policy

This class defines the rules for when a cache entity is ready to be used or ready to be thrown out in favor of re-creating the item.  For the most part I didn't see the need to create an xml based schema to hold these rules as it would mean overhead.  In something like Java or C# that might make sense.  Here just a simple PHP class is the simplest and most efficient method.

class StudentCachePolicy implements CachePolicyInterface{ public function ValidateCache($cacheEnity) { // either call protected/private methods or // just check to see if the cached object is // usable or it it's to old, corrupted, etc. }}

The Actual Cachable Objects

The cacheable interface really doesn't have to have any associated methods.  I put some in there because I wanted to constrain how something was cached a bit further.  You really don't need to.  Let's look at how the interface might be used.

class CheerLeader extends Student implements Cacheable{ // Because if you've seen one you've seen 'em all.}

class PreppyKid extends Student implements Cacheable{ // Because you can't be a prep if you are different.}

class Nerd extends Student{ // He may be a student, but he's one of a kind, don't cache this one.}

class Student{ function __construct($student, $data) { if($this instanceof Cachable) { // define this object based on the cache } }}

Since (in my humble opinion) all cheerleaders and preps are the same, we will cache them and not go through the trouble of creating a new one. We'll just used the cached version since they have the "Cachable" interface.  If it does not have that interface, we don't cache it.  This allows us to cache things that will almost never change and not waste any time when we already know we'll need to create an object from scratch.

Conlusion

While this is pretty skeletal, it should provide a nice starting point to building a system that can cache most anything and store it most anywhere.  With this kind of granularity the programmer can be very picky about what we are caching an what we are not.  The end result is a caching system that can serve all the systems within your application rather than just page renders or database results.

Happy caching!