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