Cache.php 6.7 KB

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