pictcode / lib / Cake / Cache / Engine / FileEngine.php @ 0b1b8047
履歴 | 表示 | アノテート | ダウンロード (11.108 KB)
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 |
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
10 |
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
11 |
*
|
12 |
* Licensed under The MIT License
|
13 |
* For full copyright and license information, please see the LICENSE.txt
|
14 |
* Redistributions of files must retain the above copyright notice.
|
15 |
*
|
16 |
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
17 |
* @link http://cakephp.org CakePHP(tm) Project
|
18 |
* @since CakePHP(tm) v 1.2.0.4933
|
19 |
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
20 |
*/
|
21 |
|
22 |
/**
|
23 |
* File Storage engine for cache. Filestorage is the slowest cache storage
|
24 |
* to read and write. However, it is good for servers that don't have other storage
|
25 |
* engine available, or have content which is not performance sensitive.
|
26 |
*
|
27 |
* You can configure a FileEngine cache, using Cache::config()
|
28 |
*
|
29 |
* @package Cake.Cache.Engine
|
30 |
*/
|
31 |
class FileEngine extends CacheEngine { |
32 |
|
33 |
/**
|
34 |
* Instance of SplFileObject class
|
35 |
*
|
36 |
* @var File
|
37 |
*/
|
38 |
protected $_File = null; |
39 |
|
40 |
/**
|
41 |
* Settings
|
42 |
*
|
43 |
* - path = absolute path to cache directory, default => CACHE
|
44 |
* - prefix = string prefix for filename, default => cake_
|
45 |
* - lock = enable file locking on write, default => true
|
46 |
* - serialize = serialize the data, default => true
|
47 |
*
|
48 |
* @var array
|
49 |
* @see CacheEngine::__defaults
|
50 |
*/
|
51 |
public $settings = array(); |
52 |
|
53 |
/**
|
54 |
* True unless FileEngine::__active(); fails
|
55 |
*
|
56 |
* @var bool
|
57 |
*/
|
58 |
protected $_init = true; |
59 |
|
60 |
/**
|
61 |
* Initialize the Cache Engine
|
62 |
*
|
63 |
* Called automatically by the cache frontend
|
64 |
* To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array());
|
65 |
*
|
66 |
* @param array $settings array of setting for the engine
|
67 |
* @return bool True if the engine has been successfully initialized, false if not
|
68 |
*/
|
69 |
public function init($settings = array()) { |
70 |
$settings += array( |
71 |
'engine' => 'File', |
72 |
'path' => CACHE, |
73 |
'prefix' => 'cake_', |
74 |
'lock' => true, |
75 |
'serialize' => true, |
76 |
'isWindows' => false, |
77 |
'mask' => 0664 |
78 |
); |
79 |
parent::init($settings); |
80 |
|
81 |
if (DS === '\\') { |
82 |
$this->settings['isWindows'] = true; |
83 |
} |
84 |
if (substr($this->settings['path'], -1) !== DS) { |
85 |
$this->settings['path'] .= DS; |
86 |
} |
87 |
if (!empty($this->_groupPrefix)) { |
88 |
$this->_groupPrefix = str_replace('_', DS, $this->_groupPrefix); |
89 |
} |
90 |
return $this->_active(); |
91 |
} |
92 |
|
93 |
/**
|
94 |
* Garbage collection. Permanently remove all expired and deleted data
|
95 |
*
|
96 |
* @param int $expires [optional] An expires timestamp, invalidating all data before.
|
97 |
* @return bool True if garbage collection was successful, false on failure
|
98 |
*/
|
99 |
public function gc($expires = null) { |
100 |
return $this->clear(true); |
101 |
} |
102 |
|
103 |
/**
|
104 |
* Write data for key into cache
|
105 |
*
|
106 |
* @param string $key Identifier for the data
|
107 |
* @param mixed $data Data to be cached
|
108 |
* @param int $duration How long to cache the data, in seconds
|
109 |
* @return bool True if the data was successfully cached, false on failure
|
110 |
*/
|
111 |
public function write($key, $data, $duration) { |
112 |
if ($data === '' || !$this->_init) { |
113 |
return false; |
114 |
} |
115 |
|
116 |
if ($this->_setKey($key, true) === false) { |
117 |
return false; |
118 |
} |
119 |
|
120 |
$lineBreak = "\n"; |
121 |
|
122 |
if ($this->settings['isWindows']) { |
123 |
$lineBreak = "\r\n"; |
124 |
} |
125 |
|
126 |
if (!empty($this->settings['serialize'])) { |
127 |
if ($this->settings['isWindows']) { |
128 |
$data = str_replace('\\', '\\\\\\\\', serialize($data)); |
129 |
} else {
|
130 |
$data = serialize($data); |
131 |
} |
132 |
} |
133 |
|
134 |
$expires = time() + $duration; |
135 |
$contents = $expires . $lineBreak . $data . $lineBreak; |
136 |
|
137 |
if ($this->settings['lock']) { |
138 |
$this->_File->flock(LOCK_EX); |
139 |
} |
140 |
|
141 |
$this->_File->rewind(); |
142 |
$success = $this->_File->ftruncate(0) && $this->_File->fwrite($contents) && $this->_File->fflush(); |
143 |
|
144 |
if ($this->settings['lock']) { |
145 |
$this->_File->flock(LOCK_UN); |
146 |
} |
147 |
|
148 |
return $success; |
149 |
} |
150 |
|
151 |
/**
|
152 |
* Read a key from the cache
|
153 |
*
|
154 |
* @param string $key Identifier for the data
|
155 |
* @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
|
156 |
*/
|
157 |
public function read($key) { |
158 |
if (!$this->_init || $this->_setKey($key) === false) { |
159 |
return false; |
160 |
} |
161 |
|
162 |
if ($this->settings['lock']) { |
163 |
$this->_File->flock(LOCK_SH); |
164 |
} |
165 |
|
166 |
$this->_File->rewind(); |
167 |
$time = time(); |
168 |
$cachetime = (int)$this->_File->current(); |
169 |
|
170 |
if ($cachetime !== false && ($cachetime < $time || ($time + $this->settings['duration']) < $cachetime)) { |
171 |
if ($this->settings['lock']) { |
172 |
$this->_File->flock(LOCK_UN); |
173 |
} |
174 |
return false; |
175 |
} |
176 |
|
177 |
$data = ''; |
178 |
$this->_File->next(); |
179 |
while ($this->_File->valid()) { |
180 |
$data .= $this->_File->current(); |
181 |
$this->_File->next(); |
182 |
} |
183 |
|
184 |
if ($this->settings['lock']) { |
185 |
$this->_File->flock(LOCK_UN); |
186 |
} |
187 |
|
188 |
$data = trim($data); |
189 |
|
190 |
if ($data !== '' && !empty($this->settings['serialize'])) { |
191 |
if ($this->settings['isWindows']) { |
192 |
$data = str_replace('\\\\\\\\', '\\', $data); |
193 |
} |
194 |
$data = unserialize((string)$data); |
195 |
} |
196 |
return $data; |
197 |
} |
198 |
|
199 |
/**
|
200 |
* Delete a key from the cache
|
201 |
*
|
202 |
* @param string $key Identifier for the data
|
203 |
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
|
204 |
*/
|
205 |
public function delete($key) { |
206 |
if ($this->_setKey($key) === false || !$this->_init) { |
207 |
return false; |
208 |
} |
209 |
$path = $this->_File->getRealPath(); |
210 |
$this->_File = null; |
211 |
|
212 |
//@codingStandardsIgnoreStart
|
213 |
return @unlink($path); |
214 |
//@codingStandardsIgnoreEnd
|
215 |
} |
216 |
|
217 |
/**
|
218 |
* Delete all values from the cache
|
219 |
*
|
220 |
* @param bool $check Optional - only delete expired cache items
|
221 |
* @return bool True if the cache was successfully cleared, false otherwise
|
222 |
*/
|
223 |
public function clear($check) { |
224 |
if (!$this->_init) { |
225 |
return false; |
226 |
} |
227 |
$this->_File = null; |
228 |
|
229 |
$threshold = $now = false; |
230 |
if ($check) { |
231 |
$now = time(); |
232 |
$threshold = $now - $this->settings['duration']; |
233 |
} |
234 |
|
235 |
$this->_clearDirectory($this->settings['path'], $now, $threshold); |
236 |
|
237 |
$directory = new RecursiveDirectoryIterator($this->settings['path']); |
238 |
$contents = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST); |
239 |
$cleared = array(); |
240 |
foreach ($contents as $path) { |
241 |
if ($path->isFile()) { |
242 |
continue;
|
243 |
} |
244 |
|
245 |
$path = $path->getRealPath() . DS; |
246 |
if (!in_array($path, $cleared)) { |
247 |
$this->_clearDirectory($path, $now, $threshold); |
248 |
$cleared[] = $path; |
249 |
} |
250 |
} |
251 |
return true; |
252 |
} |
253 |
|
254 |
/**
|
255 |
* Used to clear a directory of matching files.
|
256 |
*
|
257 |
* @param string $path The path to search.
|
258 |
* @param int $now The current timestamp
|
259 |
* @param int $threshold Any file not modified after this value will be deleted.
|
260 |
* @return void
|
261 |
*/
|
262 |
protected function _clearDirectory($path, $now, $threshold) { |
263 |
$prefixLength = strlen($this->settings['prefix']); |
264 |
|
265 |
if (!is_dir($path)) { |
266 |
return;
|
267 |
} |
268 |
|
269 |
$dir = dir($path); |
270 |
while (($entry = $dir->read()) !== false) { |
271 |
if (substr($entry, 0, $prefixLength) !== $this->settings['prefix']) { |
272 |
continue;
|
273 |
} |
274 |
|
275 |
try {
|
276 |
$file = new SplFileObject($path . $entry, 'r'); |
277 |
} catch (Exception $e) { |
278 |
continue;
|
279 |
} |
280 |
|
281 |
if ($threshold) { |
282 |
$mtime = $file->getMTime(); |
283 |
|
284 |
if ($mtime > $threshold) { |
285 |
continue;
|
286 |
} |
287 |
$expires = (int)$file->current(); |
288 |
|
289 |
if ($expires > $now) { |
290 |
continue;
|
291 |
} |
292 |
} |
293 |
if ($file->isFile()) { |
294 |
$filePath = $file->getRealPath(); |
295 |
$file = null; |
296 |
|
297 |
//@codingStandardsIgnoreStart
|
298 |
@unlink($filePath); |
299 |
//@codingStandardsIgnoreEnd
|
300 |
} |
301 |
} |
302 |
} |
303 |
|
304 |
/**
|
305 |
* Not implemented
|
306 |
*
|
307 |
* @param string $key The key to decrement
|
308 |
* @param int $offset The number to offset
|
309 |
* @return void
|
310 |
* @throws CacheException
|
311 |
*/
|
312 |
public function decrement($key, $offset = 1) { |
313 |
throw new CacheException(__d('cake_dev', 'Files cannot be atomically decremented.')); |
314 |
} |
315 |
|
316 |
/**
|
317 |
* Not implemented
|
318 |
*
|
319 |
* @param string $key The key to decrement
|
320 |
* @param int $offset The number to offset
|
321 |
* @return void
|
322 |
* @throws CacheException
|
323 |
*/
|
324 |
public function increment($key, $offset = 1) { |
325 |
throw new CacheException(__d('cake_dev', 'Files cannot be atomically incremented.')); |
326 |
} |
327 |
|
328 |
/**
|
329 |
* Sets the current cache key this class is managing, and creates a writable SplFileObject
|
330 |
* for the cache file the key is referring to.
|
331 |
*
|
332 |
* @param string $key The key
|
333 |
* @param bool $createKey Whether the key should be created if it doesn't exists, or not
|
334 |
* @return bool true if the cache key could be set, false otherwise
|
335 |
*/
|
336 |
protected function _setKey($key, $createKey = false) { |
337 |
$groups = null; |
338 |
if (!empty($this->_groupPrefix)) { |
339 |
$groups = vsprintf($this->_groupPrefix, $this->groups()); |
340 |
} |
341 |
$dir = $this->settings['path'] . $groups; |
342 |
|
343 |
if (!is_dir($dir)) { |
344 |
mkdir($dir, 0775, true); |
345 |
} |
346 |
$path = new SplFileInfo($dir . $key); |
347 |
|
348 |
if (!$createKey && !$path->isFile()) { |
349 |
return false; |
350 |
} |
351 |
if (empty($this->_File) || $this->_File->getBaseName() !== $key) { |
352 |
$exists = file_exists($path->getPathname()); |
353 |
try {
|
354 |
$this->_File = $path->openFile('c+'); |
355 |
} catch (Exception $e) { |
356 |
trigger_error($e->getMessage(), E_USER_WARNING); |
357 |
return false; |
358 |
} |
359 |
unset($path); |
360 |
|
361 |
if (!$exists && !chmod($this->_File->getPathname(), (int)$this->settings['mask'])) { |
362 |
trigger_error(__d(
|
363 |
'cake_dev', 'Could not apply permission mask "%s" on cache file "%s"', |
364 |
array($this->_File->getPathname(), $this->settings['mask'])), E_USER_WARNING); |
365 |
} |
366 |
} |
367 |
return true; |
368 |
} |
369 |
|
370 |
/**
|
371 |
* Determine is cache directory is writable
|
372 |
*
|
373 |
* @return bool
|
374 |
*/
|
375 |
protected function _active() { |
376 |
$dir = new SplFileInfo($this->settings['path']); |
377 |
if (Configure::read('debug')) { |
378 |
$path = $dir->getPathname(); |
379 |
if (!is_dir($path)) { |
380 |
mkdir($path, 0775, true); |
381 |
} |
382 |
} |
383 |
if ($this->_init && !($dir->isDir() && $dir->isWritable())) { |
384 |
$this->_init = false; |
385 |
trigger_error(__d('cake_dev', '%s is not writable', $this->settings['path']), E_USER_WARNING); |
386 |
return false; |
387 |
} |
388 |
return true; |
389 |
} |
390 |
|
391 |
/**
|
392 |
* Generates a safe key for use with cache engine storage engines.
|
393 |
*
|
394 |
* @param string $key the key passed over
|
395 |
* @return mixed string $key or false
|
396 |
*/
|
397 |
public function key($key) { |
398 |
if (empty($key)) { |
399 |
return false; |
400 |
} |
401 |
|
402 |
$key = Inflector::underscore(str_replace(array(DS, '/', '.', '<', '>', '?', ':', '|', '*', '"'), '_', strval($key))); |
403 |
return $key; |
404 |
} |
405 |
|
406 |
/**
|
407 |
* Recursively deletes all files under any directory named as $group
|
408 |
*
|
409 |
* @param string $group The group to clear.
|
410 |
* @return bool success
|
411 |
*/
|
412 |
public function clearGroup($group) { |
413 |
$this->_File = null; |
414 |
$directoryIterator = new RecursiveDirectoryIterator($this->settings['path']); |
415 |
$contents = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST); |
416 |
foreach ($contents as $object) { |
417 |
$containsGroup = strpos($object->getPathName(), DS . $group . DS) !== false; |
418 |
$hasPrefix = true; |
419 |
if (strlen($this->settings['prefix']) !== 0) { |
420 |
$hasPrefix = strpos($object->getBaseName(), $this->settings['prefix']) === 0; |
421 |
} |
422 |
if ($object->isFile() && $containsGroup && $hasPrefix) { |
423 |
$path = $object->getPathName(); |
424 |
$object = null; |
425 |
//@codingStandardsIgnoreStart
|
426 |
@unlink($path); |
427 |
//@codingStandardsIgnoreEnd
|
428 |
} |
429 |
} |
430 |
return true; |
431 |
} |
432 |
} |