FileEngine.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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. $this->_File->rewind();
  117. $success = $this->_File->ftruncate(0) && $this->_File->fwrite($contents) && $this->_File->fflush();
  118. if ($this->settings['lock']) {
  119. $this->_File->flock(LOCK_UN);
  120. }
  121. return $success;
  122. }
  123. /**
  124. * Read a key from the cache
  125. *
  126. * @param string $key Identifier for the data
  127. * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
  128. */
  129. public function read($key) {
  130. if (!$this->_init || $this->_setKey($key) === false) {
  131. return false;
  132. }
  133. if ($this->settings['lock']) {
  134. $this->_File->flock(LOCK_SH);
  135. }
  136. $this->_File->rewind();
  137. $time = time();
  138. $cachetime = intval($this->_File->current());
  139. if ($cachetime !== false && ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime)) {
  140. if ($this->settings['lock']) {
  141. $this->_File->flock(LOCK_UN);
  142. }
  143. return false;
  144. }
  145. $data = '';
  146. $this->_File->next();
  147. while ($this->_File->valid()) {
  148. $data .= $this->_File->current();
  149. $this->_File->next();
  150. }
  151. if ($this->settings['lock']) {
  152. $this->_File->flock(LOCK_UN);
  153. }
  154. $data = trim($data);
  155. if ($data !== '' && !empty($this->settings['serialize'])) {
  156. if ($this->settings['isWindows']) {
  157. $data = str_replace('\\\\\\\\', '\\', $data);
  158. }
  159. $data = unserialize((string)$data);
  160. }
  161. return $data;
  162. }
  163. /**
  164. * Delete a key from the cache
  165. *
  166. * @param string $key Identifier for the data
  167. * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
  168. */
  169. public function delete($key) {
  170. if ($this->_setKey($key) === false || !$this->_init) {
  171. return false;
  172. }
  173. $path = $this->_File->getRealPath();
  174. $this->_File = null;
  175. return unlink($path);
  176. }
  177. /**
  178. * Delete all values from the cache
  179. *
  180. * @param boolean $check Optional - only delete expired cache items
  181. * @return boolean True if the cache was successfully cleared, false otherwise
  182. */
  183. public function clear($check) {
  184. if (!$this->_init) {
  185. return false;
  186. }
  187. $dir = dir($this->settings['path']);
  188. if ($check) {
  189. $now = time();
  190. $threshold = $now - $this->settings['duration'];
  191. }
  192. $prefixLength = strlen($this->settings['prefix']);
  193. while (($entry = $dir->read()) !== false) {
  194. if (substr($entry, 0, $prefixLength) !== $this->settings['prefix']) {
  195. continue;
  196. }
  197. if ($this->_setKey($entry) === false) {
  198. continue;
  199. }
  200. if ($check) {
  201. $mtime = $this->_File->getMTime();
  202. if ($mtime > $threshold) {
  203. continue;
  204. }
  205. $expires = (int)$this->_File->current();
  206. if ($expires > $now) {
  207. continue;
  208. }
  209. }
  210. $path = $this->_File->getRealPath();
  211. $this->_File = null;
  212. if (file_exists($path)) {
  213. unlink($path);
  214. }
  215. }
  216. $dir->close();
  217. return true;
  218. }
  219. /**
  220. * Not implemented
  221. *
  222. * @param string $key
  223. * @param integer $offset
  224. * @return void
  225. * @throws CacheException
  226. */
  227. public function decrement($key, $offset = 1) {
  228. throw new CacheException(__d('cake_dev', 'Files cannot be atomically decremented.'));
  229. }
  230. /**
  231. * Not implemented
  232. *
  233. * @param string $key
  234. * @param integer $offset
  235. * @return void
  236. * @throws CacheException
  237. */
  238. public function increment($key, $offset = 1) {
  239. throw new CacheException(__d('cake_dev', 'Files cannot be atomically incremented.'));
  240. }
  241. /**
  242. * Sets the current cache key this class is managing, and creates a writable SplFileObject
  243. * for the cache file the key is refering to.
  244. *
  245. * @param string $key The key
  246. * @param boolean $createKey Whether the key should be created if it doesn't exists, or not
  247. * @return boolean true if the cache key could be set, false otherwise
  248. */
  249. protected function _setKey($key, $createKey = false) {
  250. $path = new SplFileInfo($this->settings['path'] . $key);
  251. if (!$createKey && !$path->isFile()) {
  252. return false;
  253. }
  254. if (empty($this->_File) || $this->_File->getBaseName() !== $key) {
  255. $exists = file_exists($path->getPathname());
  256. try {
  257. $this->_File = $path->openFile('c+');
  258. } catch (Exception $e) {
  259. trigger_error($e->getMessage(), E_USER_WARNING);
  260. return false;
  261. }
  262. unset($path);
  263. if (!$exists && !chmod($this->_File->getPathname(), (int) $this->settings['mask'])) {
  264. trigger_error(__d(
  265. 'cake_dev', 'Could not apply permission mask "%s" on cache file "%s"',
  266. array($this->_File->getPathname(), $this->settings['mask'])), E_USER_WARNING);
  267. }
  268. }
  269. return true;
  270. }
  271. /**
  272. * Determine is cache directory is writable
  273. *
  274. * @return boolean
  275. */
  276. protected function _active() {
  277. $dir = new SplFileInfo($this->settings['path']);
  278. if ($this->_init && !($dir->isDir() && $dir->isWritable())) {
  279. $this->_init = false;
  280. trigger_error(__d('cake_dev', '%s is not writable', $this->settings['path']), E_USER_WARNING);
  281. return false;
  282. }
  283. return true;
  284. }
  285. }