Last days I’m working making many changes in Open Classifieds and one of them is this new class to handle the cache.

Few days a go I wrote about a cache class and longer a go about application variables for php. This is a mix of both in just one powerful class.

Explanation:

In this class we have 2 different kinds of cache.

First the normal file cache, where we store values in a single file. This is good for example to cache an entire page.

Second we have an APPLICATION kind integrated in the cache. This means that whatever you store in the APP cache it would be kept in the same file as all the other APP. Really useful to store small amount of data, for example menus, counters etc… Remember that this file is loaded everytime yo create a new object fileCache, you need to be careful to not store many things on it.

The latest version of the class chooses which is the best option of storage engine, based on the size of the variable. Then for example an entire page HTML will be in a single file, and a number or a person name would be in the APP cache.

Usage:

 
$cache = fileCache::GetInstance(30,'cache/');//creating new instance singleton
 
$cache->setDebug(true);//this runs the debug and in destruct prints it
 
//normal usage
$cache->cache('test','test values for the var!!!!');
echo $cache->cache('test');//get
 
//overloads
$cache->anyvar='tes asd asd aasdasdasda sada ast';//overload of set and get methods
echo $cache->anyvar;//get overload
var_dump(isset($cache->anyvar));//isset and unset are overloaded
unset($cache->anyvar);
 
//clear
$cache->deleteCache();//deletes all the cache
$cache->deleteCache(60);//deletes any cache older than X seconds

Updated 17 October 2010
The Class: (Download)

<?php
/*
 * Name:	fileCache
 * URL:		http://neo22s.com/filecache/
 * Version:	v1.1
 * Date:	21/10/2010
 * Author:	Chema Garrido
 * License: GPL v3
 * Notes:	fileCache class, caches variables in standalone files if value is too long or uses unique file for small ones
 */
 
/////////////////////class file cache
 
class fileCache {
	private $cache_path;//path for the cache
	private $cache_expire;//seconds that the cache expires
	private $application=array();//application object like in ASP
 	private $application_file;//file for the application object
 	private $application_write=false;//if application write is true means there was changes and we need to write the app file
 	private $debug=false; //no debug by default
	private $log=array();//log for the debug system
	private $start_time=0;//application start time
 	private static $content_size=64;//this is the max size can be used in APP cache if bigger writes independent file
	private static $instance;//Instance of this class
 
    // Always returns only one instance
    public static function GetInstance($exp_time=3600,$path='cache/'){
        if (!isset(self::$instance)){//doesn't exists the isntance
        	 self::$instance = new self($exp_time,$path);//goes to the constructor
        }
        return self::$instance;
    }
 
	//cache constructor, optional expiring time and cache path
	private function __construct($exp_time,$path){
	    $this->start_time=microtime(true);//time starts
		$this->cache_expire=$exp_time;
		if ( ! is_writable($path) ) trigger_error('Path not writable:'.$path);
		else $this->cache_path=$path;
		$this->APP_start();//starting application cache
	}
 
	public function __destruct() {
    	$this->addLog('destruct');
		$this->APP_write();//on destruct we write if needed
		$this->returnDebug();
	}
 
	// Prevent users to clone the instance
    public function __clone(){
        trigger_error('Clone is not allowed.', E_USER_ERROR);
    }
 
	//deletes cache from folder
	public function deleteCache($older_than=''){
	    $this->addLog('delete cache');
		if (!is_numeric($older_than)) $older_than=$this->cache_expire;
 
		$files = scandir($this->cache_path); 
		foreach($files as $file){
			if (strlen($file)>2 && time() > (filemtime($this->cache_path.$file) + $older_than) ) {
				unlink($this->cache_path.$file);//echo "<br />-".$file; 
				$this->addLog('delete cache file:'.$this->cache_path.$file);
			}
		}
 
	}
 
	//writes or reads the cache
	public function cache($key, $value=''){
		if ($value!=''){//wants to write
			if (strlen(serialize($value)) > 64 ){//write independent file it's a big result
			    $this->addLog('cache function write in file key:'. $key);
				$this->put($key, $value);
			}
			else {
			    $this->addLog('cache function write in APP key:'. $key);
			    $this->APP($key,$value);//write in the APP cache
			}
		}
		else{//reading value
			if ( $this->APP($key)!=null ){
			    $this->addLog('cache function read APP key:'. $key);
			    return $this->APP($key);//returns from app cache
			}
			else {
			    $this->addLog('cache function read file key:'. $key);
			    return $this->get($key);//returns from file cache
			}
		}
	}
 
	//deletes a key from cache
	public function delete($name){
		if ( $this->APP($name)!=null ){//unset the APP var
    	    $this->addLog('unset APP key:'. $name);
    		unset($this->application[md5($name)]);
        	$this->application_write=true;//says that we have changes to later save the APP
    	}
		elseif ( file_exists($this->fileName($name)) ){//unlink filename
		    $this->addLog('unset File key:'. $name);
			unlink($this->fileName($name));			
		}
	}
 
	// Overloading for the variables and automatically cached
	 	public function __set($name, $value) {
	 		$this->cache($name, $value);
	    }
 
	    public function __get($name) {
	        return $this->cache($name);
	    }
 
	    public function __isset($name) {//echo "Is '$name' set?\n"
	        $this->addLog('isset key:'. $name);
	    	$value=$this->cache($name);
	        return isset($value);
	    }
 
