gabriele
mittica
.com

Hello, i'm a cloud and web developer.

BrennoCache: a smart cache engine for your php applications

written by Gabriele Mittica, Jul 16 2012

In computer science, a cache is a component that transparently stores data so that future requests for that data can be served faster. In the last years this system has become more and more appreciated by web developers who use caching systems to optimize the performance of their web applications. In PHP, some of the most common cache engine are APC, memcache, memcached...

Why BrennoCache?

BrennoCache is a small tool, part of the Brenno Library that I'm developing. Each tool in the library has a specified goal: in this case, provide a smart way to:
  - create a layer over various cache systems
  - integrate a tagging service in the cache handler

The last point is very important: usually php cache doesn't provide a way to store and fetch data by a common tag, that could be useful when you need to split up you data keeping a membership tag.

First, the interface

The consistency must be guaranteed over the handler chosen. In order to do this, we set a simple interface like this one:

<?php

interface BrennoCacheInterface {
    
    /**
     * constructor
     * @param array $options
     */               
    public function __construct($options = array());
    
    /**
     * store a value in the cache
     * @param string $key the identifier for the value
     * @param mixed $value the data to store in the cache
     * @param integer $expire the expire time (seconds)
     * @return bool
     */                                   
    public function store($key, $value, $expire = 0);
    
    /**
     * delete a value from the cache
     * @param string $key the identifier for the value
     * @return bool
     */    
    public function delete($key);
                        
    /**
     * get a value from the cache
     * @param string $key the identifier for the value
     * @return mixed the value store in the cache
     */    
    public function fetch($key);
                         
    /**
     * destroy the cache (delete all entries in the cache)
     * @return bool
     */    
    public function flush();

}

Then, the cache handler

Here the implementation of one of the most common open source framework in php: APC (Alternative PHP Cache):

<?php

class BrennoCacheApc implements BrennoCacheInterface {
    
    public function __construct($options = array()) {}
    
    public function store($key, $value, $expire = 0) {
        return apc_store($key, $value, $expire);
    }
    
    public function delete($key) {
        return apc_delete($key);
    }
    
    public function fetch($key) {
        return apc_fetch($key);
    }
    
    public function flush() {
        return apc_clear_cache();
    }    

} 

Finally, the BrennoCache Class

