| 
<?php
 namespace JLaso\ToolsLib;
 
 class PreEmptiveCache
 {
 const OLDEST_MODE = 1;      // remove from cache first the oldest item
 const LESS_MODE = 2;        // remove from cache first the less accessed item
 const LESS_OLDEST_MODE = 3; // remove from cache first the oldest item from the less accessed items
 
 /** @var array  */
 protected $items = array();
 /** @var array  */
 protected $metaInfo = array();
 /** @var  \Closure */
 protected $callable;
 /** @var  int */
 protected $mode;
 /** @var int  */
 protected $maxRecordsCached;
 /** @var  bool */
 protected $debug;
 /** @var int  */
 protected $baseTime = 0;
 
 /**
 * @param \Closure $callable
 * @param array $options
 */
 public function __construct($callable, $options = array())
 {
 $this->callable = $callable;
 $options = array_merge(array(
 "mode" => self::OLDEST_MODE,
 "maxRecordsCached" => 500,
 "debug" => false,
 ), $options);
 $this->debug = $options["debug"];
 $this->mode = $options["mode"];
 $this->maxRecordsCached = isset($options["maxRecordsCached"]) ? $options["maxRecordsCached"] : -1;
 $this->baseTime = $this->getTimeStamp();
 }
 
 /**
 * @return int
 */
 protected function getTimeStamp()
 {
 return intval(1000000*microtime(true)) - $this->baseTime;
 }
 
 /**
 * @param $id
 * @return mixed
 */
 public function fetch($id)
 {
 if (!isset($this->items[$id])) {
 if (count($this->items) >= $this->maxRecordsCached){
 if ($this->debug){
 print (sprintf("max mem exceeded (%d bytes) with %d items!\n", $this->currentMemUsed, count($this->items)));
 }
 $this->findCandidateAndRemoveFromCache();
 }
 
 $this->items[$id] = call_user_func_array($this->callable, array($id));
 $this->metaInfo[$id]['id'] = $id;
 $this->metaInfo[$id]['inMem'] = true;
 $this->metaInfo[$id]['lastAccess'] = $this->getTimeStamp();
 $this->metaInfo[$id]['numAccess'] = isset($this->metaInfo[$id]['numAccess']) ? $this->metaInfo[$id]['numAccess']+1 : 1;
 }
 
 return $this->items[$id];
 }
 
 /**
 * @return bool
 */
 protected function findCandidateAndRemoveFromCache()
 {
 $candidate = null;
 
 switch ($this->mode){
 case self::LESS_MODE:
 $less = 9999999;
 foreach ($this->metaInfo as $metaInfo){
 if (($metaInfo['numAccess'] < $less) && $metaInfo['inMem']){
 $less = $metaInfo['numAccess'];
 $candidate = $metaInfo;
 if ($less == 1){
 break;
 }
 }
 }
 break;
 
 case self::LESS_OLDEST_MODE:
 $less = 9999999;
 // calculate the min value for numAccess
 foreach ($this->metaInfo as $metaInfo){
 if (($metaInfo['numAccess'] < $less) && $metaInfo['inMem']){
 $less = $metaInfo['numAccess'];
 }
 }
 $oldest = $this->getTimeStamp();
 foreach ($this->metaInfo as &$metaInfo){
 if (($less == $metaInfo['numAccess']) && $metaInfo['inMem'] && ($metaInfo['lastAccess'] < $oldest)){
 $oldest = $metaInfo['lastAccess'];
 $candidate = $metaInfo;
 }
 }
 break;
 
 case self::OLDEST_MODE:
 $oldest = date("U");
 foreach ($this->metaInfo as $metaInfo){
 if (($metaInfo['lastAccess'] < $oldest) && $metaInfo['inMem']){
 $oldest = $metaInfo['lastAccess'];
 $candidate = $metaInfo;
 }
 }
 break;
 }
 if ($this->debug){
 var_dump($candidate);
 }
 if ($candidate){
 if ($this->debug){
 $this->dumpMetaInfo();
 print sprintf("freeing item %d from memory\n", $candidate['id']);
 }
 $this->metaInfo[$candidate['id']]['inMem'] = false;
 unset($this->items[$candidate['id']]);
 if ($this->debug){
 $this->dumpMetaInfo();
 }
 return true;
 }
 }
 
 /**
 * Only with debug purposes
 */
 protected function dumpMetaInfo()
 {
 $text = "";
 $temp = $this->metaInfo;
 sort($temp);
 foreach($temp as $metaInfo){
 $text .= sprintf("%d[%d]%s,", $metaInfo['id'], $metaInfo['numAccess'], ($metaInfo['inMem']?'*':''));
 }
 print $text."\n";
 }
 
 }
 
 |