1: <?php
2: 3: 4: 5: 6: 7:
8:
9: namespace Wei;
10:
11: 12: 13: 14: 15:
16: class FileCache extends BaseCache
17: {
18: 19: 20: 21: 22:
23: protected $dir = 'cache';
24:
25: 26: 27: 28: 29:
30: protected $illegalChars = array(
31: '\\', '/', ':', '*', '?', '"', '<', '>', '|', "\r", "\n"
32: );
33:
34: 35: 36: 37: 38:
39: protected $ext = 'cache';
40:
41: 42: 43: 44: 45:
46: public function __construct(array $options = array())
47: {
48: parent::__construct($options + array(
49: 'dir' => $this->dir
50: ));
51: }
52:
53: 54: 55:
56: public function get($key, $expire = null, $fn = null)
57: {
58: if (!is_file($file = $this->getFile($key))) {
59: $result = false;
60: } else {
61: $content = $this->getContent($file);;
62: if ($content && is_array($content) && time() < $content[0]) {
63: $result = $content[1];
64: } else {
65: $this->remove($key);
66: $result = false;
67: }
68: }
69: return $this->processGetResult($key, $result, $expire, $fn);
70: }
71:
72: 73: 74:
75: public function set($key, $value, $expire = 0)
76: {
77: $file = $this->getFile($key);
78: $content = $this->prepareContent($value, $expire);
79: return (bool) file_put_contents($file, $content, LOCK_EX);
80: }
81:
82: 83: 84:
85: public function remove($key)
86: {
87: if (is_file($file = $this->getFile($key))) {
88: return unlink($file);
89: } else {
90: return false;
91: }
92: }
93:
94: 95: 96:
97: public function exists($key)
98: {
99: if (!is_file($file = $this->getFile($key))) {
100: return false;
101: }
102:
103: $content = $this->getContent($file);
104: if ($content && is_array($content) && time() < $content[0]) {
105: return true;
106: } else {
107: $this->remove($key);
108: return false;
109: }
110: }
111:
112: 113: 114:
115: public function add($key, $value, $expire = 0)
116: {
117: $file = $this->getFile($key);
118:
119: if (!is_file($file)) {
120:
121: if (!$handle = $this->openAndLock($file, 'wb', LOCK_EX | LOCK_NB)) {
122: return false;
123: }
124:
125: $rewrite = false;
126: } else {
127:
128: if (!$handle = $this->openAndLock($file, 'r+b', LOCK_EX)) {
129: return false;
130: }
131:
132:
133: if ($this->readAndVerify($handle, $file)) {
134: fclose($handle);
135: return false;
136: }
137:
138: $rewrite = true;
139: }
140:
141: $content = $this->prepareContent($value, $expire);
142: return $this->writeAndRelease($handle, $content, $rewrite);
143: }
144:
145: 146: 147:
148: public function replace($key, $value, $expire = 0)
149: {
150: if (!is_file($file = $this->getFile($key))) {
151: return false;
152: }
153:
154:
155: if (!$handle = $this->openAndLock($file, 'r+b', LOCK_EX)) {
156: return false;
157: }
158:
159: if (!$this->readAndVerify($handle, $file)) {
160: fclose($handle);
161: return false;
162: }
163:
164: $content = $this->prepareContent($value, $expire);
165: return $this->writeAndRelease($handle, $content, true);
166: }
167:
168: 169: 170:
171: public function incr($key, $offset = 1)
172: {
173: $file = $this->getFile($key);
174:
175: if (!is_file($file)) {
176: return $this->set($key, $offset) ? $offset : false;
177: }
178:
179:
180: if (!$handle = $this->openAndLock($file, 'r+b', LOCK_EX)) {
181: return false;
182: }
183:
184:
185: if (!$content = $this->readAndVerify($handle, $file)) {
186: $content = $this->prepareContent($offset, 0);
187: $result = $offset;
188: } else {
189: $result = $content[1] += $offset;
190: $content = $this->prepareContent($content[1], $content[0]);
191: }
192:
193:
194: return $this->writeAndRelease($handle, $content, true) ? $result : false;
195: }
196:
197:
198: 199: 200:
201: public function clear()
202: {
203: $result = true;
204: foreach (glob($this->dir . '/' . '*.' . $this->ext) as $file) {
205: $result = $result && @unlink($file);
206: }
207: return $result;
208: }
209:
210: 211: 212: 213: 214: 215:
216: public function getFile($key)
217: {
218: $key = str_replace($this->illegalChars, '_', $this->namespace . $key);
219: return $this->dir . '/' . $key . '.' . $this->ext;
220: }
221:
222: 223: 224: 225: 226: 227: 228:
229: public function setDir($dir)
230: {
231: if (!is_dir($dir) && !@mkdir($dir, 0755, true)) {
232: $message = sprintf('Failed to create directory "%s"', $dir);
233: ($e = error_get_last()) && $message .= ': ' . $e['message'];
234: throw new \RuntimeException($message);
235: }
236: $this->dir = realpath($dir);
237: return $this;
238: }
239:
240: 241: 242: 243: 244:
245: public function getDir()
246: {
247: return $this->dir;
248: }
249:
250: 251: 252: 253: 254: 255: 256: 257:
258: protected function openAndLock($file, $mode, $operation)
259: {
260: if (!$handle = fopen($file, $mode)) {
261: return false;
262: }
263: if (!flock($handle, $operation)) {
264: fclose($handle);
265: return false;
266: }
267: return $handle;
268: }
269:
270: 271: 272: 273: 274: 275: 276:
277: protected function readAndVerify($handle, $file)
278: {
279:
280: $content = fread($handle, filesize($file));
281: $content = @unserialize($content);
282:
283:
284: if ($content && is_array($content) && time() < $content[0]) {
285: return $content;
286: } else {
287: return false;
288: }
289: }
290:
291: 292: 293: 294: 295: 296:
297: protected function getContent($file)
298: {
299: return @unserialize(file_get_contents($file));
300: }
301:
302: 303: 304: 305: 306: 307: 308:
309: protected function prepareContent($content, $expire)
310: {
311:
312:
313:
314: return serialize(array(
315: 0 => $expire ? time() + $expire : 2147483647,
316: 1 => $content,
317: ));
318: }
319:
320: 321: 322: 323: 324: 325: 326: 327:
328: protected function writeAndRelease($handle, $content, $rewrite = false)
329: {
330: $rewrite && rewind($handle) && ftruncate($handle, 0);
331: $result = fwrite($handle, $content);
332: flock($handle, LOCK_UN);
333: fclose($handle);
334: return (bool) $result;
335: }
336: }
337: