1: <?php
2:
3: 4: 5: 6: 7:
8:
9:
10:
11: 12: 13: 14: 15: 16: 17: 18: 19:
20: class Cache extends Object implements ArrayAccess
21: {
22:
23: const PRIORITY = 'priority',
24: EXPIRATION = 'expire',
25: EXPIRE = 'expire',
26: SLIDING = 'sliding',
27: TAGS = 'tags',
28: FILES = 'files',
29: ITEMS = 'items',
30: CONSTS = 'consts',
31: CALLBACKS = 'callbacks',
32: ALL = 'all';
33:
34:
35: const NAMESPACE_SEPARATOR = "\x00";
36:
37:
38: private $storage;
39:
40:
41: private $namespace;
42:
43:
44: private $key;
45:
46:
47: private $data;
48:
49:
50: public function __construct(ICacheStorage $storage, $namespace = NULL)
51: {
52: $this->storage = $storage;
53: $this->namespace = $namespace . self::NAMESPACE_SEPARATOR;
54: }
55:
56:
57: 58: 59: 60:
61: public function getStorage()
62: {
63: return $this->storage;
64: }
65:
66:
67: 68: 69: 70:
71: public function getNamespace()
72: {
73: return (string) substr($this->namespace, 0, -1);
74: }
75:
76:
77: 78: 79: 80: 81:
82: public function derive($namespace)
83: {
84: $derived = new self($this->storage, $this->namespace . $namespace);
85: return $derived;
86: }
87:
88:
89: 90: 91: 92: 93: 94:
95: public function load($key, $fallback = NULL)
96: {
97: $data = $this->storage->read($this->generateKey($key));
98: if ($data === NULL && $fallback) {
99: return $this->save($key, new Callback($fallback));
100: }
101: return $data;
102: }
103:
104:
105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121:
122: public function save($key, $data, array $dependencies = NULL)
123: {
124: $this->release();
125: $key = $this->generateKey($key);
126:
127: if ($data instanceof Callback || $data instanceof Closure) {
128: $this->storage->lock($key);
129: $data = Callback::create($data)->invokeArgs(array(& $dependencies));
130: }
131:
132: if ($data === NULL) {
133: $this->storage->remove($key);
134: } else {
135: $this->storage->write($key, $data, $this->completeDependencies($dependencies, $data));
136: return $data;
137: }
138: }
139:
140:
141: private function completeDependencies($dp, $data)
142: {
143: if (is_object($data)) {
144: $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkSerializationVersion'), get_class($data),
145: ClassReflection::from($data)->getAnnotation('serializationVersion'));
146: }
147:
148:
149: if (isset($dp[Cache::EXPIRATION])) {
150: $dp[Cache::EXPIRATION] = DateTime53::from($dp[Cache::EXPIRATION])->format('U') - time();
151: }
152:
153:
154: if (isset($dp[self::FILES])) {
155: foreach (array_unique((array) $dp[self::FILES]) as $item) {
156: $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkFile'), $item, @filemtime($item));
157: }
158: unset($dp[self::FILES]);
159: }
160:
161:
162: if (isset($dp[self::ITEMS])) {
163: $dp[self::ITEMS] = array_unique(array_map(array($this, 'generateKey'), (array) $dp[self::ITEMS]));
164: }
165:
166:
167: if (isset($dp[self::CONSTS])) {
168: foreach (array_unique((array) $dp[self::CONSTS]) as $item) {
169: $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkConst'), $item, constant($item));
170: }
171: unset($dp[self::CONSTS]);
172: }
173:
174: if (!is_array($dp)) {
175: $dp = array();
176: }
177: return $dp;
178: }
179:
180:
181: 182: 183: 184: 185:
186: public function remove($key)
187: {
188: $this->save($key, NULL);
189: }
190:
191:
192: 193: 194: 195: 196: 197: 198: 199:
200: public function clean(array $conditions = NULL)
201: {
202: $this->release();
203: $this->storage->clean((array) $conditions);
204: }
205:
206:
207: 208: 209: 210: 211:
212: public function call($function)
213: {
214: $key = func_get_args();
215: return $this->load($key, create_function('', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('function'=>$function,'key'=> $key)).'-1], EXTR_REFS);
216: return Callback::create($function)->invokeArgs(array_slice($key, 1));
217: '));
218: }
219:
220:
221: 222: 223: 224: 225: 226:
227: public function wrap($function, array $dependencies = NULL)
228: {
229: $cache = $this;
230: return create_function('', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('cache'=>$cache,'function'=> $function,'dependencies'=> $dependencies)).'-1], EXTR_REFS);
231: $_args=func_get_args(); $key = array($function, $_args);
232: $data = $cache->load($key);
233: if ($data === NULL) {
234: $data = $cache->save($key, Callback::create($function)->invokeArgs($key[1]), $dependencies);
235: }
236: return $data;
237: ');
238: }
239:
240:
241: 242: 243: 244: 245:
246: public function start($key)
247: {
248: $data = $this->load($key);
249: if ($data === NULL) {
250: return new CachingHelper($this, $key);
251: }
252: echo $data;
253: }
254:
255:
256: 257: 258: 259: 260: 261:
262: protected function generateKey($key)
263: {
264: return $this->namespace . md5(is_scalar($key) ? $key : serialize($key));
265: }
266:
267:
268:
269:
270:
271: 272: 273: 274: 275: 276: 277:
278: public function offsetSet($key, $data)
279: {
280: $this->save($key, $data);
281: }
282:
283:
284: 285: 286: 287: 288: 289:
290: public function offsetGet($key)
291: {
292: $key = is_scalar($key) ? (string) $key : serialize($key);
293: if ($this->key !== $key) {
294: $this->key = $key;
295: $this->data = $this->load($key);
296: }
297: return $this->data;
298: }
299:
300:
301: 302: 303: 304: 305: 306:
307: public function offsetExists($key)
308: {
309: $this->release();
310: return $this->offsetGet($key) !== NULL;
311: }
312:
313:
314: 315: 316: 317: 318: 319:
320: public function offsetUnset($key)
321: {
322: $this->save($key, NULL);
323: }
324:
325:
326: 327: 328: 329:
330: public function release()
331: {
332: $this->key = $this->data = NULL;
333: }
334:
335:
336:
337:
338:
339: 340: 341: 342: 343:
344: public static function checkCallbacks($callbacks)
345: {
346: foreach ($callbacks as $callback) {
347: if (!call_user_func_array(array_shift($callback), $callback)) {
348: return FALSE;
349: }
350: }
351: return TRUE;
352: }
353:
354:
355: 356: 357: 358: 359: 360:
361: private static function checkConst($const, $value)
362: {
363: return defined($const) && constant($const) === $value;
364: }
365:
366:
367: 368: 369: 370: 371: 372:
373: private static function checkFile($file, $time)
374: {
375: return @filemtime($file) == $time;
376: }
377:
378:
379: 380: 381: 382: 383: 384:
385: private static function checkSerializationVersion($class, $value)
386: {
387: return ClassReflection::from($class)->getAnnotation('serializationVersion') === $value;
388: }
389:
390: }
391: