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