PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Varien/Cache/Backend/Database.php

https://bitbucket.org/andrewjleavitt/magestudy
PHP | 544 lines | 304 code | 32 blank | 208 comment | 40 complexity | 92cce0ccb39120a2baf732f3a5ab7fad MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Varien
  22. * @package Varien_Cache
  23. * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. Tables declaration:
  28. CREATE TABLE IF NOT EXISTS `core_cache` (
  29. `id` VARCHAR(255) NOT NULL,
  30. `data` mediumblob,
  31. `create_time` int(11),
  32. `update_time` int(11),
  33. `expire_time` int(11),
  34. PRIMARY KEY (`id`),
  35. KEY `IDX_EXPIRE_TIME` (`expire_time`)
  36. )ENGINE=InnoDB DEFAULT CHARSET=utf8;
  37. CREATE TABLE IF NOT EXISTS `core_cache_tag` (
  38. `tag` VARCHAR(255) NOT NULL,
  39. `cache_id` VARCHAR(255) NOT NULL,
  40. KEY `IDX_TAG` (`tag`),
  41. KEY `IDX_CACHE_ID` (`cache_id`),
  42. CONSTRAINT `FK_CORE_CACHE_TAG` FOREIGN KEY (`cache_id`) REFERENCES `core_cache` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
  43. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  44. */
  45. /**
  46. * Database cache backend
  47. */
  48. class Varien_Cache_Backend_Database extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
  49. {
  50. /**
  51. * Available options
  52. *
  53. * @var array available options
  54. */
  55. protected $_options = array(
  56. 'adapter' => '',
  57. 'adapter_callback' => '',
  58. 'data_table' => '',
  59. 'tags_table' => '',
  60. 'store_data' => true,
  61. );
  62. protected $_adapter = null;
  63. /**
  64. * Constructor
  65. *
  66. * @param array $options associative array of options
  67. */
  68. public function __construct($options = array())
  69. {
  70. parent::__construct($options);
  71. if (empty($this->_options['adapter_callback'])) {
  72. if (!($this->_options['adapter'] instanceof Zend_Db_Adapter_Abstract)) {
  73. Zend_Cache::throwException('Option "adapter" should be declared and extend Zend_Db_Adapter_Abstract!');
  74. }
  75. }
  76. if (empty($this->_options['data_table']) || empty ($this->_options['tags_table'])) {
  77. Zend_Cache::throwException('Options "data_table" and "tags_table" should be declared!');
  78. }
  79. }
  80. /**
  81. * Get DB adapter
  82. *
  83. * @return Zend_Db_Adapter_Abstract
  84. */
  85. protected function _getAdapter()
  86. {
  87. if (!$this->_adapter) {
  88. if (!empty($this->_options['adapter_callback'])) {
  89. $adapter = call_user_func($this->_options['adapter_callback']);
  90. } else {
  91. $adapter = $this->_options['adapter'];
  92. }
  93. if (!($adapter instanceof Zend_Db_Adapter_Abstract)) {
  94. Zend_Cache::throwException('DB Adapter should be declared and extend Zend_Db_Adapter_Abstract');
  95. } else {
  96. $this->_adapter = $adapter;
  97. }
  98. }
  99. return $this->_adapter;
  100. }
  101. /**
  102. * Get table name where data is stored
  103. *
  104. * @return string
  105. */
  106. protected function _getDataTable()
  107. {
  108. return $this->_options['data_table'];
  109. }
  110. /**
  111. * Get table name where tags are stored
  112. *
  113. * @return string
  114. */
  115. protected function _getTagsTable()
  116. {
  117. return $this->_options['tags_table'];
  118. }
  119. /**
  120. * Test if a cache is available for the given id and (if yes) return it (false else)
  121. *
  122. * Note : return value is always "string" (unserialization is done by the core not by the backend)
  123. *
  124. * @param string $id Cache id
  125. * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
  126. * @return string|false cached datas
  127. */
  128. public function load($id, $doNotTestCacheValidity = false)
  129. {
  130. if ($this->_options['store_data']) {
  131. $select = $this->_getAdapter()->select()
  132. ->from($this->_getDataTable(), 'data')
  133. ->where('id=:cache_id');
  134. if (!$doNotTestCacheValidity) {
  135. $select->where('expire_time=0 OR expire_time>?', time());
  136. }
  137. return $this->_getAdapter()->fetchOne($select, array('cache_id'=>$id));
  138. } else {
  139. return false;
  140. }
  141. }
  142. /**
  143. * Test if a cache is available or not (for the given id)
  144. *
  145. * @param string $id cache id
  146. * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
  147. */
  148. public function test($id)
  149. {
  150. if ($this->_options['store_data']) {
  151. $select = $this->_getAdapter()->select()
  152. ->from($this->_getDataTable(), 'update_time')
  153. ->where('id=:cache_id')
  154. ->where('expire_time=0 OR expire_time>?', time());
  155. return $this->_getAdapter()->fetchOne($select, array('cache_id'=>$id));
  156. } else {
  157. return false;
  158. }
  159. }
  160. /**
  161. * Save some string datas into a cache record
  162. *
  163. * Note : $data is always "string" (serialization is done by the
  164. * core not by the backend)
  165. *
  166. * @param string $data Datas to cache
  167. * @param string $id Cache id
  168. * @param array $tags Array of strings, the cache record will be tagged by each string entry
  169. * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
  170. * @return boolean true if no problem
  171. */
  172. public function save($data, $id, $tags = array(), $specificLifetime = false)
  173. {
  174. if ($this->_options['store_data']) {
  175. $adapter = $this->_getAdapter();
  176. $dataTable = $this->_getDataTable();
  177. $lifetime = $this->getLifetime($specificLifetime);
  178. $time = time();
  179. $expire = ($lifetime === 0 || $lifetime === null) ? 0 : $time+$lifetime;
  180. $dataCol = $adapter->quoteIdentifier('data');
  181. $expireCol = $adapter->quoteIdentifier('expire_time');
  182. $query = "INSERT INTO {$dataTable} (
  183. {$adapter->quoteIdentifier('id')},
  184. {$dataCol},
  185. {$adapter->quoteIdentifier('create_time')},
  186. {$adapter->quoteIdentifier('update_time')},
  187. {$expireCol})
  188. VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE
  189. {$dataCol}=VALUES({$dataCol}),
  190. {$expireCol}=VALUES({$expireCol})";
  191. $result = $adapter->query($query, array($id, $data, $time, $time, $expire))->rowCount();
  192. if (!$result) {
  193. return false;
  194. }
  195. }
  196. $tagRes = $this->_saveTags($id, $tags);
  197. return $tagRes;
  198. }
  199. /**
  200. * Remove a cache record
  201. *
  202. * @param string $id Cache id
  203. * @return boolean True if no problem
  204. */
  205. public function remove($id)
  206. {
  207. if ($this->_options['store_data']) {
  208. $adapter = $this->_getAdapter();
  209. $result = $adapter->delete($this->_getDataTable(), array('id=?'=>$id));
  210. return $result;
  211. } else {
  212. return false;
  213. }
  214. }
  215. /**
  216. * Clean some cache records
  217. *
  218. * Available modes are :
  219. * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
  220. * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
  221. * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
  222. * ($tags can be an array of strings or a single string)
  223. * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
  224. * ($tags can be an array of strings or a single string)
  225. * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
  226. * ($tags can be an array of strings or a single string)
  227. *
  228. * @param string $mode Clean mode
  229. * @param array $tags Array of tags
  230. * @return boolean true if no problem
  231. */
  232. public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  233. {
  234. $adapter = $this->_getAdapter();
  235. switch($mode) {
  236. case Zend_Cache::CLEANING_MODE_ALL:
  237. if ($this->_options['store_data']) {
  238. $result = $adapter->query('TRUNCATE TABLE '.$this->_getDataTable());
  239. } else {
  240. $result = true;
  241. }
  242. $result = $result && $adapter->query('TRUNCATE TABLE '.$this->_getTagsTable());
  243. break;
  244. case Zend_Cache::CLEANING_MODE_OLD:
  245. if ($this->_options['store_data']) {
  246. $result = $adapter->delete($this->_getDataTable(), array(
  247. 'expire_time>' => 0,
  248. 'expire_time<=' => time()
  249. ));
  250. } else {
  251. $result = true;
  252. }
  253. break;
  254. case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
  255. case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
  256. case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
  257. $result = $this->_cleanByTags($mode, $tags);
  258. break;
  259. default:
  260. Zend_Cache::throwException('Invalid mode for clean() method');
  261. break;
  262. }
  263. return $result;
  264. }
  265. /**
  266. * Return an array of stored cache ids
  267. *
  268. * @return array array of stored cache ids (string)
  269. */
  270. public function getIds()
  271. {
  272. if ($this->_options['store_data']) {
  273. $select = $this->_getAdapter()->select()
  274. ->from($this->_getDataTable(), 'id');
  275. return $this->_getAdapter()->fetchCol($select);
  276. } else {
  277. return array();
  278. }
  279. }
  280. /**
  281. * Return an array of stored tags
  282. *
  283. * @return array array of stored tags (string)
  284. */
  285. public function getTags()
  286. {
  287. $select = $this->_getAdapter()->select()
  288. ->from($this->_getTagsTable(), 'tag')
  289. ->distinct(true);
  290. return $this->_getAdapter()->fetchCol($select);
  291. }
  292. /**
  293. * Return an array of stored cache ids which match given tags
  294. *
  295. * In case of multiple tags, a logical AND is made between tags
  296. *
  297. * @param array $tags array of tags
  298. * @return array array of matching cache ids (string)
  299. */
  300. public function getIdsMatchingTags($tags = array())
  301. {
  302. $select = $this->_getAdapter()->select()
  303. ->from($this->_getTagsTable(), 'cache_id')
  304. ->distinct(true)
  305. ->where('tag IN(?)', $tags)
  306. ->group('cache_id')
  307. ->having('COUNT(cache_id)='.count($tags));
  308. return $this->_getAdapter()->fetchCol($select);
  309. }
  310. /**
  311. * Return an array of stored cache ids which don't match given tags
  312. *
  313. * In case of multiple tags, a logical OR is made between tags
  314. *
  315. * @param array $tags array of tags
  316. * @return array array of not matching cache ids (string)
  317. */
  318. public function getIdsNotMatchingTags($tags = array())
  319. {
  320. return array_diff($this->getIds(), $this->getIdsMatchingAnyTags($tags));
  321. }
  322. /**
  323. * Return an array of stored cache ids which match any given tags
  324. *
  325. * In case of multiple tags, a logical AND is made between tags
  326. *
  327. * @param array $tags array of tags
  328. * @return array array of any matching cache ids (string)
  329. */
  330. public function getIdsMatchingAnyTags($tags = array())
  331. {
  332. $select = $this->_getAdapter()->select()
  333. ->from($this->_getTagsTable(), 'cache_id')
  334. ->distinct(true)
  335. ->where('tag IN(?)', $tags);
  336. return $this->_getAdapter()->fetchCol($select);
  337. }
  338. /**
  339. * Return the filling percentage of the backend storage
  340. *
  341. * @return int integer between 0 and 100
  342. */
  343. public function getFillingPercentage()
  344. {
  345. return 1;
  346. }
  347. /**
  348. * Return an array of metadatas for the given cache id
  349. *
  350. * The array must include these keys :
  351. * - expire : the expire timestamp
  352. * - tags : a string array of tags
  353. * - mtime : timestamp of last modification time
  354. *
  355. * @param string $id cache id
  356. * @return array array of metadatas (false if the cache id is not found)
  357. */
  358. public function getMetadatas($id)
  359. {
  360. $select = $this->_getAdapter()->select()
  361. ->from($this->_getTagsTable(), 'tag')
  362. ->where('cache_id=?', $id);
  363. $tags = $this->_getAdapter()->fetchCol($select);
  364. $select = $this->_getAdapter()->select()
  365. ->from($this->_getDataTable())
  366. ->where('id=?', $id);
  367. $data = $this->_getAdapter()->fetchRow($select);
  368. $res = false;
  369. if ($data) {
  370. $res = array (
  371. 'expire'=> $data['expire_time'],
  372. 'mtime' => $data['update_time'],
  373. 'tags' => $tags
  374. );
  375. }
  376. return $res;
  377. }
  378. /**
  379. * Give (if possible) an extra lifetime to the given cache id
  380. *
  381. * @param string $id cache id
  382. * @param int $extraLifetime
  383. * @return boolean true if ok
  384. */
  385. public function touch($id, $extraLifetime)
  386. {
  387. if ($this->_options['store_data']) {
  388. return $this->_getAdapter()->update(
  389. $this->_getDataTable(),
  390. array('expire_time'=>new Zend_Db_Expr('expire_time+'.$extraLifetime)),
  391. array('id=?'=>$id, 'expire_time = 0 OR expire_time>'=>time())
  392. );
  393. } else {
  394. return true;
  395. }
  396. }
  397. /**
  398. * Return an associative array of capabilities (booleans) of the backend
  399. *
  400. * The array must include these keys :
  401. * - automatic_cleaning (is automating cleaning necessary)
  402. * - tags (are tags supported)
  403. * - expired_read (is it possible to read expired cache records
  404. * (for doNotTestCacheValidity option for example))
  405. * - priority does the backend deal with priority when saving
  406. * - infinite_lifetime (is infinite lifetime can work with this backend)
  407. * - get_list (is it possible to get the list of cache ids and the complete list of tags)
  408. *
  409. * @return array associative of with capabilities
  410. */
  411. public function getCapabilities()
  412. {
  413. return array(
  414. 'automatic_cleaning' => true,
  415. 'tags' => true,
  416. 'expired_read' => true,
  417. 'priority' => false,
  418. 'infinite_lifetime' => true,
  419. 'get_list' => true
  420. );
  421. }
  422. /**
  423. * Save tags related to specific id
  424. *
  425. * @param string $id
  426. * @param array $tags
  427. * @return bool
  428. */
  429. protected function _saveTags($id, $tags)
  430. {
  431. if (!is_array($tags)) {
  432. $tags = array($tags);
  433. }
  434. if (empty($tags)) {
  435. return true;
  436. }
  437. $adapter = $this->_getAdapter();
  438. $tagsTable = $this->_getTagsTable();
  439. $select = $adapter->select()
  440. ->from($tagsTable, 'tag')
  441. ->where('cache_id=?', $id)
  442. ->where('tag IN(?)', $tags);
  443. $existingTags = $adapter->fetchCol($select);
  444. $insertTags = array_diff($tags, $existingTags);
  445. if (!empty($insertTags)) {
  446. $query = 'INSERT IGNORE INTO ' . $tagsTable . ' (tag, cache_id) VALUES ';
  447. $bind = array();
  448. $lines = array();
  449. foreach ($insertTags as $tag) {
  450. $lines[] = '(?, ?)';
  451. $bind[] = $tag;
  452. $bind[] = $id;
  453. }
  454. $query.= implode(',', $lines);
  455. $adapter->query($query, $bind);
  456. }
  457. $result = true;
  458. return $result;
  459. }
  460. /**
  461. * Remove cache data by tags with specified mode
  462. *
  463. * @param string $mode
  464. * @param array $tags
  465. * @return bool
  466. */
  467. protected function _cleanByTags($mode, $tags)
  468. {
  469. if ($this->_options['store_data']) {
  470. $adapter = $this->_getAdapter();
  471. $select = $adapter->select()
  472. ->from($this->_getTagsTable(), 'cache_id');
  473. switch ($mode) {
  474. case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
  475. $select->where('tag IN (?)', $tags)
  476. ->group('cache_id')
  477. ->having('COUNT(cache_id)='.count($tags));
  478. break;
  479. case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
  480. $select->where('tag NOT IN (?)', $tags);
  481. break;
  482. case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
  483. $select->where('tag IN (?)', $tags);
  484. break;
  485. default:
  486. Zend_Cache::throwException('Invalid mode for _cleanByTags() method');
  487. break;
  488. }
  489. $result = true;
  490. $ids = array();
  491. $counter = 0;
  492. $stmt = $adapter->query($select);
  493. while ($row = $stmt->fetch()) {
  494. $ids[] = $row['cache_id'];
  495. $counter++;
  496. if ($counter>100) {
  497. $result = $result && $adapter->delete($this->_getDataTable(), array('id IN (?)' => $ids));
  498. $ids = array();
  499. $counter = 0;
  500. }
  501. }
  502. if (!empty($ids)) {
  503. $result = $result && $adapter->delete($this->_getDataTable(), array('id IN (?)' => $ids));
  504. }
  505. return $result;
  506. } else {
  507. return true;
  508. }
  509. }
  510. }