Last Updated: February 25, 2016
·
1.835K
· tjuice

Simple generic caching using php magic __call method

Even if there are more and more scalable, high performance solutions for accessing and processing data, you have to implement small cachings from time to time.

This example shows an easy, generic implementation for a simple to add, non disruptive caching mechanism using the php __call method.

DISCLAIMER

The example was never productive and should only explain the idea. In this example memcache is used, but there is no direct dependency to memcache and the method could also be implemented with every other caching technology. The memcache in this code is shown as some Service Facade with a get() and a set() method, cause the example should not show, how to do a memcache implementation.

The Idea

The main idea is, that the result of a function can be stored with a key build from its attributes. This will of course not work for all situations. e.g if there are inner method dependencies which are not included in the attibutes list. (time, etc) but it will work in many cases.

e.g. If you have a UserService with a method getUser(id) you can easily inherit from this AbstractService and call cache_getUser(id) to get cached results.

The magic method __call() is used here like an interceptor which is triggered with a specific prefix (in this case "cache_" ) and dispatches between the original implementation and the caching result.
So the __call method will be triggered and take the id attribute and serialize it and build an MD5 together with the method name (in differend situations you have to add classname, method name and maybe the package name to prevent getting results from another context) This will be the key to identify the right result. now you can check the cache if there is a valid result. If not, just call the original method, store the result with the key in the cache and return the result.

One Problem is, that there is no cache invalidation, so after a result is stored in the cache, the method will only give you back the first stored result. To solve this problem, you can define some rules to invalidate outside., e.g. in memcache you can handle it to set a global expire time to declare how long cached results will be stored in the cache.

So thats the idea. Please let me know, how you like it.

here the code:

class AbstractService {
  public static $CACHEPREFIX = 'cache_';
  public function __call($methodName, $args) {
  //checks first if the methodname includes the right prefix
  if (strpos($methodName, AbstractService::$CACHEPREFIX) === 0) {

    // here we have to build the original Methodname
    $methodName = substr($methodName, strlen(AbstractService::$CACHEPREFIX));

    /*
     * We build the cacheHash from the methodName & the method arguments
     * Attention!! if you use this Impementation in different classes,
     * you have to add the classname too. 
     */
    $cacheHash = $this->buildCacheHash($methodName, $args);

    if (method_exists($this, $methodName)) {

      /*
       * here we use our $cacheHash to fetch the result from a
       * MemcacheServie
       */
      $cacheResult = MemcacheService::getInstance()->get($cacheHash);

      // check if there is an Result
      if ($cacheResult == true) {
        return $cacheResult;
      } else {

        /*
         * if there is no valid result, we have to call the original method
         * to get the result, write the result to our cache System and
         * return the result 
         */
        $result = call_user_func_array(array($this, $methodName), $args);
        MemcacheService::getInstance()->set($cacheHash, $result);

        return $result;
      }
    }
  }
  /*
   * the __call function will only be called, if no other methodname .
   * will match thats why we can throw an exception here. 
   */
  throw new Exception('Method ' . $methodName . ' not found');
  }

  /**
   *  method to build an hashkey for
   * @ param string $methodName Name of the called method
   * @ param array $args Arguments of the Method call
   * @ return string MD5 of the methodName and the serialized Arguments
   */
  public function buildCacheHash($methodName, $args) {
    return md5($methodName . serialize($args));
  }
}