In less than 100 code lines (without comments), we provide a class that will be able to store and manage data and tags through the selected cache handler (APC, memcache or your own caching system). Take care to the first parameter of the constructor: you have to set a cache domain that will be used such as suffix for all your cache entries (so you'll be able to separate cache repositories of different webapps on the same cache engine).

Here the public methods of the class:

<?php
class BrennoCache {
    
    /**
     * the handler of the cache (must bn instance of a class that implements the CacheHandler interface)
     * @var bool
     */
    private $_handler;   
    
    /**
     * The domain used such as prefix for all cache entries
     * @var string
     */
    private $_domain;
    
    /** 
     * Constructor
     * @param string $cacheHandler the chache handle (apc, memcached)
     */                  
    public function __construct( $domain, $handler, $options = array() ) {
        if($domain) $this->_domain = $domain;
        else if(defined("APC_DOMAIN")) $this->_domain = APC_DOMAIN;
        $handlerClassName = "BrennoCache".ucfirst(strtolower($handler));
        if(!class_exists($handlerClassName) && file_exists($handlerClassName).".php") require_once $handlerClassName.".php";
        $this->_handler = new $handlerClassName($options);
    }
    
    /**
     * store a value in the cache
     * @param string $key the key name
     * @param mixed $value the value to store
     * @param integer $exprire the life time of the object store (in ms, 0 == no limit)               
     */         
    public function store($key, $value, $expire = 0) {
        return $this->_storeKey($key, $value, $expire);
    }
    
    /**
     * store a value in the cache by tag
     * @param string $key the key name
     * @param mixed $value the value to store     
     * @param string $tag the tag name
     * @param integer $exprire the life time of the object store (in ms, 0 == no limit)               
     */      
    public function storeByTag($key, $value, $tag, $expire = 0) {
        $result = $this->_storeKey($key, $value, $expire);
        if($result) $this->_updateTag($key, $tag);
        return $result;
    }
    /**
     * fetch a key from the cache   
     * @param string $key the key name
     * @param mixed $default the default value to return if key is not fetched
     * @return mixed the value stored or the $default value     
     *       
     */         
    public function fetch($key, $default = false) {
        return $this->_fetchKey($key, $default);
    }
    
     /**
     * fetch tagged keys from the cache   
     * @param string $key the key name
     * @param mixed $default the default value to return if key is not fetched
     * @return mixed the value stored or the $default value     
     *       
     */         
    public function fetchByTag($tag, $default = false) {
        $result = array();
        $keys = $this->_getKeysByTag($tag);
        array_pop($keys);
        foreach($keys AS $key) $result[$key] = $this->_fetchKey($key, $default);
        return $result;
    }
   
   /**
    * remove one or more keys from the cache
    * @param string|array $keys a key (or an array of keys) to remove
    * @return bool true if successeful, false instead        
    */       
    public function delete($keys) {     
        return $this->_deleteKey($keys);
    }
    
    /**
    * remove all keys with the same tag from the cache
    * @param string $tag the tag name
    * @return bool true if successeful, false instead        
    */     
    public function deleteTag($tag) {
        return $this->_deleteKey($tagList = $this->_getKeysByTag($tag));    
    }
   
    /**
    * flush the cace
    * @return bool true if successeful, false instead        
    */       
    public function deleteAll() {
        return $this->_handler->flush();    
    }
    

Then, a set of private methods:

<?php
  /**
    * private method to construct the key id used by cache system
    * @param string $key the key
    * @param string $suffix the suffix     
    * @return string the private key id       
    */     
    private function _getKeyId($key, $suffix = "") {
        return strtolower(implode("_", array($this->_domain, $key)).$suffix);           
    }
    
    /**
    * private method to construct the tag id used by cache system
    * @param string $tag the tag
    * @return string the private tag id       
    */     
    private function _getTagId($tag) {
        return $this->_getKeyId($tag, "#tag");  
    }
    
    /**
    * private method to fetch keys with the same tag
    * @param string $tag the tag
    * @return array the array of keys   
    */     
    private function _getKeysByTag($tag) {
        return array_merge($this->fetch($this->_getKeyId($tag, "#tag"), array()), array($this->_getTagId($tag))); 
    }
    
    /**
    * private method to update the list of keys linked to a tag
    * @param string $id the id of the key
    * @param string $tag the tag 
    */     
    private function _updateTag($id, $tag) {
        $tagId = $this->_getTagId($tag);
        $tagList = $this->fetch($tagId, array());
        if(!in_array($id, $tagList)) $tagList[] = $id;
        $this->store($tagId, $tagList);
    }
    
    /**
    * private method to remove one or more keys from the cache
    * @param string|array $keys the key or an array of keys
    * @return bool true if successeful, false instead        
    */     
    private function _deleteKey($keys) {  
        $result = array(); 
        if(is_array($keys)) {
            foreach($keys AS $key) $result[$key] = $this->_handler->delete($this->_getKeyId($key));
        }
        return $result;
    }

    /**
    * private method to store a value in the cache
    * @param string $key the key name
    * @param integer $exprire the life time of the object store (in ms, 0 == no limit)      
    * @return the handler result        
    */     
    private function _storeKey($key, $value, $expire = 0) {
        return $this->_handler->store($this->_getKeyId($key), serialize($value), $expire);
    }

    /**
    * private method to fache a value from the cache 
     * @param string $key the key name
     * @param mixed $default the default value to return if key is not fetched
     * @return mixed the value stored or the $default value     
     *       
     */         
    private function _fetchKey($key, $default = false) {
        $result = unserialize($this->_handler->fetch($this->_getKeyId($key)));
        if(!$result) return $default;
        else return $result;
    }

Examples

BrennoCache is very simple to use. You can easily manage your cache increasing the performance of your web application.

In the first example, we'll store and fetch a simple data:

<?php

//require BrennoCache
require_once("/../BrennoCache.php");

//create the cache manager with APC
$cache =  new BrennoCache("myapp", "apc");

//storing values
$cache->store("key1", "data1");
$cache->store(
    "batman", 
    array(
        "name" => "Bruce Wayne",
        "enemies" => array("Joker", "Two Face", "Bane", "Hush", "Mr. Freeze")
    )
);  


$result = $cache->fetch("batman");
var_dump($result);
   

The code printed by the var_dump

array
  'name' => string 'Bruce Wayne' (length=11)
  'enemies' => 
    array
      0 => string 'Joker' (length=5)
      1 => string 'Two Face' (length=8)
      2 => string 'Bane' (length=4)
      3 => string 'Hush' (length=4)
      4 => string 'Mr. Freeze' (length=10) 

In the second example, we'll store, fetch and destroy tagged data:

//require BrennoCache
require_once("/../BrennoCache.php");


//create the cache manager with APC
$cache =  new BrennoCache("myapp", "apc");

//storing tagged data
$cache->storeByTag(
    "batman", //the key 
    array(
        "name" => "Bruce Wayne",
        "enemies" => array("Joker", "Two Face", "Bane", "Hush", "Mr. Freeze")
    ),
    "superheroes" //the tag
);
$cache->storeByTag(
    "spiderman", //the key 
    array(
        "name" => "Peter Parker",
        "enemies" => array("Carnage", "Hobgoblin", "Kingpin", "Venom", "Goblin")
    ),
    "superheroes" //the tag
);  

//fetching all keys stored with superheroes tag
$result = $cache->fetchByTag("superheroes");
var_dump($result); 

//deleting tag
$cache->deleteTag("superheroes");
$result = $cache->fetchByTag("superheroes");
var_dump($result); 

The code printed by the two var_dump calls (before and after the delete command)

array
  'batman' => 
    array
      'name' => string 'Bruce Wayne' (length=11)
      'enemies' => 
        array
          0 => string 'Joker' (length=5)
          1 => string 'Two Face' (length=8)
          2 => string 'Bane' (length=4)
          3 => string 'Hush' (length=4)
          4 => string 'Mr. Freeze' (length=10)
  'spiderman' => 
    array
      'name' => string 'Peter Parker' (length=12)
      'enemies' => 
        array
          0 => string 'Carnage' (length=7)
          1 => string 'Hobgoblin' (length=9)
          2 => string 'Kingpin' (length=7)
          3 => string 'Venom' (length=5)
          4 => string 'Goblin' (length=6)
array
  empty  

The tag system can help you to group and list your data in the cache. A cool feature!

About the Author

Gabriele Mittica
Gabriele Mittica I'm a 28 years old web developer with a long experience on dynamic websites. I worked over 7 years on content management systems designing. Now I'm focused on AWS integration and cloud development. In early 2012 I opened a new cloudy startup:Corley.

Post a comment

blog comments powered by Disqus

Articles from blog and friends!

Keep in touch!

If you want ask a question, share a content, report a bug or simply contact me you can find me on Facebook, Twitter and obviously on Linkedin!

Cloud and LAMP applications: how to scale

published May 13 2013

The Slides from my speech at the first italian Cloud Conference in last April. An introduction to lamp apps and scalability: the issues and the related solutions, how to scale the app and which services use to do it in the best way. [PHP, Cloud, AWS, Cache]

Cloud Conference and Cloud Training

published Apr 12 2013

I'm happy to introduce the Italian Cloud conference in Turin next 18th April, where a lot of speakers (also me) from great companies such as AWS, Trigger.io, Zend Technolgies, Corley, Read Hat and NuvolaBase will speak about cloud computing and scalability. [Cloud, AWS, startup]

Mongo ad PHP: review

published Feb 12 2013

My review of the manual MongoDB and PHP, an interesting book 8maybe too much short?) that explain quickly how to use a very populare nosql database like mongoDB with no troubles. [PHP, Cloud, Nosql]

WordPress integration with AWS - Part 2

published Feb 11 2013

From the Wordcamp Bologna Conference where i was such as relator, the second version of slide about integration of WordPress with cloud services and tutorial about scalability of PHP applications. [Cloud, AWS, WordPress]

My interview for DoesWhat.com

published Jan 29 2013

The interview published by DoesWhat.com where I talked about startups, cloud computing, entrepreneur life and passion that I put into my work every day. Hoping not find it too boring! [Cloud, startup]

GabrieleMittica.com
Web & Cloud Developer
© 2017 Gabriele Mittica
 
Clicca per i dettagli