FileEngine.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <?php
  2. /**
  3. * File Storage engine for cache. Filestorage is the slowest cache storage
  4. * to read and write. However, it is good for servers that don't have other storage
  5. * engine available, or have content which is not performance sensitive.
  6. *
  7. * You can configure a FileEngine cache, using Cache::config()
  8. *
  9. * PHP 5
  10. *
  11. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  12. * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  13. *
  14. * Licensed under The MIT License
  15. * Redistributions of files must retain the above copyright notice.
  16. *
  17. * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  18. * @link http://cakephp.org CakePHP(tm) Project
  19. * @since CakePHP(tm) v 1.2.0.4933
  20. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  21. */
  22. /**
  23. * File Storage engine for cache
  24. *
  25. * @package Cake.Cache.Engine
  26. */
  27. class FileEngine extends CacheEngine {
  28. /**
  29. * Instance of SplFileObject class
  30. *
  31. * @var File
  32. */
  33. protected $_File = null;
  34. /**
  35. * Settings
  36. *
  37. * - path = absolute path to cache directory, default => CACHE
  38. * - prefix = string prefix for filename, default => cake_
  39. * - lock = enable file locking on write, default => false
  40. * - serialize = serialize the data, default => true
  41. *
  42. * @var array
  43. * @see CacheEngine::__defaults
  44. */
  45. public $settings = array();
  46. /**
  47. * True unless FileEngine::__active(); fails
  48. *
  49. * @var boolean
  50. */
  51. protected $_init = true;
  52. /**
  53. * Initialize the Cache Engine
  54. *
  55. * Called automatically by the cache frontend
  56. * To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array());
  57. *
  58. * @param array $settings array of setting for the engine
  59. * @return boolean True if the engine has been successfully initialized, false if not
  60. */
  61. public function init($settings = array()) {
  62. parent::init(array_merge(
  63. array(
  64. 'engine' => 'File', 'path' => CACHE, 'prefix'=> 'cake_', 'lock'=> true,
  65. 'serialize'=> true, 'isWindows' => false, 'mask' => 0664
  66. ),
  67. $settings
  68. ));
  69. if (DS === '\\') {
  70. $this->settings['isWindows'] = true;
  71. }
  72. if (substr($this->settings['path'], -1) !== DS) {
  73. $this->settings['path'] .= DS;
  74. }
  75. return $this->_active();
  76. }
  77. /**
  78. * Garbage collection. Permanently remove all expired and deleted data
  79. *
  80. * @return boolean True if garbage collection was succesful, false on failure
  81. */
  82. public function gc() {
  83. return $this->clear(true);
  84. }
  85. /**
  86. * Write data for key into cache
  87. *
  88. * @param string $key Identifier for the data
  89. * @param mixed $data Data to be cached
  90. * @param mixed $duration How long to cache the data, in seconds
  91. * @return boolean True if the data was successfully cached, false on failure
  92. */
  93. public function write($key, $data, $duration) {
  94. if ($data === '' || !$this->_init) {
  95. return false;
  96. }
  97. if ($this->_setKey($key, true) === false) {
  98. return false;
  99. }
  100. $lineBreak = "\n";
  101. if ($this->settings['isWindows']) {
  102. $lineBreak = "\r\n";
  103. }
  104. if (!empty($this->settings['serialize'])) {
  105. if ($this->settings['isWindows']) {
  106. $data = str_replace('\\', '\\\\\\\\', serialize($data));
  107. } else {
  108. $data = serialize($data);
  109. }
  110. }
  111. $expires = time() + $duration;
  112. $contents = $expires . $lineBreak . $data . $lineBreak;
  113. if ($this->settings['lock']) {
  114. $this->_File->flock(LOCK_EX);
  115. }
  116. $success = $this->_File->ftruncate(0) && $this->_File->fwrite($contents) && $this->_File->fflush();
  117. if ($this->settings['lock']) {
  118. $this->_File->flock(LOCK_UN);
  119. }
  120. return $success;
  121. }
  122. /**
  123. * Read a key from the cache
  124. *
  125. * @param string $key Identifier for the data
  126. * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
  127. */
  128. public function read($key) {
  129. if (!$this->_init || $this->_setKey($key) === false) {
  130. return false;
  131. }
  132. if ($this->settings['lock']) {
  133. $this->_File->flock(LOCK_SH);
  134. }
  135. $this->_File->rewind();
  136. $time = time();
  137. $cachetime = intval($this->_File->current());
  138. if ($cachetime !== false && ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime)) {
  139. if ($this->settings['lock']) {
  140. $this->_File->flock(LOCK_UN);
  141. }
  142. return false;
  143. }
  144. $data = '';
  145. $this->_File->next();
  146. while ($this->_File->valid()) {
  147. $data .= $this->_File->current();
  148. $this->_File->next();
  149. }
  150. if ($this->settings['lock']) {
  151. $this->_File->flock(LOCK_UN);
  152. }
  153. $data = trim($data);
  154. if ($data !== '' && !empty($this->settings['serialize'])) {
  155. if ($this->settings['isWindows']) {
  156. $data = str_replace('\\\\\\\\', '\\', $data);
  157. }
  158. $data = unserialize((string)$data);
  159. }
  160. return $data;
  161. }
  162. /**
  163. * Delete a key from the cache
  164. *
  165. * @param string $key Identifier for the data
  166. * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
  167. */
  168. public function delete($key) {
  169. if ($this->_setKey($key) === false || !$this->_init) {
  170. return false;
  171. }
  172. $path = $this->_File->getRealPath();
  173. $this->_File = null;
  174. return unlink($path);
  175. }
  176. /**
  177. * Delete all values from the cache
  178. *
  179. * @param boolean $check Optional - only delete expired cache items
  180. * @return boolean True if the cache was successfully cleared, false otherwise
  181. */
  182. public function clear($check) {
  183. if (!$this->_init) {
  184. return false;
  185. }
  186. $dir = dir($this->settings['path']);
  187. if ($check) {
  188. $now = time();
  189. $threshold = $now - $this->settings['duration'];
  190. }
  191. $prefixLength = strlen($this->settings['prefix']);
  192. while (($entry = $dir->read()) !== false) {
  193. if (substr($entry, 0, $prefixLength) !== $this->settings['prefix']) {
  194. continue;
  195. }
  196. if ($this->_setKey($entry) === false) {
  197. continue;
  198. }
  199. if ($check) {
  200. $mtime = $this->_File->getMTime();
  201. if ($mtime > $threshold) {
  202. continue;
  203. }
  204. $expires = (int)$this->_File->current();
  205. if ($expires > $now) {
  206. continue;
  207. }
  208. }
  209. $path = $this->_File->getRealPath();
  210. $this->_File = null;
  211. if (file_exists($path)) {
  212. unlink($path);
  213. }
  214. }
  215. $dir->close();
  216. return true;
  217. }
  218. /**
  219. * Not implemented
  220. *
  221. * @param string $key
  222. * @param integer $offset
  223. * @return void
  224. * @throws CacheException
  225. */
  226. public function decrement($key, $offset = 1) {
  227. throw new CacheException(__d('cake_dev', 'Files cannot be atomically decremented.'));
  228. }
  229. /**
  230. * Not implemented
  231. *
  232. * @param string $key
  233. * @param integer $offset
  234. * @return void
  235. * @throws CacheException
  236. */
  237. public function increment($key, $offset = 1) {
  238. throw new CacheException(__d('cake_dev', 'Files cannot be atomically incremented.'));
  239. }
  240. /**
  241. * Sets the current cache key this class is managing, and creates a writable SplFileObject
  242. * for the cache file the key is refering to.
  243. *
  244. * @param string $key The key
  245. * @param boolean $createKey Whether the key should be created if it doesn't exists, or not
  246. * @return boolean true if the cache key could be set, false otherwise
  247. */
  248. protected function _setKey($key, $createKey = false) {
  249. $path = new SplFileInfo($this->settings['path'] . $key);
  250. if (!$createKey && !$path->isFile()) {
  251. return false;
  252. }
  253. if (empty($this->_File) || $this->_File->getBaseName() !== $key) {
  254. $exists = file_exists($path->getPathname());
  255. try {
  256. $this->_File = $path->openFile('c+');
  257. } catch (Exception $e) {
  258. trigger_error(__d('cake_dev', $e->getMessage()), E_USER_WARNING);
  259. return false;
  260. }
  261. unset($path);
  262. if (!$exists && !chmod($this->_File->getPathname(), (int) $this->settings['mask'])) {
  263. trigger_error(__d(
  264. 'cake_dev', 'Could not apply permission mask "%s" on cache file "%s"',
  265. array($this->_File->getPathname(), $this->settings['mask'])), E_USER_WARNING);
  266. }
  267. }
  268. return true;
  269. }
  270. /**
  271. * Determine is cache directory is writable
  272. *
  273. * @return boolean
  274. */
  275. protected function _active() {
  276. $dir = new SplFileInfo($this->settings['path']);
  277. if ($this->_init && !($dir->isDir() && $dir->isWritable())) {
  278. $this->_init = false;
  279. trigger_error(__d('cake_dev', '%s is not writable', $this->settings['path']), E_USER_WARNING);
  280. return false;
  281. }
  282. return true;
  283. }
  284. }