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

/standard/tags/release-0.2.0/library/Zend/Cache/Backend/Sqlite.php

https://github.com/bhaumik25/zend-framework
PHP | 403 lines | 218 code | 26 blank | 159 comment | 38 complexity | 735eb17e1f9ea9680bbfb29bbf89eb42 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  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@zend.com so we can send you a copy immediately.
  14. *
  15. * @package Zend_Cache
  16. * @subpackage Backend
  17. * @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. */
  20. /**
  21. * Zend_Cache_Backend_Interface
  22. */
  23. require_once 'Zend/Cache/Backend/Interface.php';
  24. /**
  25. * Zend_Cache_Backend
  26. */
  27. require_once 'Zend/Cache/Backend.php';
  28. /**
  29. * @package Zend_Cache
  30. * @subpackage Backend
  31. * @copyright Copyright (c) 2006 Zend Technologies USA Inc. (http://www.zend.com)
  32. * @license http://framework.zend.com/license/new-bsd New BSD License
  33. */
  34. class Zend_Cache_Backend_Sqlite extends Zend_Cache_Backend implements Zend_Cache_Backend_Interface
  35. {
  36. // ------------------
  37. // --- Properties ---
  38. // ------------------
  39. /**
  40. * Available options
  41. *
  42. * =====> (string) cacheDBCompletePath :
  43. * - the complete path (filename included) of the SQLITE database
  44. *
  45. * ====> (int) automaticVacuumFactor :
  46. * - Disable / Tune the automatic vacuum process
  47. * - The automatic vacuum process defragment the database file (and make it smaller)
  48. * when a clean() or delete() is called
  49. * 0 => no automatic vacuum
  50. * 1 => systematic vacuum (when delete() or clean() methods are called)
  51. * x (integer) > 1 => automatic vacuum randomly 1 times on x clean() or delete()
  52. *
  53. * @var array available options
  54. */
  55. protected $_options = array(
  56. 'cacheDBCompletePath' => null,
  57. 'automaticVacuumFactor' => 0
  58. );
  59. /**
  60. * DB ressource
  61. *
  62. * @var mixed $_db
  63. */
  64. private $_db = null;
  65. // ----------------------
  66. // --- Public methods ---
  67. // ----------------------
  68. /**
  69. * Constructor
  70. *
  71. * @param array $options associative array of options
  72. */
  73. public function __construct($options = array())
  74. {
  75. if (!isset($options['cacheDBCompletePath'])) Zend_Cache::throwException('cacheDBCompletePath option has to set');
  76. $this->_db = @sqlite_open($options['cacheDBCompletePath']);
  77. if (!($this->_db)) {
  78. Zend_Cache::throwException("Impossible to open " . $options['cacheDBCompletePath'] . " cache DB file");
  79. }
  80. parent::__construct($options);
  81. }
  82. /**
  83. * Destructor
  84. */
  85. public function __destruct()
  86. {
  87. @sqlite_close($this->_db);
  88. }
  89. /**
  90. * Test if a cache is available for the given id and (if yes) return it (false else)
  91. *
  92. * @param string $id cache id
  93. * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
  94. * @return string cached datas (or false)
  95. */
  96. public function get($id, $doNotTestCacheValidity = false)
  97. {
  98. $sql = "SELECT content FROM cache WHERE id='$id'";
  99. if (!$doNotTestCacheValidity) {
  100. $sql = $sql . " AND (expire=0 OR expire>" . time() . ')';
  101. }
  102. $result = @sqlite_query($this->_db, $sql);
  103. $row = @sqlite_fetch_array($result);
  104. if ($row) {
  105. return $row['content'];
  106. }
  107. return false;
  108. }
  109. /**
  110. * Test if a cache is available or not (for the given id)
  111. *
  112. * @param string $id cache id
  113. * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
  114. */
  115. public function test($id)
  116. {
  117. $sql = "SELECT lastModified FROM cache WHERE id='$id' AND (expire=0 OR expire>" . time() . ')';
  118. $result = @sqlite_query($this->_db, $sql);
  119. $row = @sqlite_fetch_array($result);
  120. if ($row) {
  121. return ((int) $row['lastModified']);
  122. }
  123. return false;
  124. }
  125. /**
  126. * Save some string datas into a cache record
  127. *
  128. * Note : $data is always "string" (serialization is done by the
  129. * core not by the backend)
  130. *
  131. * @param string $data datas to cache
  132. * @param string $id cache id
  133. * @param array $tags array of strings, the cache record will be tagged by each string entry
  134. * @return boolean true if no problem
  135. */
  136. public function save($data, $id, $tags = array())
  137. {
  138. if (!$this->_checkStructureVersion()) {
  139. $this->_buildStructure();
  140. if (!$this->_checkStructureVersion()) {
  141. Zend_Cache::throwException("Impossible to build cache structure in " . $this->_options['cacheDBCompletePath']);
  142. }
  143. }
  144. $data = @sqlite_escape_string($data);
  145. $mktime = time();
  146. if (is_null($this->_directives['lifeTime'])) {
  147. $expire = 0;
  148. } else {
  149. $expire = $mktime + $this->_directives['lifeTime'];
  150. }
  151. @sqlite_query($this->_db, "DELETE FROM cache WHERE id='$id'");
  152. $sql = "INSERT INTO cache (id, content, lastModified, expire) VALUES ('$id', '$data', $mktime, $expire)";
  153. $res = @sqlite_query($this->_db, $sql);
  154. if (!$res) {
  155. if ($this->_directives['logging']) {
  156. Zend_Log::log("Zend_Cache_Backend_Sqlite::save() : impossible to store the cache id=$id", Zend_Log::LEVEL_WARNING);
  157. }
  158. return false;
  159. }
  160. $res = true;
  161. foreach ($tags as $tag) {
  162. $res = $res && $this->_registerTag($id, $tag);
  163. }
  164. return $res;
  165. }
  166. /**
  167. * Remove a cache record
  168. *
  169. * @param string $id cache id
  170. * @return boolean true if no problem
  171. */
  172. public function remove($id)
  173. {
  174. $res = @sqlite_query($this->_db, "SELECT COUNT(*) AS nbr FROM cache WHERE id='$id'");
  175. $result1 = @sqlite_fetch_single($res);
  176. $result2 = @sqlite_query($this->_db, "DELETE FROM cache WHERE id='$id'");
  177. $result3 = @sqlite_query($this->_db, "DELETE FROM tag WHERE id='$id'");
  178. $this->_automaticVacuum();
  179. return ($result1 && $result2 && $result3);
  180. }
  181. /**
  182. * Clean some cache records
  183. *
  184. * Available modes are :
  185. * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
  186. * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
  187. * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
  188. * ($tags can be an array of strings or a single string)
  189. * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
  190. * ($tags can be an array of strings or a single string)
  191. *
  192. * @param string $mode clean mode
  193. * @param tags array $tags array of tags
  194. * @return boolean true if no problem
  195. */
  196. public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  197. {
  198. $return = $this->_clean($mode, $tags);
  199. $this->_automaticVacuum();
  200. return $return;
  201. }
  202. /**
  203. * PUBLIC METHOD FOR UNIT TESTING ONLY !
  204. *
  205. * Force a cache record to expire
  206. *
  207. * @param string $id cache id
  208. */
  209. public function ___expire($id)
  210. {
  211. $time = time() - 1;
  212. @sqlite_query($this->_db, "UPDATE cache SET lastModified=$time, expire=$time WHERE id='$id'");
  213. }
  214. /**
  215. * PUBLIC METHOD FOR UNIT TESTING ONLY !
  216. *
  217. * Unlink the database file
  218. */
  219. public function ___dropDatabaseFile()
  220. {
  221. @sqlite_close($this->_db);
  222. @unlink($this->_options['cacheDBCompletePath']);
  223. }
  224. // -----------------------
  225. // --- Private methods ---
  226. // -----------------------
  227. /**
  228. * Deal with the automatic vacuum process
  229. */
  230. private function _automaticVacuum()
  231. {
  232. if ($this->_options['automaticVacuumFactor'] > 0) {
  233. $rand = rand(1, $this->_options['automaticVacuumFactor']);
  234. if ($rand == 1) {
  235. @sqlite_query($this->_db, 'VACUUM');
  236. }
  237. }
  238. }
  239. /**
  240. * Register a cache id with the given tag
  241. *
  242. * @param string $id cache id
  243. * @param string $tag tag
  244. * @return boolean true if no problem
  245. */
  246. private function _registerTag($id, $tag) {
  247. $res = @sqlite_query($this->_db, "DELETE FROM TAG WHERE tag='$tag' AND id='$id'");
  248. $res = @sqlite_query($this->_db, "INSERT INTO tag (name, id) VALUES ('$tag', '$id')");
  249. if (!$res) {
  250. if ($this->_directives['logging']) {
  251. Zend_Log::log("Zend_Cache_Backend_Sqlite::_registerTag() : impossible to register tag=$tag on id=$id", Zend_Log::LEVEL_WARNING);
  252. }
  253. return false;
  254. }
  255. return true;
  256. }
  257. /**
  258. * Build the database structure
  259. */
  260. private function _buildStructure()
  261. {
  262. @sqlite_query($this->_db, 'DROP INDEX tag_id_index');
  263. @sqlite_query($this->_db, 'DROP INDEX tag_name_index');
  264. @sqlite_query($this->_db, 'DROP INDEX cache_id_expire_index');
  265. @sqlite_query($this->_db, 'DROP TABLE version');
  266. @sqlite_query($this->_db, 'DROP TABLE cache');
  267. @sqlite_query($this->_db, 'DROP TABLE tag');
  268. @sqlite_query($this->_db, 'CREATE TABLE version (num INTEGER PRIMARY KEY)');
  269. @sqlite_query($this->_db, 'CREATE TABLE cache (id TEXT PRIMARY KEY, content BLOB, lastModified INTEGER, expire INTEGER)');
  270. @sqlite_query($this->_db, 'CREATE TABLE tag (name TEXT, id TEXT)');
  271. @sqlite_query($this->_db, 'CREATE INDEX tag_id_index ON tag(id)');
  272. @sqlite_query($this->_db, 'CREATE INDEX tag_name_index ON tag(name)');
  273. @sqlite_query($this->_db, 'CREATE INDEX cache_id_expire_index ON cache(id, expire)');
  274. @sqlite_query($this->_db, 'INSERT INTO version (num) VALUES (1)');
  275. }
  276. /**
  277. * Check if the database structure is ok (with the good version)
  278. *
  279. * @return boolean true if ok
  280. */
  281. private function _checkStructureVersion()
  282. {
  283. $result = @sqlite_query($this->_db, "SELECT num FROM version");
  284. if (!$result) return false;
  285. $row = @sqlite_fetch_array($result);
  286. if (!$row) {
  287. return false;
  288. }
  289. if (((int) $row['num']) != 1) {
  290. // old cache structure
  291. if ($this->_directives['logging']) {
  292. Zend_Log::log('Zend_Cache_Backend_Sqlite::_checkStructureVersion() : old cache structure version detected => the cache is going to be dropped', Zend_Log::LEVEL_WARNING);
  293. }
  294. return false;
  295. }
  296. return true;
  297. }
  298. /**
  299. * Clean some cache records
  300. *
  301. * Available modes are :
  302. * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
  303. * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
  304. * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
  305. * ($tags can be an array of strings or a single string)
  306. * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
  307. * ($tags can be an array of strings or a single string)
  308. *
  309. * @param string $mode clean mode
  310. * @param tags array $tags array of tags
  311. * @return boolean true if no problem
  312. */
  313. private function _clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  314. {
  315. if ($mode==Zend_Cache::CLEANING_MODE_ALL) {
  316. $res1 = @sqlite_query($this->_db, 'DELETE FROM cache');
  317. $res2 = @sqlite_query($this->_db, 'DELETE FROM tag');
  318. return $res1 && $res2;
  319. }
  320. if ($mode==Zend_Cache::CLEANING_MODE_OLD) {
  321. $mktime = time();
  322. $res1 = @sqlite_query($this->_db, "DELETE FROM tag WHERE id IN (SELECT id FROM cache WHERE expire>0 AND expire<=$mktime)");
  323. $res2 = @sqlite_query($this->_db, "DELETE FROM cache WHERE expire>0 AND expire<=$mktime");
  324. return $res1 && $res2;
  325. }
  326. if ($mode==Zend_Cache::CLEANING_MODE_MATCHING_TAG) {
  327. $first = true;
  328. $ids = array();
  329. foreach ($tags as $tag) {
  330. $res = @sqlite_query($this->_db, "SELECT DISTINCT(id) AS id FROM tag WHERE name='$tag'");
  331. if (!$res) {
  332. return false;
  333. }
  334. $rows = @sqlite_fetch_all($res, SQLITE_ASSOC);
  335. $ids2 = array();
  336. foreach ($rows as $row) {
  337. $ids2[] = $row['id'];
  338. }
  339. if ($first) {
  340. $ids = $ids2;
  341. $first = false;
  342. } else {
  343. $ids = array_intersect($ids, $ids2);
  344. }
  345. }
  346. $result = true;
  347. foreach ($ids as $id) {
  348. $result = $result && ($this->remove($id));
  349. }
  350. return $result;
  351. }
  352. if ($mode==Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG) {
  353. $res = @sqlite_query($this->_db, "SELECT id FROM cache");
  354. $rows = @sqlite_fetch_all($res, SQLITE_ASSOC);
  355. $result = true;
  356. foreach ($rows as $row) {
  357. $id = $row['id'];
  358. $matching = false;
  359. foreach ($tags as $tag) {
  360. $res = @sqlite_query($this->_db, "SELECT COUNT(*) AS nbr FROM tag WHERE name='$tag' AND id='$id'");
  361. if (!$res) {
  362. return false;
  363. }
  364. $nbr = (int) @sqlite_fetch_single($res);
  365. if ($nbr > 0) {
  366. $matching = true;
  367. }
  368. }
  369. if (!$matching) {
  370. $result = $result && $this->remove($id);
  371. }
  372. }
  373. return $result;
  374. }
  375. return false;
  376. }
  377. }