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 _useCache 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 _useCache() 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, '_useCache'))); 51 } else { 52 return $this->_stats($this->_useCache()); 53 } 54 } 55 56 /** 57 * private 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 * @return bool see useCache() 69 */ 70 public function _useCache() 71 { 72 73 if ($this->_nocache) { 74 return false; 75 } // caching turned off 76 if (!empty($this->depends['purge'])) { 77 return false; 78 } // purge requested? 79 if (!($this->_time = @filemtime($this->cache))) { 80 return false; 81 } // cache exists? 82 83 // cache too old? 84 if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) { 85 return false; 86 } 87 88 if (!empty($this->depends['files'])) { 89 foreach ($this->depends['files'] as $file) { 90 if ($this->_time <= @filemtime($file)) { 91 return false; 92 } // cache older than files it depends on? 93 } 94 } 95 96 return true; 97 } 98 99 /** 100 * add dependencies to the depends array 101 * 102 * this method should only add dependencies, 103 * it should not remove any existing dependencies and 104 * it should only overwrite a dependency when the new value is more stringent than the old 105 */ 106 protected function _addDependencies() 107 { 108 global $INPUT; 109 if ($INPUT->has('purge')) { 110 $this->depends['purge'] = true; 111 } // purge requested 112 } 113 114 /** 115 * retrieve the cached data 116 * 117 * @param bool $clean true to clean line endings, false to leave line endings alone 118 * @return string cache contents 119 */ 120 public function retrieveCache($clean = true) 121 { 122 return io_readFile($this->cache, $clean); 123 } 124 125 /** 126 * cache $data 127 * 128 * @param string $data the data to be cached 129 * @return bool true on success, false otherwise 130 */ 131 public function storeCache($data) 132 { 133 if ($this->_nocache) { 134 return false; 135 } 136 137 return io_savefile($this->cache, $data); 138 } 139 140 /** 141 * remove any cached data associated with this cache instance 142 */ 143 public function removeCache() 144 { 145 @unlink($this->cache); 146 } 147 148 /** 149 * Record cache hits statistics. 150 * (Only when debugging allowed, to reduce overhead.) 151 * 152 * @param bool $success result of this cache use attempt 153 * @return bool pass-thru $success value 154 */ 155 protected function _stats($success) 156 { 157 global $conf; 158 static $stats = null; 159 static $file; 160 161 if (!$conf['allowdebug']) { 162 return $success; 163 } 164 165 if (is_null($stats)) { 166 $file = $conf['cachedir'] . '/cache_stats.txt'; 167 $lines = explode("\n", io_readFile($file)); 168 169 foreach ($lines as $line) { 170 $i = strpos($line, ','); 171 $stats[substr($line, 0, $i)] = $line; 172 } 173 } 174 175 if (isset($stats[$this->ext])) { 176 list($ext, $count, $hits) = explode(',', $stats[$this->ext]); 177 } else { 178 $ext = $this->ext; 179 $count = 0; 180 $hits = 0; 181 } 182 183 $count++; 184 if ($success) { 185 $hits++; 186 } 187 $stats[$this->ext] = "$ext,$count,$hits"; 188 189 io_saveFile($file, join("\n", $stats)); 190 191 return $success; 192 } 193} 194