統計
| ブランチ: | リビジョン:

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
}