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