PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/Nette/Caching/Cache.php

http://github.com/nette/nette
PHP | 414 lines | 179 code | 88 blank | 147 comment | 18 complexity | 98c012b0f04564ca283d91499d6fb786 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (http://nette.org)
  4. *
  5. * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. */
  10. namespace Nette\Caching;
  11. use Nette;
  12. /**
  13. * Implements the cache for a application.
  14. *
  15. * @author David Grudl
  16. *
  17. * @property-read IStorage $storage
  18. * @property-read string $namespace
  19. */
  20. class Cache extends Nette\Object implements \ArrayAccess
  21. {
  22. /** dependency */
  23. const PRIORITY = 'priority',
  24. EXPIRATION = 'expire',
  25. EXPIRE = 'expire',
  26. SLIDING = 'sliding',
  27. TAGS = 'tags',
  28. FILES = 'files',
  29. ITEMS = 'items',
  30. CONSTS = 'consts',
  31. CALLBACKS = 'callbacks',
  32. ALL = 'all';
  33. /** @internal */
  34. const NAMESPACE_SEPARATOR = "\x00";
  35. /** @var IStorage */
  36. private $storage;
  37. /** @var string */
  38. private $namespace;
  39. /** @var string last query cache used by offsetGet() */
  40. private $key;
  41. /** @var mixed last query cache used by offsetGet() */
  42. private $data;
  43. public function __construct(IStorage $storage, $namespace = NULL)
  44. {
  45. $this->storage = $storage;
  46. $this->namespace = $namespace . self::NAMESPACE_SEPARATOR;
  47. }
  48. /**
  49. * Returns cache storage.
  50. * @return IStorage
  51. */
  52. public function getStorage()
  53. {
  54. return $this->storage;
  55. }
  56. /**
  57. * Returns cache namespace.
  58. * @return string
  59. */
  60. public function getNamespace()
  61. {
  62. return (string) substr($this->namespace, 0, -1);
  63. }
  64. /**
  65. * Returns new nested cache object.
  66. * @param string
  67. * @return Cache
  68. */
  69. public function derive($namespace)
  70. {
  71. $derived = new static($this->storage, $this->namespace . $namespace);
  72. return $derived;
  73. }
  74. /**
  75. * Reads the specified item from the cache or generate it.
  76. * @param mixed key
  77. * @param callable
  78. * @return mixed|NULL
  79. */
  80. public function load($key, $fallback = NULL)
  81. {
  82. $data = $this->storage->read($this->namespace . md5(is_scalar($key) ? $key : serialize($key)));
  83. if ($data === NULL && $fallback) {
  84. return $this->save($key, callback($fallback));
  85. }
  86. return $data;
  87. }
  88. /**
  89. * Writes item into the cache.
  90. * Dependencies are:
  91. * - Cache::PRIORITY => (int) priority
  92. * - Cache::EXPIRATION => (timestamp) expiration
  93. * - Cache::SLIDING => (bool) use sliding expiration?
  94. * - Cache::TAGS => (array) tags
  95. * - Cache::FILES => (array|string) file names
  96. * - Cache::ITEMS => (array|string) cache items
  97. * - Cache::CONSTS => (array|string) cache items
  98. *
  99. * @param mixed key
  100. * @param mixed value
  101. * @param array dependencies
  102. * @return mixed value itself
  103. * @throws Nette\InvalidArgumentException
  104. */
  105. public function save($key, $data, array $dp = NULL)
  106. {
  107. $this->release();
  108. $key = $this->namespace . md5(is_scalar($key) ? $key : serialize($key));
  109. if ($data instanceof Nette\Callback || $data instanceof \Closure) {
  110. $this->storage->lock($key);
  111. $data = callback($data)->invokeArgs(array(&$dp));
  112. }
  113. if ($data === NULL) {
  114. $this->storage->remove($key);
  115. } else {
  116. $this->storage->write($key, $data, $this->completeDependencies($dp, $data));
  117. return $data;
  118. }
  119. }
  120. private function completeDependencies($dp, $data)
  121. {
  122. if (is_object($data)) {
  123. $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkSerializationVersion'), get_class($data),
  124. Nette\Reflection\ClassType::from($data)->getAnnotation('serializationVersion'));
  125. }
  126. // convert expire into relative amount of seconds
  127. if (isset($dp[Cache::EXPIRATION])) {
  128. $dp[Cache::EXPIRATION] = Nette\DateTime::from($dp[Cache::EXPIRATION])->format('U') - time();
  129. }
  130. // convert FILES into CALLBACKS
  131. if (isset($dp[self::FILES])) {
  132. //clearstatcache();
  133. foreach (array_unique((array) $dp[self::FILES]) as $item) {
  134. $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkFile'), $item, @filemtime($item)); // @ - stat may fail
  135. }
  136. unset($dp[self::FILES]);
  137. }
  138. // add namespaces to items
  139. if (isset($dp[self::ITEMS])) {
  140. $dp[self::ITEMS] = array_unique((array) $dp[self::ITEMS]);
  141. foreach ($dp[self::ITEMS] as $k => $item) {
  142. $dp[self::ITEMS][$k] = $this->namespace . md5(is_scalar($item) ? $item : serialize($item));
  143. }
  144. }
  145. // convert CONSTS into CALLBACKS
  146. if (isset($dp[self::CONSTS])) {
  147. foreach (array_unique((array) $dp[self::CONSTS]) as $item) {
  148. $dp[self::CALLBACKS][] = array(array(__CLASS__, 'checkConst'), $item, constant($item));
  149. }
  150. unset($dp[self::CONSTS]);
  151. }
  152. if (!is_array($dp)) {
  153. $dp = array();
  154. }
  155. return $dp;
  156. }
  157. /**
  158. * Removes item from the cache.
  159. * @param mixed key
  160. * @return void
  161. */
  162. public function remove($key)
  163. {
  164. $this->save($key, NULL);
  165. }
  166. /**
  167. * Removes items from the cache by conditions.
  168. * Conditions are:
  169. * - Cache::PRIORITY => (int) priority
  170. * - Cache::TAGS => (array) tags
  171. * - Cache::ALL => TRUE
  172. *
  173. * @param array
  174. * @return void
  175. */
  176. public function clean(array $conds = NULL)
  177. {
  178. $this->release();
  179. $this->storage->clean((array) $conds);
  180. }
  181. /**
  182. * Caches results of function/method calls.
  183. * @param mixed
  184. * @return mixed
  185. */
  186. public function call($function)
  187. {
  188. $key = func_get_args();
  189. return $this->load($key, function() use ($function, $key) {
  190. array_shift($key);
  191. return call_user_func_array($function, $key);
  192. });
  193. }
  194. /**
  195. * Caches results of function/method calls.
  196. * @param mixed
  197. * @return Closure
  198. */
  199. public function wrap($function)
  200. {
  201. $cache = $this;
  202. return function() use ($cache, $function) {
  203. $key = array($function, func_get_args());
  204. $data = $cache->load($key);
  205. if ($data === NULL) {
  206. $data = $cache->save($key, call_user_func_array($function, $key[1]));
  207. }
  208. return $data;
  209. };
  210. }
  211. /**
  212. * Starts the output cache.
  213. * @param mixed key
  214. * @return OutputHelper|NULL
  215. */
  216. public function start($key)
  217. {
  218. $data = $this->load($key);
  219. if ($data === NULL) {
  220. return new OutputHelper($this, $key);
  221. }
  222. echo $data;
  223. }
  224. /********************* interface ArrayAccess ****************d*g**/
  225. /**
  226. * Inserts (replaces) item into the cache (\ArrayAccess implementation).
  227. * @param mixed key
  228. * @param mixed
  229. * @return void
  230. * @throws Nette\InvalidArgumentException
  231. */
  232. public function offsetSet($key, $data)
  233. {
  234. $this->save($key, $data);
  235. }
  236. /**
  237. * Retrieves the specified item from the cache or NULL if the key is not found (\ArrayAccess implementation).
  238. * @param mixed key
  239. * @return mixed|NULL
  240. * @throws Nette\InvalidArgumentException
  241. */
  242. public function offsetGet($key)
  243. {
  244. $key = is_scalar($key) ? (string) $key : serialize($key);
  245. if ($this->key !== $key) {
  246. $this->key = $key;
  247. $this->data = $this->load($key);
  248. }
  249. return $this->data;
  250. }
  251. /**
  252. * Exists item in cache? (\ArrayAccess implementation).
  253. * @param mixed key
  254. * @return bool
  255. * @throws Nette\InvalidArgumentException
  256. */
  257. public function offsetExists($key)
  258. {
  259. $this->release();
  260. return $this->offsetGet($key) !== NULL;
  261. }
  262. /**
  263. * Removes the specified item from the cache.
  264. * @param mixed key
  265. * @return void
  266. * @throws Nette\InvalidArgumentException
  267. */
  268. public function offsetUnset($key)
  269. {
  270. $this->save($key, NULL);
  271. }
  272. /**
  273. * Discards the internal cache used by ArrayAccess.
  274. * @return void
  275. */
  276. public function release()
  277. {
  278. $this->key = $this->data = NULL;
  279. }
  280. /********************* dependency checkers ****************d*g**/
  281. /**
  282. * Checks CALLBACKS dependencies.
  283. * @param array
  284. * @return bool
  285. */
  286. public static function checkCallbacks($callbacks)
  287. {
  288. foreach ($callbacks as $callback) {
  289. $func = array_shift($callback);
  290. if (!call_user_func_array($func, $callback)) {
  291. return FALSE;
  292. }
  293. }
  294. return TRUE;
  295. }
  296. /**
  297. * Checks CONSTS dependency.
  298. * @param string
  299. * @param mixed
  300. * @return bool
  301. */
  302. private static function checkConst($const, $value)
  303. {
  304. return defined($const) && constant($const) === $value;
  305. }
  306. /**
  307. * Checks FILES dependency.
  308. * @param string
  309. * @param int
  310. * @return bool
  311. */
  312. private static function checkFile($file, $time)
  313. {
  314. return @filemtime($file) == $time; // @ - stat may fail
  315. }
  316. /**
  317. * Checks object @serializationVersion label.
  318. * @param string
  319. * @param mixed
  320. * @return bool
  321. */
  322. private static function checkSerializationVersion($class, $value)
  323. {
  324. return Nette\Reflection\ClassType::from($class)->getAnnotation('serializationVersion') === $value;
  325. }
  326. }