FRED™  3.0
FRED™: Framework for Rapid and Easy Development
Cache.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Rsi\Fred;
4 
5 /**
6  * Wrapper for the Doctrine cache.
7  */
8 class Cache extends Component{
9 
10  const TYPE_ARRAY = 'array';
11  const TYPE_CHAIN = 'chain';
12 
13  public $busyPlaceholder = null; //!< Placeholder for preventing cache slam.
14  public $defaultTtl = null; //!< Default lifetime.
15  public $ttlVariation = null; //!< Variation (plus and minus; percentage) of input value.
16  public $statPrefix = null; //!< Set to something to stat all cache requests (hit/miss).
17 
18  protected $_type = self::TYPE_ARRAY; //!< Cache type to use.
19  protected $_params = null; //!< Parameters (arguments) for this type of cache.
21  protected $_altParams = [self::TYPE_ARRAY => null,null => null];
22  protected $_busyTtl = null; //!< Lifetime for cache slam prevention (default = current timeout).
23 
24  protected $_provider = null;
25  protected $_altProvider = null;
26  protected $_useAlt = false;
27  protected $_refresh = false;
28  protected $_refreshed = [];
29  protected $_duploCount = [];
30 
31  protected function init(){
32  parent::init();
33  $this->publish(['type','refresh']);
34  }
35 
36  public function createProvider($type,$params = null){
37  if($type == self::TYPE_CHAIN){
38  $providers = [];
39  foreach($params as $sub_type => $sub_params)
40  $providers[] = $sub_type ? $this->createProvider($sub_type,$sub_params) : $this->_provider;
41  $params = [$providers];
42  }
43  $reflect = new \ReflectionClass('\\Doctrine\\Common\\Cache\\' . ucfirst($type) . 'Cache');
44  return $reflect->newInstanceArgs($params ?: []);
45  }
46  /**
47  * Save data to the cache.
48  * @param string $id Cache id.
49  * @param mixed $data
50  * @param int $ttl Cache lifetime (null = use default, 0 = never expire).
51  * @return bool Provider response (true = OK).
52  */
53  public function save($id,$data,$ttl = null){
54  $log = $this->component('log');
55  $log->debug(__CLASS__ . "::save('$id',...,$ttl)",__FILE__,__LINE__);
56  if($data === false) $log->warning("Saving false in cache for id '$id' (means 'no cache found')",__FILE__,__LINE__);
57  if($this->_refresh) $this->_refreshed[] = $id;
58  if($ttl === null) $ttl = $this->defaultTtl;
59  if($this->ttlVariation) $ttl *= 1 + rand(-1000,1000) * $this->ttlVariation / 100000;
60  try{
61  return $this->provider->save($id,$data,$ttl);
62  }
63  catch(\Exception $e){
64  $log->error($e,compact('id','data'));
65  }
66  return false;
67  }
68  /**
69  * Fetch with callback function in case cache fails.
70  * @param string $id Cache id.
71  * @param int $ttl Cache lifetime (null = use default, 0 = never expire).
72  * @param callable $callback Function to get data if cache fails (parameters: $id; returns data).
73  * @return mixed Data (false on fail).
74  */
75  public function fetch($id,$ttl = null,$callback = null){
76  $log = $this->component('log');
77  $time = $this->statPrefix ? microtime(true) : null;
78  try{
79  do{
80  $data = $this->_refresh && !in_array($id,$this->_refreshed) ? false : $this->provider->fetch($id);
81  if($busy = $this->busyPlaceholder && ($data === $this->busyPlaceholder)) sleep(1);
82  }
83  while($busy);
84  }
85  catch(\Exception $e){
86  $data = $busy = false;
87  $log->error($e,compact('id'));
88  }
89  $log->debug(__CLASS__ . "::fetch('$id',$ttl,...)",__FILE__,__LINE__,['cache' => $id,'found' => $data !== false]);
90  if($data !== false) $time = false;
91  elseif($callback){
92  if($this->busyPlaceholder) $this->save($id,$this->busyPlaceholder,$this->_busyTtl ?: ini_get('max_execution_time'));
93  $this->save($id,$data = call_user_func($callback,$id),$ttl);
94  }
95  if($this->statPrefix) $this->component('stats')->inc(
96  $this->statPrefix . ':' . $id . ':' . ($time ? 'miss' : 'hit'),
97  $time && $callback ? microtime(true) - $time : null
98  );
99  if($this->_fred->debug){
100  if(array_key_exists($id,$this->_duploCount))
101  $log->info('Duplicate fetch' . ($this->_duploCount[$id]++ ? ' (' . $this->_duploCount[$id] . ')' : ''),__FILE__,__LINE__,compact('id','ttl'));
102  else $this->_duploCount[$id] = 0;
103  }
104  return $data;
105  }
106  /**
107  * Remove expired items from the cache.
108  * @param array $errors Returns any errors.
109  * @return array Statistics about the purge.
110  */
111  public function purge(&$errors = null){
112  $type = null;
113  $stats = $errors = [];
114  if($this->provider instanceof \Doctrine\Common\Cache\FilesystemCache) $type = 'file';
115  elseif($this->provider instanceof \Doctrine\Common\Cache\PhpFileCache) $type = 'php';
116  if($type){
117  set_time_limit(300);
118  $files = new \RegexIterator(
119  new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getDirectory())),
120  '/^.+' . preg_quote($this->getExtension(),'/') . '$/i'
121  );
122  $time = time();
123  $count = $purged = $total = $largest = $dirs = 0;
124  foreach($files as $path => $file) try{
125  set_time_limit(10);
126  $count++;
127  if(($size = filesize($path)) > $largest) $largest = $size;
128  switch($type){
129  case 'file':
130  $resource = fopen($path,'r');
131  $purge = (int)fgets($resource) < $time;
132  fclose($resource);
133  break;
134  case 'php':
135  $purge = !is_array($data = include($path)) || !array_key_exists('lifetime',$data) || ($data['lifetime'] < $time);
136  break;
137  }
138  if($purge){
139  $total += $size;
140  $purged += unlink($path);
141  if(!(new \FilesystemIterator($dir = dirname($path)))->valid()) $dirs += rmdir($dir);
142  }
143  }
144  catch(Exception $e){
145  $errors[] = "Could not purge $path: " . $e->getMessage();
146  }
147  $stats = compact('type','count','purged','total','largest','dirs');
148  }
149  else $errors[] = 'Cache type not supported';
150  return $stats;
151  }
152 
153  protected function getProvider(){
154  if(!$this->_provider) $this->_provider = $this->createProvider($this->_type,$this->_params);
155  if(!$this->_useAlt) return $this->_provider;
156  if(!$this->_altProvider) $this->_altProvider = $this->createProvider($this->_altType,$this->_altParams);
157  return $this->_altProvider;
158  }
159 
160  protected function setRefresh($value){
161  $this->_refresh = $value;
162  $this->_refreshed = [];
163  }
164  /**
165  * See vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php for the available functions (save, contains, fetch, delete,
166  * flushAll, getStats) and more explanation.
167  */
168  public function __call($func_name,$params){
169  if($this->_useAlt = substr($func_name,0,1) == '_'){
170  $result = call_user_func_array([$this,substr($func_name,1)],$params);
171  $this->_useAlt = false;
172  return $result;
173  }
174  if(in_array($func_name,['save','contains','fetch','delete']))
175  $this->component('log')->debug(__CLASS__ . "::$func_name('$params[0]')",__FILE__,__LINE__);
176  return call_user_func_array([$this->provider,$func_name],$params);
177  }
178 
179  public function __invoke($id){
180  return $this->provider->fetch($id);
181  }
182 
183 }
Rsi\Fred\Cache\TYPE_CHAIN
const TYPE_CHAIN
Definition: Cache.php:11
Rsi\Fred\Cache\$_useAlt
$_useAlt
Definition: Cache.php:26
Rsi\Fred\Cache\$_type
$_type
Cache type to use.
Definition: Cache.php:18
Rsi\Fred\Cache\$statPrefix
$statPrefix
Set to something to stat all cache requests (hit/miss).
Definition: Cache.php:16
Rsi\Fred\Cache\__call
__call($func_name, $params)
See vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php for the available functions (save,...
Definition: Cache.php:168
Rsi\Fred\Cache\$_refresh
$_refresh
Definition: Cache.php:27
Rsi\Fred\Cache\$_altType
$_altType
Definition: Cache.php:20
Rsi\Fred\Cache\save
save($id, $data, $ttl=null)
Save data to the cache.
Definition: Cache.php:53
Rsi\Fred\Cache\$_params
$_params
Parameters (arguments) for this type of cache.
Definition: Cache.php:19
Rsi\Fred\Cache\purge
purge(&$errors=null)
Remove expired items from the cache.
Definition: Cache.php:111
Rsi\Fred\Cache\$_duploCount
$_duploCount
Definition: Cache.php:29
Rsi\Fred\Cache\$defaultTtl
$defaultTtl
Default lifetime.
Definition: Cache.php:14
Rsi\Fred\Cache
Wrapper for the Doctrine cache.
Definition: Cache.php:8
Rsi\Fred\Cache\$_altProvider
$_altProvider
Definition: Cache.php:25
Rsi\Fred\Cache\$_altParams
$_altParams
Definition: Cache.php:21
Rsi\Fred\Component
Basic component class.
Definition: Component.php:8
Rsi\Fred\Cache\setRefresh
setRefresh($value)
Definition: Cache.php:160
Rsi\Fred\Component\component
component($name)
Get a component (local or default).
Definition: Component.php:81
Rsi\Fred\Cache\$_busyTtl
$_busyTtl
Lifetime for cache slam prevention (default = current timeout).
Definition: Cache.php:22
Rsi\Fred\Cache\createProvider
createProvider($type, $params=null)
Definition: Cache.php:36
Rsi\Thing\publish
publish($property, $visibility=self::READABLE)
Publish a property (or hide it again).
Definition: Thing.php:28
Rsi\Fred\Cache\init
init()
Definition: Cache.php:31
Rsi\Fred\Cache\$_provider
$_provider
Definition: Cache.php:24
Rsi\Fred\Cache\$ttlVariation
$ttlVariation
Variation (plus and minus; percentage) of input value.
Definition: Cache.php:15
Rsi\Fred\Cache\fetch
fetch($id, $ttl=null, $callback=null)
Fetch with callback function in case cache fails.
Definition: Cache.php:75
Rsi\Fred\Cache\$_refreshed
$_refreshed
Definition: Cache.php:28
Rsi\Fred\Cache\$busyPlaceholder
$busyPlaceholder
Placeholder for preventing cache slam.
Definition: Cache.php:13
Rsi\Fred\Cache\__invoke
__invoke($id)
Definition: Cache.php:179
Rsi\Fred\Cache\TYPE_ARRAY
const TYPE_ARRAY
Definition: Cache.php:10
Rsi\Fred\Cache\getProvider
getProvider()
Definition: Cache.php:153
Rsi\Fred\Exception
Definition: Exception.php:5
Rsi\Fred
Definition: Alive.php:3