1<?php 2 3namespace dokuwiki\Cache; 4 5/** 6 * Generic handling of caching 7 */ 8class Cache 9{ 10 public $key = ''; // primary identifier for this item 11 public $ext = ''; // file ext for cache data, secondary identifier for this item 12 public $cache = ''; // cache file name 13 public $depends = array(); // array containing cache dependency information, 14 // used by makeDefaultCacheDecision to determine cache validity 15 16 public $_event = ''; // event to be triggered during useCache 17 public $_time; 18 public $_nocache = false; // if set to true, cache will not be used or stored 19 20 /** 21 * @param string $key primary identifier 22 * @param string $ext file extension 23 */ 24 public function __construct($key, $ext) 25 { 26 $this->key = $key; 27 $this->ext = $ext; 28 $this->cache = getCacheName($key, $ext); 29 } 30 31 /** 32 * public method to determine whether the cache can be used 33 * 34 * to assist in centralisation of event triggering and calculation of cache statistics, 35 * don't override this function override makeDefaultCacheDecision() 36 * 37 * @param array $depends array of cache dependencies, support dependecies: 38 * 'age' => max age of the cache in seconds 39 * 'files' => cache must be younger than mtime of each file 40 * (nb. dependency passes if file doesn't exist) 41 * 42 * @return bool true if cache can be used, false otherwise 43 */ 44 public function useCache($depends = array()) 45 { 46 $this->depends = $depends; 47 $this->addDependencies(); 48 49 if ($this->_event) { 50 return $this->stats(trigger_event($this->_event, $this, array($this, 'makeDefaultCacheDecision'))); 51 } else { 52 return $this->stats($this->makeDefaultCacheDecision()); 53 } 54 } 55 56 /** 57 * internal method containing cache use decision logic 58 * 59 * this function processes the following keys in the depends array 60 * purge - force a purge on any non empty value 61 * age - expire cache if older than age (seconds) 62 * files - expire cache if any file in this array was updated more recently than the cache 63 * 64 * Note that this function needs to be public as it is used as callback for the event handler 65 * 66 * can be overridden 67 * 68 * @internal This method may only be called by the event handler! Call \dokuwiki\Cache\Cache::useCache instead! 69 * 70 * @return bool see useCache() 71 */ 72 public function makeDefaultCacheDecision() 73 { 74 75 if ($this->_nocache) { 76 return false; 77 } // caching turned off 78 if (!empty($this->depends['purge'])) { 79 return false; 80 } // purge requested? 81 if (!($this->_time = @filemtime($this->cache))) { 82 return false; 83 } // cache exists? 84 85 // cache too old? 86 if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) { 87 return false; 88 } 89 90 if (!empty($this->depends['files'])) { 91 foreach ($this->depends['files'] as $file) { 92 if ($this->_time <= @filemtime($file)) { 93 return false; 94 } // cache older than files it depends on? 95 } 96 } 97 98 return true; 99 } 100 101 /** 102 * add dependencies to the depends array 103 * 104 * this method should only add dependencies, 105 * it should not remove any existing dependencies and 106 * it should only overwrite a dependency when the new value is more stringent than the old 107 */ 108 protected function addDependencies() 109 { 110 global $INPUT; 111 if ($INPUT->has('purge')) { 112 $this->depends['purge'] = true; 113 } // purge requested 114 } 115 116 /** 117 * retrieve the cached data 118 * 119 * @param bool $clean true to clean line endings, false to leave line endings alone 120 * @return string cache contents 121 */ 122 public function retrieveCache($clean = true) 123 { 124 return io_readFile($this->cache, $clean); 125 } 126 127 /** 128 * cache $data 129 * 130 * @param string $data the data to be cached 131 * @return bool true on success, false otherwise 132 */ 133 public function storeCache($data) 134 { 135 if ($this->_nocache) { 136 return false; 137 } 138 139 return io_savefile($this->cache, $data); 140 } 141 142 /** 143 * remove any cached data associated with this cache instance 144 */ 145 public function removeCache() 146 { 147 @unlink($this->cache); 148 } 149 150 /** 151 * Record cache hits statistics. 152 * (Only when debugging allowed, to reduce overhead.) 153 * 154 * @param bool $success result of this cache use attempt 155 * @return bool pass-thru $success value 156 */ 157 protected function stats($success) 158 { 159 global $conf; 160 static $stats = null; 161 static $file; 162 163 if (!$conf['allowdebug']) { 164 return $success; 165 } 166 167 if (is_null($stats)) { 168 $file = $conf['cachedir'] . '/cache_stats.txt'; 169 $lines = explode("\n", io_readFile($file)); 170 171 foreach ($lines as $line) { 172 $i = strpos($line, ','); 173 $stats[substr($line, 0, $i)] = $line; 174 } 175 } 176 177 if (isset($stats[$this->ext])) { 178 list($ext, $count, $hits) = explode(',', $stats[$this->ext]); 179 } else { 180 $ext = $this->ext; 181 $count = 0; 182 $hits = 0; 183 } 184 185 $count++; 186 if ($success) { 187 $hits++; 188 } 189 $stats[$this->ext] = "$ext,$count,$hits"; 190 191 io_saveFile($file, join("\n", $stats)); 192 193 return $success; 194 } 195} 196