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