1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Caching;
9:
10: use Nette,
11: 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, Callback::closure($fallback));
102: }
103: return $data;
104: }
105:
106:
107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123:
124: public function save($key, $data, array $dependencies = NULL)
125: {
126: $this->release();
127: $key = $this->generateKey($key);
128:
129: if ($data instanceof Nette\Callback || $data instanceof \Closure) {
130: $this->storage->lock($key);
131: $data = call_user_func_array($data, array(& $dependencies));
132: }
133:
134: if ($data === NULL) {
135: $this->storage->remove($key);
136: } else {
137: $this->storage->write($key, $data, $this->completeDependencies($dependencies, $data));
138: return $data;
139: }
140: }
141:
142:
143: private function completeDependencies($dp, $data)
144: {
145: if (is_object($data)) {
146: $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkSerializationVersion'), get_class($data),
147: Nette\Reflection\ClassType::from($data)->getAnnotation('serializationVersion'));
148: }
149:
150:
151: if (isset($dp[Cache::EXPIRATION])) {
152: $dp[Cache::EXPIRATION] = Nette\DateTime::from($dp[Cache::EXPIRATION])->format('U') - time();
153: }
154:
155:
156: if (isset($dp[self::FILES])) {
157:
158: foreach (array_unique((array) $dp[self::FILES]) as $item) {
159: $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkFile'), $item, @filemtime($item));
160: }
161: unset($dp[self::FILES]);
162: }
163:
164:
165: if (isset($dp[self::ITEMS])) {
166: $dp[self::ITEMS] = array_unique(array_map(array($this, 'generateKey'), (array) $dp[self::ITEMS]));
167: }
168:
169:
170: if (isset($dp[self::CONSTS])) {
171: foreach (array_unique((array) $dp[self::CONSTS]) as $item) {
172: $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkConst'), $item, constant($item));
173: }
174: unset($dp[self::CONSTS]);
175: }
176:
177: if (!is_array($dp)) {
178: $dp = array();
179: }
180: return $dp;
181: }
182:
183:
184: 185: 186: 187: 188:
189: public function remove($key)
190: {
191: $this->save($key, NULL);
192: }
193:
194:
195: 196: 197: 198: 199: 200: 201: 202:
203: public function clean(array $conditions = NULL)
204: {
205: $this->release();
206: $this->storage->clean((array) $conditions);
207: }
208:
209:
210: 211: 212: 213: 214:
215: public function call($function)
216: {
217: $key = func_get_args();
218: $key[0] = Callback::toReflection($function);
219: return $this->load($key, function() use ($function, $key) {
220: return Callback::invokeArgs($function, array_slice($key, 1));
221: });
222: }
223:
224:
225: 226: 227: 228: 229: 230:
231: public function wrap($function, array $dependencies = NULL)
232: {
233: $cache = $this;
234: return function() use ($cache, $function, $dependencies) {
235: $key = array(Callback::toReflection($function), func_get_args());
236: $data = $cache->load($key);
237: if ($data === NULL) {
238: $data = $cache->save($key, Callback::invokeArgs($function, $key[1]), $dependencies);
239: }
240: return $data;
241: };
242: }
243:
244:
245: 246: 247: 248: 249:
250: public function start($key)
251: {
252: $data = $this->load($key);
253: if ($data === NULL) {
254: return new OutputHelper($this, $key);
255: }
256: echo $data;
257: }
258:
259:
260: 261: 262: 263: 264: 265:
266: protected function generateKey($key)
267: {
268: return $this->namespace . md5(is_scalar($key) ? $key : serialize($key));
269: }
270:
271:
272:
273:
274:
275: 276: 277: 278: 279: 280: 281:
282: public function offsetSet($key, $data)
283: {
284: $this->save($key, $data);
285: }
286:
287:
288: 289: 290: 291: 292: 293:
294: public function offsetGet($key)
295: {
296: $key = is_scalar($key) ? (string) $key : serialize($key);
297: if ($this->key !== $key) {
298: $this->key = $key;
299: $this->data = $this->load($key);
300: }
301: return $this->data;
302: }
303:
304:
305: 306: 307: 308: 309: 310:
311: public function offsetExists($key)
312: {
313: $this->release();
314: return $this->offsetGet($key) !== NULL;
315: }
316:
317:
318: 319: 320: 321: 322: 323:
324: public function offsetUnset($key)
325: {
326: $this->save($key, NULL);
327: }
328:
329:
330: 331: 332: 333:
334: public function release()
335: {
336: $this->key = $this->data = NULL;
337: }
338:
339:
340:
341:
342:
343: 344: 345: 346: 347:
348: public static function checkCallbacks($callbacks)
349: {
350: foreach ($callbacks as $callback) {
351: if (!call_user_func_array(array_shift($callback), $callback)) {
352: return FALSE;
353: }
354: }
355: return TRUE;
356: }
357:
358:
359: 360: 361: 362: 363: 364:
365: private static function checkConst($const, $value)
366: {
367: return defined($const) && constant($const) === $value;
368: }
369:
370:
371: 372: 373: 374: 375: 376:
377: private static function checkFile($file, $time)
378: {
379: return @filemtime($file) == $time;
380: }
381:
382:
383: 384: 385: 386: 387: 388:
389: private static function checkSerializationVersion($class, $value)
390: {
391: return Nette\Reflection\ClassType::from($class)->getAnnotation('serializationVersion') === $value;
392: }
393:
394: }
395: