Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • Cache
  • OutputHelper

Interfaces

  • IBulkReader
  • IStorage
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Caching;
  9: 
 10: use Nette;
 11: 
 12: 
 13: /**
 14:  * Implements the cache for a application.
 15:  */
 16: class Cache
 17: {
 18:     use Nette\SmartObject;
 19: 
 20:     /** dependency */
 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:     /** @internal */
 35:     const NAMESPACE_SEPARATOR = "\x00";
 36: 
 37:     /** @var IStorage */
 38:     private $storage;
 39: 
 40:     /** @var string */
 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:      * Returns cache storage.
 53:      * @return IStorage
 54:      */
 55:     public function getStorage()
 56:     {
 57:         return $this->storage;
 58:     }
 59: 
 60: 
 61:     /**
 62:      * Returns cache namespace.
 63:      * @return string
 64:      */
 65:     public function getNamespace()
 66:     {
 67:         return (string) substr($this->namespace, 0, -1);
 68:     }
 69: 
 70: 
 71:     /**
 72:      * Returns new nested cache object.
 73:      * @param  string  $namespace
 74:      * @return static
 75:      */
 76:     public function derive($namespace)
 77:     {
 78:         $derived = new static($this->storage, $this->namespace . $namespace);
 79:         return $derived;
 80:     }
 81: 
 82: 
 83:     /**
 84:      * Reads the specified item from the cache or generate it.
 85:      * @param  mixed  $key
 86:      * @param  callable  $fallback
 87:      * @return mixed
 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:      * Reads multiple items from the cache.
103:      * @param  callable  $fallback
104:      * @return array
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:      * Writes item into the cache.
151:      * Dependencies are:
152:      * - Cache::PRIORITY => (int) priority
153:      * - Cache::EXPIRATION => (timestamp) expiration
154:      * - Cache::SLIDING => (bool) use sliding expiration?
155:      * - Cache::TAGS => (array) tags
156:      * - Cache::FILES => (array|string) file names
157:      * - Cache::ITEMS => (array|string) cache items
158:      * - Cache::CONSTS => (array|string) cache items
159:      *
160:      * @param  mixed  $key
161:      * @param  mixed  $data
162:      * @return mixed  value itself
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:         // convert expire into relative amount of seconds
201:         if (isset($dp[self::EXPIRATION])) {
202:             $dp[self::EXPIRATION] = Nette\Utils\DateTime::from($dp[self::EXPIRATION])->format('U') - time();
203:         }
204: 
205:         // make list from TAGS
206:         if (isset($dp[self::TAGS])) {
207:             $dp[self::TAGS] = array_values((array) $dp[self::TAGS]);
208:         }
209: 
210:         // make list from NAMESPACES
211:         if (isset($dp[self::NAMESPACES])) {
212:             $dp[self::NAMESPACES] = array_values((array) $dp[self::NAMESPACES]);
213:         }
214: 
215:         // convert FILES into CALLBACKS
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]; // @ - stat may fail
219:             }
220:             unset($dp[self::FILES]);
221:         }
222: 
223:         // add namespaces to items
224:         if (isset($dp[self::ITEMS])) {
225:             $dp[self::ITEMS] = array_unique(array_map([$this, 'generateKey'], (array) $dp[self::ITEMS]));
226:         }
227: 
228:         // convert CONSTS into CALLBACKS
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:      * Removes item from the cache.
245:      * @param  mixed  $key
246:      * @return void
247:      */
248:     public function remove($key)
249:     {
250:         $this->save($key, null);
251:     }
252: 
253: 
254:     /**
255:      * Removes items from the cache by conditions.
256:      * Conditions are:
257:      * - Cache::PRIORITY => (int) priority
258:      * - Cache::TAGS => (array) tags
259:      * - Cache::ALL => true
260:      * @return void
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:      * Caches results of function/method calls.
274:      * @param  callable  $function
275:      * @return mixed
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:      * Caches results of function/method calls.
291:      * @param  callable  $function
292:      * @return \Closure
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:      * Starts the output cache.
312:      * @param  mixed  $key
313:      * @return OutputHelper|null
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:      * Generates internal cache key.
327:      * @param  mixed  $key
328:      * @return string
329:      */
330:     protected function generateKey($key)
331:     {
332:         return $this->namespace . md5(is_scalar($key) ? (string) $key : serialize($key));
333:     }
334: 
335: 
336:     /********************* dependency checkers ****************d*g**/
337: 
338: 
339:     /**
340:      * Checks CALLBACKS dependencies.
341:      * @return bool
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:      * Checks CONSTS dependency.
356:      * @param  string  $const
357:      * @param  mixed  $value
358:      * @return bool
359:      */
360:     private static function checkConst($const, $value)
361:     {
362:         return defined($const) && constant($const) === $value;
363:     }
364: 
365: 
366:     /**
367:      * Checks FILES dependency.
368:      * @param  string  $file
369:      * @param  int|null  $time
370:      * @return bool
371:      */
372:     private static function checkFile($file, $time)
373:     {
374:         return @filemtime($file) == $time; // @ - stat may fail
375:     }
376: }
377: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0