	    public function __unset($name) {//echo "Unsetting '$name'\n";
	    	$this->delete($name);
	    }
	//end overloads
 
	//////////Cache for files individually///////////////////
 
		//creates new cache files with the given data, $key== name of the cache, data the info/values to store
		private function put($key, $data){
			if ( $this->get($key)!= $data ){//only write if it's different
				$values = serialize($data);
				$filename = $this->fileName($key);
				$file = fopen($filename, 'w');
			    if ($file){//able to create the file
			        $this->addLog('writting key: '.$key.' file: '.$filename);
			        fwrite($file, $values);
			        fclose($file);
			    }
			    else  $this->addLog('unable to write key: '.$key.' file: '.$filename);
			}//end if different
		}
 
		//returns cache for the given key
		private function get($key){
			$filename = $this->fileName($key);
			if (!file_exists($filename) || !is_readable($filename)){//can't read the cache
			    $this->addLog('can\'t read key: '.$key.' file: '.$filename);
				return null;
			}
 
			if ( time() < (filemtime($filename) + $this->cache_expire) ) {//cache for the key not expired
				$file = fopen($filename, 'r');// read data file
		        if ($file){//able to open the file
		            $data = fread($file, filesize($filename));
		            fclose($file);
		            $this->addLog('reading key: '.$key.' file: '.$filename);
		            return unserialize($data);//return the values
		        }
		        else{
		            $this->addLog('unable to read key: '.$key.' file: '.$filename);
		            return null;
		        }
			}
			else{
			    $this->addLog('expired key: '.$key.' file: '.$filename);
			    unlink($filename);	
			    return null;//was expired you need to create new
			}
	 	}
 
	 	//returns the filename for the cache
		private function fileName($key){
			return $this->cache_path.md5($key);
		}
 	//////////END Cache for files individually///////////////////
 
	//////////Cache for APP variables///////////////////
 
	 	//load variables from the file
		private function APP_start ($app_file='application'){
			$this->application_file=$app_file;		
 
		    if (file_exists($this->cache_path.$this->application_file)){ // if data file exists, load the cached variables
		        //erase the cache every X minutes
			    $app_time=filemtime($this->cache_path.$this->application_file)+$this->cache_expire;
			    if (time()>$app_time){
			        $this->addLog('deleting APP file: '.$this->cache_path.$this->application_file);
			        unlink ($this->cache_path.$this->application_file);//erase the cache
			    }
			    else{//not expired
			        $filesize=filesize($this->cache_path.$this->application_file);
	                if ($filesize>0){
	                    $file = fopen($this->cache_path.$this->application_file, 'r');// read data file
	                    if ($file){
	                        $this->addLog('reading APP file: '.$this->cache_path.$this->application_file);
	                        $data = fread($file, $filesize);
	                        fclose($file);
        		            $this->application = unserialize($data);// build application variables from data file
	                    }//en if file could open
	                }//end if file size
 
			    }     
	        }
	        else  {//if the file does not exist we create it
	            $this->addLog('creating APP file: '.$this->cache_path.$this->application_file);
	            fopen($this->cache_path.$this->application_file, 'w');
	        }
 
		}
 
		// write application data to file
		private function APP_write(){
			if ($this->application_write){
			    $data = serialize($this->application);
			    $file = fopen($this->cache_path.$this->application_file, 'w');
			    if ($file){
			        $this->addLog('writting APP file: '.$this->cache_path.$this->application_file);
			        fwrite($file, $data);
			        fclose($file);
			    }
			}
		}
 
		//returns the value form APP cache or stores it
		private function APP($var,$value=''){
			if ($value!=''){//wants to write
 
				if (is_array($this->application)){
				    if ( array_key_exists(md5($var), $this->application) ){//exist the value in the APP
					    $write=false;//we don't need to wirte
					    if ($this->application[md5($var)]!=$value)$write=true;//but exists and is different then we write
				    }
				    else $write=true;//not set we write!
				}
				else $write=false;
 
				if ($write){
				    $this->addLog('writting APP key:'.$var);
					$this->application[md5($var)]=$value;
					$this->application_write=true;//says that we have changes to later save the APP
				}
			}
			else {//reading	
				if ( !is_array($this->application) || ! array_key_exists(md5($var), $this->application) ){
				    $this->addLog('nothing found for APP key:'.$var);
				    return null;//nothing found not in array
				}
				else{
                    $this->addLog('reading APP key:'.$var);				
				    return $this->application[md5($var)];//return value
				}
			}
		}
    //////////End Cache for APP variables///////////////////
 
    ////DEBUG
    //sets debug on or off
		public function setDebug($state){
			$this->debug=(bool) $state;
		}
 
		public function returnDebug($type='HTML'){
			if ($this->debug){
				switch($type){
					case 'array':
						return $this->log;
					break;
					case 'HTML'://returns debug as HTML
						echo '<ol>';
						foreach($this->log as $key=>$value){//loop in the log var
							echo '<li>'.$value.'</li>';
						}
						echo '</ol>';	
					break;
				}	
			}
			else return false;	
		}
 
		//add debug log
		public function addLog($value){
			if ($this->debug){//only if debug enabled
				array_push($this->log, round((microtime(true) - $this->start_time),5).'s - '. $value);  
			}
		}
}
?>