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