1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Caching;
9:
10: use Nette;
11:
12:
13: 14: 15:
16: class Cache
17: {
18: use Nette\SmartObject;
19:
20:
21: const
22: 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: 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: public function save($key, $data, array $dependencies = null)
165: {
166: $key = $this->generateKey($key);
167:
168: if ($data instanceof Nette\Callback || $data instanceof \Closure) {
169: if ($data instanceof Nette\Callback) {
170: trigger_error('Nette\Callback is deprecated, use closure or Nette\Utils\Callback::toClosure().', E_USER_DEPRECATED);
171: }
172: $this->storage->lock($key);
173: try {
174: $data = call_user_func_array($data, [&$dependencies]);
175: } catch (\Exception $e) {
176: $this->storage->remove($key);
177: throw $e;
178: } catch (\Throwable $e) {
179: $this->storage->remove($key);
180: throw $e;
181: }
182: }
183:
184: if ($data === null) {
185: $this->storage->remove($key);
186: } else {
187: $dependencies = $this->completeDependencies($dependencies);
188: if (isset($dependencies[self::EXPIRATION]) && $dependencies[self::EXPIRATION] <= 0) {
189: $this->storage->remove($key);
190: } else {
191: $this->storage->write($key, $data, $dependencies);
192: }
193: return $data;
194: }
195: }
196:
197:
198: private function completeDependencies($dp)
199: {
200:
201: if (isset($dp[self::EXPIRATION])) {
202: $dp[self::EXPIRATION] = Nette\Utils\DateTime::from($dp[self::EXPIRATION])->format('U') - time();
203: }
204:
205:
206: if (isset($dp[self::TAGS])) {
207: $dp[self::TAGS] = array_values((array) $dp[self::TAGS]);
208: }
209:
210:
211: if (isset($dp[self::NAMESPACES])) {
212: $dp[self::NAMESPACES] = array_values((array) $dp[self::NAMESPACES]);
213: }
214:
215:
216: if (isset($dp[self::FILES])) {
217: foreach (array_unique((array) $dp[self::FILES]) as $item) {
218: $dp[self::CALLBACKS][] = [[__CLASS__, 'checkFile'], $item, @filemtime($item) ?: null];
219: }
220: unset($dp[self::FILES]);
221: }
222:
223:
224: if (isset($dp[self::ITEMS])) {
225: $dp[self::ITEMS] = array_unique(array_map([$this, 'generateKey'], (array) $dp[self::ITEMS]));
226: }
227:
228:
229: if (isset($dp[self::CONSTS])) {
230: foreach (array_unique((array) $dp[self::CONSTS]) as $item) {
231: $dp[self::CALLBACKS][] = [[__CLASS__, 'checkConst'], $item, constant($item)];
232: }
233: unset($dp[self::CONSTS]);
234: }
235:
236: if (!is_array($dp)) {
237: $dp = [];
238: }
239: return $dp;
240: }
241:
242:
243: 244: 245: 246: 247:
248: public function remove($key)
249: {
250: $this->save($key, null);
251: }
252:
253:
254: 255: 256: 257: 258: 259: 260: 261:
262: public function clean(array $conditions = null)
263: {
264: $conditions = (array) $conditions;
265: if (isset($conditions[self::TAGS])) {
266: $conditions[self::TAGS] = array_values((array) $conditions[self::TAGS]);
267: }
268: $this->storage->clean($conditions);
269: }
270:
271:
272: 273: 274: 275: 276:
277: public function call($function)
278: {
279: $key = func_get_args();
280: if (is_array($function) && is_object($function[0])) {
281: $key[0][0] = get_class($function[0]);
282: }
283: return $this->load($key, function () use ($function, $key) {
284: return call_user_func_array($function, array_slice($key, 1));
285: });
286: }
287:
288:
289: 290: 291: 292: 293:
294: public function wrap($function, array $dependencies = null)
295: {
296: return function () use ($function, $dependencies) {
297: $key = [$function, func_get_args()];
298: if (is_array($function) && is_object($function[0])) {
299: $key[0][0] = get_class($function[0]);
300: }
301: $data = $this->load($key);
302: if ($data === null) {
303: $data = $this->save($key, call_user_func_array($function, $key[1]), $dependencies);
304: }
305: return $data;
306: };
307: }
308:
309:
310: 311: 312: 313: 314:
315: public function start($key)
316: {
317: $data = $this->load($key);
318: if ($data === null) {
319: return new OutputHelper($this, $key);
320: }
321: echo $data;
322: }
323:
324:
325: 326: 327: 328: 329:
330: protected function generateKey($key)
331: {
332: return $this->namespace . md5(is_scalar($key) ? (string) $key : serialize($key));
333: }
334:
335:
336:
337:
338:
339: 340: 341: 342:
343: public static function checkCallbacks(array $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: