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