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