pictcode / lib / Cake / Cache / Engine / FileEngine.php @ 0b1b8047
履歴 | 表示 | アノテート | ダウンロード (11.108 KB)
1 | 635eef61 | spyder1211 | <?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 | } |