cache.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. <?php
  2. /**
  3. * Caching for CakePHP.
  4. *
  5. *
  6. * PHP 5
  7. *
  8. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  9. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  10. *
  11. * Licensed under The MIT License
  12. * Redistributions of files must retain the above copyright notice.
  13. *
  14. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  15. * @link http://cakephp.org CakePHP(tm) Project
  16. * @package cake.libs
  17. * @since CakePHP(tm) v 1.2.0.4933
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. */
  20. /**
  21. * Cache provides a consistent interface to Caching in your application. It allows you
  22. * to use several different Cache engines, without coupling your application to a specific
  23. * implementation. It also allows you to change out cache storage or configuration without effecting
  24. * the rest of your application.
  25. *
  26. * You can configure Cache engines in your application's `bootstrap.php` file. A sample configuration would
  27. * be
  28. *
  29. * {{{
  30. * Cache::config('shared', array(
  31. * 'engine' => 'Apc',
  32. * 'prefix' => 'my_app_'
  33. * ));
  34. * }}}
  35. *
  36. * This would configure an APC cache engine to the 'shared' alias. You could then read and write
  37. * to that cache alias by using it for the `$config` parameter in the various Cache methods. In
  38. * general all Cache operations are supported by all cache engines. However, Cache::increment() and
  39. * Cache::decrement() are not supported by File caching.
  40. *
  41. * @package cake.libs
  42. */
  43. class Cache {
  44. /**
  45. * Cache configuration stack
  46. * Keeps the permanent/default settings for each cache engine.
  47. * These settings are used to reset the engines after temporary modification.
  48. *
  49. * @var array
  50. */
  51. protected static $_config = array();
  52. /**
  53. * Whether to reset the settings with the next call to Cache::set();
  54. *
  55. * @var array
  56. */
  57. protected static $_reset = false;
  58. /**
  59. * Engine instances keyed by configuration name.
  60. *
  61. * @var array
  62. */
  63. protected static $_engines = array();
  64. /**
  65. * Set the cache configuration to use. config() can
  66. * both create new configurations, return the settings for already configured
  67. * configurations.
  68. *
  69. * To create a new configuration, or to modify an existing configuration permanently:
  70. *
  71. * `Cache::config('my_config', array('engine' => 'File', 'path' => TMP));`
  72. *
  73. * If you need to modify a configuration temporarily, use Cache::set().
  74. * To get the settings for a configuration:
  75. *
  76. * `Cache::config('default');`
  77. *
  78. * There are 4 built-in caching engines:
  79. *
  80. * - `FileEngine` - Uses simple files to store content. Poor performance, but good for
  81. * storing large objects, or things that are not IO sensitive.
  82. * - `ApcEngine` - Uses the APC object cache, one of the fastest caching engines.
  83. * - `MemcacheEngine` - Uses the PECL::Memcache extension and Memcached for storage.
  84. * Fast reads/writes, and benefits from memcache being distributed.
  85. * - `XcacheEngine` - Uses the Xcache extension, an alternative to APC.
  86. *
  87. * @see app/config/core.php for configuration settings
  88. * @param string $name Name of the configuration
  89. * @param array $settings Optional associative array of settings passed to the engine
  90. * @return array(engine, settings) on success, false on failure
  91. * @throws CacheException
  92. */
  93. public static function config($name = null, $settings = array()) {
  94. if (is_array($name)) {
  95. $settings = $name;
  96. }
  97. $current = array();
  98. if (isset(self::$_config[$name])) {
  99. $current = self::$_config[$name];
  100. }
  101. if (!empty($settings)) {
  102. self::$_config[$name] = array_merge($current, $settings);
  103. }
  104. if (empty(self::$_config[$name]['engine'])) {
  105. return false;
  106. }
  107. $engine = self::$_config[$name]['engine'];
  108. if (!isset(self::$_engines[$name])) {
  109. self::_buildEngine($name);
  110. $settings = self::$_config[$name] = self::settings($name);
  111. } elseif ($settings = self::set(self::$_config[$name], null, $name)) {
  112. self::$_config[$name] = $settings;
  113. }
  114. return compact('engine', 'settings');
  115. }
  116. /**
  117. * Finds and builds the instance of the required engine class.
  118. *
  119. * @param string $name Name of the config array that needs an engine instance built
  120. * @return void
  121. */
  122. protected static function _buildEngine($name) {
  123. $config = self::$_config[$name];
  124. list($plugin, $class) = pluginSplit($config['engine']);
  125. $cacheClass = $class . 'Engine';
  126. if (!class_exists($cacheClass) && self::_loadEngine($class, $plugin) === false) {
  127. return false;
  128. }
  129. $cacheClass = $class . 'Engine';
  130. if (!is_subclass_of($cacheClass, 'CacheEngine')) {
  131. throw new CacheException(__('Cache engines must use CacheEngine as a base class.'));
  132. }
  133. self::$_engines[$name] = new $cacheClass();
  134. if (self::$_engines[$name]->init($config)) {
  135. if (time() % self::$_engines[$name]->settings['probability'] === 0) {
  136. self::$_engines[$name]->gc();
  137. }
  138. return true;
  139. }
  140. return false;
  141. }
  142. /**
  143. * Returns an array containing the currently configured Cache settings.
  144. *
  145. * @return array Array of configured Cache config names.
  146. */
  147. public static function configured() {
  148. return array_keys(self::$_config);
  149. }
  150. /**
  151. * Drops a cache engine. Deletes the cache configuration information
  152. * If the deleted configuration is the last configuration using an certain engine,
  153. * the Engine instance is also unset.
  154. *
  155. * @param string $name A currently configured cache config you wish to remove.
  156. * @return boolen success of the removal, returns false when the config does not exist.
  157. */
  158. public static function drop($name) {
  159. if (!isset(self::$_config[$name])) {
  160. return false;
  161. }
  162. unset(self::$_config[$name], self::$_engines[$name]);
  163. return true;
  164. }
  165. /**
  166. * Tries to find and include a file for a cache engine and returns object instance
  167. *
  168. * @param $name Name of the engine (without 'Engine')
  169. * @return mixed $engine object or null
  170. */
  171. protected static function _loadEngine($name, $plugin = null) {
  172. if ($plugin) {
  173. return App::import('Lib', $plugin . '.cache' . DS . $name, false);
  174. } else {
  175. $core = App::core();
  176. $path = $core['libs'][0] . 'cache' . DS . strtolower($name) . '.php';
  177. if (file_exists($path)) {
  178. require $path;
  179. return true;
  180. }
  181. return App::import('Lib', 'cache' . DS . $name, false);
  182. }
  183. }
  184. /**
  185. * Temporarily change the settings on a cache config. The settings will persist for the next write
  186. * operation (write, decrement, increment, clear). Any reads that are done before the write, will
  187. * use the modified settings. If `$settings` is empty, the settings will be reset to the
  188. * original configuration.
  189. *
  190. * Can be called with 2 or 3 parameters. To set multiple values at once.
  191. *
  192. * `Cache::set(array('duration' => '+30 minutes'), 'my_config');`
  193. *
  194. * Or to set one value.
  195. *
  196. * `Cache::set('duration', '+30 minutes', 'my_config');`
  197. *
  198. * To reset a config back to the originally configured values.
  199. *
  200. * `Cache::set(null, 'my_config');`
  201. *
  202. * @param mixed $settings Optional string for simple name-value pair or array
  203. * @param string $value Optional for a simple name-value pair
  204. * @param string $config The configuration name you are changing. Defaults to 'default'
  205. * @return array Array of settings.
  206. */
  207. public static function set($settings = array(), $value = null, $config = 'default') {
  208. if (is_array($settings) && $value !== null) {
  209. $config = $value;
  210. }
  211. if (!isset(self::$_config[$config]) || !isset(self::$_engines[$config])) {
  212. return false;
  213. }
  214. if (!empty($settings)) {
  215. self::$_reset = true;
  216. }
  217. if (self::$_reset === true) {
  218. if (empty($settings)) {
  219. self::$_reset = false;
  220. $settings = self::$_config[$config];
  221. } else {
  222. if (is_string($settings) && $value !== null) {
  223. $settings = array($settings => $value);
  224. }
  225. $settings = array_merge(self::$_config[$config], $settings);
  226. if (isset($settings['duration']) && !is_numeric($settings['duration'])) {
  227. $settings['duration'] = strtotime($settings['duration']) - time();
  228. }
  229. }
  230. self::$_engines[$config]->settings = $settings;
  231. }
  232. return self::settings($config);
  233. }
  234. /**
  235. * Garbage collection
  236. *
  237. * Permanently remove all expired and deleted data
  238. *
  239. * @param string $config The config name you wish to have garbage collected. Defaults to 'default'
  240. * @return void
  241. */
  242. public static function gc($config = 'default') {
  243. self::$_engines[$config]->gc();
  244. }
  245. /**
  246. * Write data for key into cache. Will automatically use the currently
  247. * active cache configuration. To set the currently active configuration use
  248. * Cache::config()
  249. *
  250. * ### Usage:
  251. *
  252. * Writing to the active cache config:
  253. *
  254. * `Cache::write('cached_data', $data);`
  255. *
  256. * Writing to a specific cache config:
  257. *
  258. * `Cache::write('cached_data', $data, 'long_term');`
  259. *
  260. * @param string $key Identifier for the data
  261. * @param mixed $value Data to be cached - anything except a resource
  262. * @param string $config Optional string configuration name to write to. Defaults to 'default'
  263. * @return boolean True if the data was successfully cached, false on failure
  264. */
  265. public static function write($key, $value, $config = 'default') {
  266. $settings = self::settings($config);
  267. if (empty($settings)) {
  268. return null;
  269. }
  270. if (!self::isInitialized($config)) {
  271. return false;
  272. }
  273. $key = self::$_engines[$config]->key($key);
  274. if (!$key || is_resource($value)) {
  275. return false;
  276. }
  277. $success = self::$_engines[$config]->write($settings['prefix'] . $key, $value, $settings['duration']);
  278. self::set(null, $config);
  279. if ($success === false && $value !== '') {
  280. trigger_error(
  281. __("%s cache was unable to write '%s' to cache", $config, $key),
  282. E_USER_WARNING
  283. );
  284. }
  285. return $success;
  286. }
  287. /**
  288. * Read a key from the cache. Will automatically use the currently
  289. * active cache configuration. To set the currently active configuration use
  290. * Cache::config()
  291. *
  292. * ### Usage:
  293. *
  294. * Reading from the active cache configuration.
  295. *
  296. * `Cache::read('my_data');`
  297. *
  298. * Reading from a specific cache configuration.
  299. *
  300. * `Cache::read('my_data', 'long_term');`
  301. *
  302. * @param string $key Identifier for the data
  303. * @param string $config optional name of the configuration to use. Defaults to 'default'
  304. * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
  305. */
  306. public static function read($key, $config = 'default') {
  307. $settings = self::settings($config);
  308. if (empty($settings)) {
  309. return null;
  310. }
  311. if (!self::isInitialized($config)) {
  312. return false;
  313. }
  314. $key = self::$_engines[$config]->key($key);
  315. if (!$key) {
  316. return false;
  317. }
  318. return self::$_engines[$config]->read($settings['prefix'] . $key);
  319. }
  320. /**
  321. * Increment a number under the key and return incremented value.
  322. *
  323. * @param string $key Identifier for the data
  324. * @param integer $offset How much to add
  325. * @param string $config Optional string configuration name. Defaults to 'default'
  326. * @return mixed new value, or false if the data doesn't exist, is not integer,
  327. * or if there was an error fetching it.
  328. */
  329. public static function increment($key, $offset = 1, $config = 'default') {
  330. $settings = self::settings($config);
  331. if (empty($settings)) {
  332. return null;
  333. }
  334. if (!self::isInitialized($config)) {
  335. return false;
  336. }
  337. $key = self::$_engines[$config]->key($key);
  338. if (!$key || !is_integer($offset) || $offset < 0) {
  339. return false;
  340. }
  341. $success = self::$_engines[$config]->increment($settings['prefix'] . $key, $offset);
  342. self::set(null, $config);
  343. return $success;
  344. }
  345. /**
  346. * Decrement a number under the key and return decremented value.
  347. *
  348. * @param string $key Identifier for the data
  349. * @param integer $offset How much to substract
  350. * @param string $config Optional string configuration name. Defaults to 'default'
  351. * @return mixed new value, or false if the data doesn't exist, is not integer,
  352. * or if there was an error fetching it
  353. */
  354. public static function decrement($key, $offset = 1, $config = 'default') {
  355. $settings = self::settings($config);
  356. if (empty($settings)) {
  357. return null;
  358. }
  359. if (!self::isInitialized($config)) {
  360. return false;
  361. }
  362. $key = self::$_engines[$config]->key($key);
  363. if (!$key || !is_integer($offset) || $offset < 0) {
  364. return false;
  365. }
  366. $success = self::$_engines[$config]->decrement($settings['prefix'] . $key, $offset);
  367. self::set(null, $config);
  368. return $success;
  369. }
  370. /**
  371. * Delete a key from the cache.
  372. *
  373. * ### Usage:
  374. *
  375. * Deleting from the active cache configuration.
  376. *
  377. * `Cache::delete('my_data');`
  378. *
  379. * Deleting from a specific cache configuration.
  380. *
  381. * `Cache::delete('my_data', 'long_term');`
  382. *
  383. * @param string $key Identifier for the data
  384. * @param string $config name of the configuration to use. Defaults to 'default'
  385. * @return boolean True if the value was succesfully deleted, false if it didn't exist or couldn't be removed
  386. */
  387. public static function delete($key, $config = 'default') {
  388. $settings = self::settings($config);
  389. if (empty($settings)) {
  390. return null;
  391. }
  392. if (!self::isInitialized($config)) {
  393. return false;
  394. }
  395. $key = self::$_engines[$config]->key($key);
  396. if (!$key) {
  397. return false;
  398. }
  399. $success = self::$_engines[$config]->delete($settings['prefix'] . $key);
  400. self::set(null, $config);
  401. return $success;
  402. }
  403. /**
  404. * Delete all keys from the cache.
  405. *
  406. * @param boolean $check if true will check expiration, otherwise delete all
  407. * @param string $config name of the configuration to use. Defaults to 'default'
  408. * @return boolean True if the cache was succesfully cleared, false otherwise
  409. */
  410. public static function clear($check = false, $config = 'default') {
  411. if (!self::isInitialized($config)) {
  412. return false;
  413. }
  414. $success = self::$_engines[$config]->clear($check);
  415. self::set(null, $config);
  416. return $success;
  417. }
  418. /**
  419. * Check if Cache has initialized a working config for the given name.
  420. *
  421. * @param string $engine Name of the engine, Defaults to default
  422. * @param string $config Name of the configuration setting
  423. * @return bool Whether or not the config name has been initialized.
  424. */
  425. public static function isInitialized($name = 'default') {
  426. if (Configure::read('Cache.disable')) {
  427. return false;
  428. }
  429. return isset(self::$_engines[$name]);
  430. }
  431. /**
  432. * Return the settings for the named cache engine.
  433. *
  434. * @param string $engine Name of the configuration to get settings for. Defaults to 'default'
  435. * @return array list of settings for this engine
  436. * @see Cache::config()
  437. * @access public
  438. * @static
  439. */
  440. public static function settings($name = 'default') {
  441. if (!empty(self::$_engines[$name])) {
  442. return self::$_engines[$name]->settings();
  443. }
  444. return array();
  445. }
  446. }
  447. /**
  448. * Storage engine for CakePHP caching
  449. *
  450. * @package cake.libs
  451. */
  452. abstract class CacheEngine {
  453. /**
  454. * Settings of current engine instance
  455. *
  456. * @var int
  457. * @access public
  458. */
  459. public $settings = array();
  460. /**
  461. * Initialize the cache engine
  462. *
  463. * Called automatically by the cache frontend
  464. *
  465. * @param array $params Associative array of parameters for the engine
  466. * @return boolean True if the engine has been succesfully initialized, false if not
  467. */
  468. public function init($settings = array()) {
  469. $this->settings = array_merge(
  470. array('prefix' => 'cake_', 'duration'=> 3600, 'probability'=> 100),
  471. $this->settings,
  472. $settings
  473. );
  474. if (!is_numeric($this->settings['duration'])) {
  475. $this->settings['duration'] = strtotime($this->settings['duration']) - time();
  476. }
  477. return true;
  478. }
  479. /**
  480. * Garbage collection
  481. *
  482. * Permanently remove all expired and deleted data
  483. * @return void
  484. */
  485. public function gc() { }
  486. /**
  487. * Write value for a key into cache
  488. *
  489. * @param string $key Identifier for the data
  490. * @param mixed $value Data to be cached
  491. * @param mixed $duration How long to cache for.
  492. * @return boolean True if the data was succesfully cached, false on failure
  493. */
  494. abstract public function write($key, $value, $duration);
  495. /**
  496. * Read a key from the cache
  497. *
  498. * @param string $key Identifier for the data
  499. * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
  500. */
  501. abstract public function read($key);
  502. /**
  503. * Increment a number under the key and return incremented value
  504. *
  505. * @param string $key Identifier for the data
  506. * @param integer $offset How much to add
  507. * @return New incremented value, false otherwise
  508. */
  509. abstract public function increment($key, $offset = 1);
  510. /**
  511. * Decrement a number under the key and return decremented value
  512. *
  513. * @param string $key Identifier for the data
  514. * @param integer $value How much to substract
  515. * @return New incremented value, false otherwise
  516. */
  517. abstract public function decrement($key, $offset = 1);
  518. /**
  519. * Delete a key from the cache
  520. *
  521. * @param string $key Identifier for the data
  522. * @return boolean True if the value was succesfully deleted, false if it didn't exist or couldn't be removed
  523. */
  524. abstract public function delete($key);
  525. /**
  526. * Delete all keys from the cache
  527. *
  528. * @param boolean $check if true will check expiration, otherwise delete all
  529. * @return boolean True if the cache was succesfully cleared, false otherwise
  530. */
  531. abstract public function clear($check);
  532. /**
  533. * Cache Engine settings
  534. *
  535. * @return array settings
  536. */
  537. public function settings() {
  538. return $this->settings;
  539. }
  540. /**
  541. * Generates a safe key for use with cache engine storage engines.
  542. *
  543. * @param string $key the key passed over
  544. * @return mixed string $key or false
  545. */
  546. public function key($key) {
  547. if (empty($key)) {
  548. return false;
  549. }
  550. $key = Inflector::underscore(str_replace(array(DS, '/', '.'), '_', strval($key)));
  551. return $key;
  552. }
  553. }