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::FILES])) {
204: foreach (array_unique((array) $dp[self::FILES]) as $item) {
205: $dp[self::CALLBACKS][] = [[__CLASS__, 'checkFile'], $item, @filemtime($item)];
206: }
207: unset($dp[self::FILES]);
208: }
209:
210:
211: if (isset($dp[self::ITEMS])) {
212: $dp[self::ITEMS] = array_unique(array_map([$this, 'generateKey'], (array) $dp[self::ITEMS]));
213: }
214:
215:
216: if (isset($dp[self::CONSTS])) {
217: foreach (array_unique((array) $dp[self::CONSTS]) as $item) {
218: $dp[self::CALLBACKS][] = [[__CLASS__, 'checkConst'], $item, constant($item)];
219: }
220: unset($dp[self::CONSTS]);
221: }
222:
223: if (!is_array($dp)) {
224: $dp = [];
225: }
226: return $dp;
227: }
228:
229:
230: 231: 232: 233: 234:
235: public function remove($key)
236: {
237: $this->save($key, NULL);
238: }
239:
240:
241: 242: 243: 244: 245: 246: 247: 248:
249: public function clean(array $conditions = NULL)
250: {
251: $this->storage->clean((array) $conditions);
252: }
253:
254:
255: 256: 257: 258: 259:
260: public function call($function)
261: {
262: $key = func_get_args();
263: if (is_array($function) && is_object($function[0])) {
264: $key[0][0] = get_class($function[0]);
265: }
266: return $this->load($key, function () use ($function, $key) {
267: return Callback::invokeArgs($function, array_slice($key, 1));
268: });
269: }
270:
271:
272: 273: 274: 275: 276: 277:
278: public function wrap($function, array $dependencies = NULL)
279: {
280: return function () use ($function, $dependencies) {
281: $key = [$function, func_get_args()];
282: if (is_array($function) && is_object($function[0])) {
283: $key[0][0] = get_class($function[0]);
284: }
285: $data = $this->load($key);
286: if ($data === NULL) {
287: $data = $this->save($key, Callback::invokeArgs($function, $key[1]), $dependencies);
288: }
289: return $data;
290: };
291: }
292:
293:
294: 295: 296: 297: 298:
299: public function start($key)
300: {
301: $data = $this->load($key);
302: if ($data === NULL) {
303: return new OutputHelper($this, $key);
304: }
305: echo $data;
306: }
307:
308:
309: 310: 311: 312: 313: 314:
315: protected function generateKey($key)
316: {
317: return $this->namespace . md5(is_scalar($key) ? $key : serialize($key));
318: }
319:
320:
321:
322:
323:
324: 325: 326: 327: 328:
329: public static function checkCallbacks($callbacks)
330: {
331: foreach ($callbacks as $callback) {
332: if (!call_user_func_array(array_shift($callback), $callback)) {
333: return FALSE;
334: }
335: }
336: return TRUE;
337: }
338:
339:
340: 341: 342: 343: 344: 345:
346: private static function checkConst($const, $value)
347: {
348: return defined($const) && constant($const) === $value;
349: }
350:
351:
352: 353: 354: 355: 356: 357:
358: private static function checkFile($file, $time)
359: {
360: return @filemtime($file) == $time;
361: }
362:
363: }
364: