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: class Cache extends Nette\Object implements \ArrayAccess
18: {
19:
20: const PRIORITY = 'priority',
21: EXPIRATION = 'expire',
22: EXPIRE = 'expire',
23: SLIDING = 'sliding',
24: TAGS = 'tags',
25: FILES = 'files',
26: ITEMS = 'items',
27: CONSTS = 'consts',
28: CALLBACKS = 'callbacks',
29: ALL = 'all';
30:
31:
32: const NAMESPACE_SEPARATOR = "\x00";
33:
34:
35: private $storage;
36:
37:
38: private $namespace;
39:
40:
41: private $key;
42:
43:
44: private $data;
45:
46:
47: public function __construct(IStorage $storage, $namespace = NULL)
48: {
49: $this->storage = $storage;
50: $this->namespace = $namespace . self::NAMESPACE_SEPARATOR;
51: }
52:
53:
54: 55: 56: 57:
58: public function getStorage()
59: {
60: return $this->storage;
61: }
62:
63:
64: 65: 66: 67:
68: public function getNamespace()
69: {
70: return (string) substr($this->namespace, 0, -1);
71: }
72:
73:
74: 75: 76: 77: 78:
79: public function derive($namespace)
80: {
81: $derived = new static($this->storage, $this->namespace . $namespace);
82: return $derived;
83: }
84:
85:
86: 87: 88: 89: 90: 91:
92: public function load($key, $fallback = NULL)
93: {
94: $data = $this->storage->read($this->generateKey($key));
95: if ($data === NULL && $fallback) {
96: return $this->save($key, function (& $dependencies) use ($fallback) {
97: return call_user_func_array($fallback, array(& $dependencies));
98: });
99: }
100: return $data;
101: }
102:
103:
104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120:
121: public function save($key, $data, array $dependencies = NULL)
122: {
123: $this->key = $this->data = NULL;
124: $key = $this->generateKey($key);
125:
126: if ($data instanceof Nette\Callback || $data instanceof \Closure) {
127: $this->storage->lock($key);
128: try {
129: $data = call_user_func_array($data, array(& $dependencies));
130: } catch (\Throwable $e) {
131: $this->storage->remove($key);
132: throw $e;
133: } catch (\Exception $e) {
134: $this->storage->remove($key);
135: throw $e;
136: }
137: }
138:
139: if ($data === NULL) {
140: $this->storage->remove($key);
141: } else {
142: $this->storage->write($key, $data, $this->completeDependencies($dependencies, $data));
143: return $data;
144: }
145: }
146:
147:
148: private function completeDependencies($dp, $data)
149: {
150:
151: if (isset($dp[self::EXPIRATION])) {
152: $dp[self::EXPIRATION] = Nette\Utils\DateTime::from($dp[self::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->key = $this->data = NULL;
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: if (is_array($function) && is_object($function[0])) {
218: $key[0][0] = get_class($function[0]);
219: }
220: return $this->load($key, function () use ($function, $key) {
221: return Callback::invokeArgs($function, array_slice($key, 1));
222: });
223: }
224:
225:
226: 227: 228: 229: 230: 231:
232: public function wrap($function, array $dependencies = NULL)
233: {
234: $cache = $this;
235: return function () use ($cache, $function, $dependencies) {
236: $key = array($function, func_get_args());
237: if (is_array($function) && is_object($function[0])) {
238: $key[0][0] = get_class($function[0]);
239: }
240: $data = $cache->load($key);
241: if ($data === NULL) {
242: $data = $cache->save($key, Callback::invokeArgs($function, $key[1]), $dependencies);
243: }
244: return $data;
245: };
246: }
247:
248:
249: 250: 251: 252: 253:
254: public function start($key)
255: {
256: $data = $this->load($key);
257: if ($data === NULL) {
258: return new OutputHelper($this, $key);
259: }
260: echo $data;
261: }
262:
263:
264: 265: 266: 267: 268: 269:
270: protected function generateKey($key)
271: {
272: return $this->namespace . md5(is_scalar($key) ? $key : serialize($key));
273: }
274:
275:
276:
277:
278:
279: 280: 281:
282: public function offsetSet($key, $data)
283: {
284: trigger_error('Using [] is deprecated; use Cache::save(key, data) instead.', E_USER_DEPRECATED);
285: $this->save($key, $data);
286: }
287:
288:
289: 290: 291:
292: public function offsetGet($key)
293: {
294: trigger_error('Using [] is deprecated; use Cache::load(key) instead.', E_USER_DEPRECATED);
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: public function offsetExists($key)
308: {
309: trigger_error('Using [] is deprecated; use Cache::load(key) !== NULL instead.', E_USER_DEPRECATED);
310: $this->key = $this->data = NULL;
311: return $this->offsetGet($key) !== NULL;
312: }
313:
314:
315: 316: 317:
318: public function offsetUnset($key)
319: {
320: trigger_error('Using [] is deprecated; use Cache::remove(key) instead.', E_USER_DEPRECATED);
321: $this->save($key, NULL);
322: }
323:
324:
325: 326: 327:
328: public function release()
329: {
330: trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
331: $this->key = $this->data = NULL;
332: }
333:
334:
335:
336:
337:
338: 339: 340: 341: 342:
343: public static function checkCallbacks($callbacks)
344: {
345: foreach ($callbacks as $callback) {
346: if (!call_user_func_array(array_shift($callback), $callback)) {
347: return FALSE;
348: }
349: }
350: return TRUE;
351: }
352:
353:
354: 355: 356: 357: 358: 359:
360: private static function checkConst($const, $value)
361: {
362: return defined($const) && constant($const) === $value;
363: }
364:
365:
366: 367: 368: 369: 370: 371:
372: private static function checkFile($file, $time)
373: {
374: return @filemtime($file) == $time;
375: }
376:
377: }
378: