Cake/Cache/Engine/FileEngine.php

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