PageRenderTime 28ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/cache/Cache.php

https://gitlab.com/jslee1/PrestaShop
PHP | 400 lines | 211 code | 41 blank | 148 comment | 34 complexity | 02ab1f5ba9e89a31a70e8e9775e409fa MD5 | raw file
  1. <?php
  2. /**
  3. * 2007-2015 PrestaShop
  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@prestashop.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 PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2015 PrestaShop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. abstract class CacheCore
  27. {
  28. /**
  29. * Name of keys index
  30. */
  31. const KEYS_NAME = '__keys__';
  32. /**
  33. * Name of SQL cache index
  34. */
  35. const SQL_TABLES_NAME = 'tablesCached';
  36. /**
  37. * @var Cache
  38. */
  39. protected static $instance;
  40. /**
  41. * @var array List all keys of cached data and their associated ttl
  42. */
  43. protected $keys = array();
  44. /**
  45. * @var array Store list of tables and their associated keys for SQL cache (warning: this var must not be initialized here !)
  46. */
  47. protected $sql_tables_cached;
  48. /**
  49. * @var array List of blacklisted tables for SQL cache, these tables won't be indexed
  50. */
  51. protected $blacklist = array(
  52. 'cart',
  53. 'cart_cart_rule',
  54. 'cart_product',
  55. 'connections',
  56. 'connections_source',
  57. 'connections_page',
  58. 'customer',
  59. 'customer_group',
  60. 'customized_data',
  61. 'guest',
  62. 'pagenotfound',
  63. 'page_viewed',
  64. );
  65. /**
  66. * @var array Store local cache
  67. */
  68. protected static $local = array();
  69. /**
  70. * Cache a data
  71. *
  72. * @param string $key
  73. * @param mixed $value
  74. * @param int $ttl
  75. * @return bool
  76. */
  77. abstract protected function _set($key, $value, $ttl = 0);
  78. /**
  79. * Retrieve a cached data by key
  80. *
  81. * @param string $key
  82. * @return mixed
  83. */
  84. abstract protected function _get($key);
  85. /**
  86. * Check if a data is cached by key
  87. *
  88. * @param string $key
  89. * @return bool
  90. */
  91. abstract protected function _exists($key);
  92. /**
  93. * Delete a data from the cache by key
  94. *
  95. * @param string $key
  96. * @return bool
  97. */
  98. abstract protected function _delete($key);
  99. /**
  100. * Write keys index
  101. */
  102. abstract protected function _writeKeys();
  103. /**
  104. * Clean all cached data
  105. *
  106. * @return bool
  107. */
  108. abstract public function flush();
  109. /**
  110. * @return Cache
  111. */
  112. public static function getInstance()
  113. {
  114. if (!self::$instance) {
  115. $caching_system = _PS_CACHING_SYSTEM_;
  116. self::$instance = new $caching_system();
  117. }
  118. return self::$instance;
  119. }
  120. /**
  121. * Unit testing purpose only
  122. * @param $test_instance Cache
  123. */
  124. public static function setInstanceForTesting($test_instance)
  125. {
  126. self::$instance = $test_instance;
  127. }
  128. /**
  129. * Unit testing purpose only
  130. */
  131. public static function deleteTestingInstance()
  132. {
  133. self::$instance = null;
  134. }
  135. /**
  136. * Store a data in cache
  137. *
  138. * @param string $key
  139. * @param mixed $value
  140. * @param int $ttl
  141. * @return bool
  142. */
  143. public function set($key, $value, $ttl = 0)
  144. {
  145. if ($this->_set($key, $value, $ttl)) {
  146. if ($ttl < 0) {
  147. $ttl = 0;
  148. }
  149. $this->keys[$key] = ($ttl == 0) ? 0 : time() + $ttl;
  150. $this->_writeKeys();
  151. return true;
  152. }
  153. return false;
  154. }
  155. /**
  156. * Retrieve a data from cache
  157. *
  158. * @param string $key
  159. * @return mixed
  160. */
  161. public function get($key)
  162. {
  163. if (!isset($this->keys[$key])) {
  164. return false;
  165. }
  166. return $this->_get($key);
  167. }
  168. /**
  169. * Check if a data is cached
  170. *
  171. * @param string $key
  172. * @return bool
  173. */
  174. public function exists($key)
  175. {
  176. if (!isset($this->keys[$key])) {
  177. return false;
  178. }
  179. return $this->_exists($key);
  180. }
  181. /**
  182. * Delete one or several data from cache (* joker can be used)
  183. * E.g.: delete('*'); delete('my_prefix_*'); delete('my_key_name');
  184. *
  185. * @param string $key
  186. * @return array List of deleted keys
  187. */
  188. public function delete($key)
  189. {
  190. // Get list of keys to delete
  191. $keys = array();
  192. if ($key == '*') {
  193. $keys = $this->keys;
  194. } elseif (strpos($key, '*') === false) {
  195. $keys = array($key);
  196. } else {
  197. $pattern = str_replace('\\*', '.*', preg_quote($key));
  198. foreach ($this->keys as $k => $ttl) {
  199. if (preg_match('#^'.$pattern.'$#', $k)) {
  200. $keys[] = $k;
  201. }
  202. }
  203. }
  204. // Delete keys
  205. foreach ($keys as $key) {
  206. if (!isset($this->keys[$key])) {
  207. continue;
  208. }
  209. if ($this->_delete($key)) {
  210. unset($this->keys[$key]);
  211. }
  212. }
  213. $this->_writeKeys();
  214. return $keys;
  215. }
  216. /**
  217. * Store a query in cache
  218. *
  219. * @param string $query
  220. * @param array $result
  221. */
  222. public function setQuery($query, $result)
  223. {
  224. if ($this->isBlacklist($query)) {
  225. return true;
  226. }
  227. if (empty($result) || $result === false) {
  228. $result = array();
  229. }
  230. if (is_null($this->sql_tables_cached)) {
  231. $this->sql_tables_cached = $this->get(Tools::encryptIV(self::SQL_TABLES_NAME));
  232. if (!is_array($this->sql_tables_cached)) {
  233. $this->sql_tables_cached = array();
  234. }
  235. }
  236. // Store query results in cache
  237. $key = Tools::encryptIV($query);
  238. // no need to check the key existence before the set : if the query is already
  239. // in the cache, setQuery is not invoked
  240. $this->set($key, $result);
  241. // Get all table from the query and save them in cache
  242. if ($tables = $this->getTables($query)) {
  243. foreach ($tables as $table) {
  244. if (!isset($this->sql_tables_cached[$table][$key])) {
  245. $this->adjustTableCacheSize($table);
  246. $this->sql_tables_cached[$table][$key] = true;
  247. }
  248. }
  249. }
  250. $this->set(Tools::encryptIV(self::SQL_TABLES_NAME), $this->sql_tables_cached);
  251. }
  252. /**
  253. * Autoadjust the table cache size to avoid storing too big elements in the cache
  254. *
  255. * @param $table
  256. */
  257. protected function adjustTableCacheSize($table)
  258. {
  259. if (isset($this->sql_tables_cached[$table])
  260. && count($this->sql_tables_cached[$table]) > 5000) {
  261. // make sure the cache doesn't contains too many elements : delete the first 1000
  262. $table_buffer = array_slice($this->sql_tables_cached[$table], 0, 1000, true);
  263. foreach ($table_buffer as $fs_key => $value) {
  264. $this->delete($fs_key);
  265. $this->delete($fs_key.'_nrows');
  266. unset($this->sql_tables_cached[$table][$fs_key]);
  267. }
  268. }
  269. }
  270. protected function getTables($string)
  271. {
  272. if (preg_match_all('/(?:from|join|update|into)\s+`?('._DB_PREFIX_.'[0-9a-z_-]+)(?:`?\s{0,},\s{0,}`?('._DB_PREFIX_.'[0-9a-z_-]+)`?)?(?:`|\s+|\Z)(?!\s*,)/Umsi', $string, $res)) {
  273. foreach ($res[2] as $table) {
  274. if ($table != '') {
  275. $res[1][] = $table;
  276. }
  277. }
  278. return array_unique($res[1]);
  279. } else {
  280. return false;
  281. }
  282. }
  283. /**
  284. * Delete a query from cache
  285. *
  286. * @param string $query
  287. */
  288. public function deleteQuery($query)
  289. {
  290. if (is_null($this->sql_tables_cached)) {
  291. $this->sql_tables_cached = $this->get(Tools::encryptIV(self::SQL_TABLES_NAME));
  292. if (!is_array($this->sql_tables_cached)) {
  293. $this->sql_tables_cached = array();
  294. }
  295. }
  296. if ($tables = $this->getTables($query)) {
  297. foreach ($tables as $table) {
  298. if (isset($this->sql_tables_cached[$table])) {
  299. foreach (array_keys($this->sql_tables_cached[$table]) as $fs_key) {
  300. $this->delete($fs_key);
  301. $this->delete($fs_key.'_nrows');
  302. }
  303. unset($this->sql_tables_cached[$table]);
  304. }
  305. }
  306. }
  307. $this->set(Tools::encryptIV(self::SQL_TABLES_NAME), $this->sql_tables_cached);
  308. }
  309. /**
  310. * Check if a query contain blacklisted tables
  311. *
  312. * @param string $query
  313. * @return bool
  314. */
  315. protected function isBlacklist($query)
  316. {
  317. foreach ($this->blacklist as $find) {
  318. if (false !== strpos($query, _DB_PREFIX_.$find)) {
  319. return true;
  320. }
  321. }
  322. return false;
  323. }
  324. public static function store($key, $value)
  325. {
  326. // PHP is not efficient at storing array
  327. // Better delete the whole cache if there are
  328. // more than 1000 elements in the array
  329. if (count(Cache::$local) > 1000) {
  330. Cache::$local = array();
  331. }
  332. Cache::$local[$key] = $value;
  333. }
  334. public static function retrieve($key)
  335. {
  336. return isset(Cache::$local[$key]) ? Cache::$local[$key] : null;
  337. }
  338. public static function retrieveAll()
  339. {
  340. return Cache::$local;
  341. }
  342. public static function isStored($key)
  343. {
  344. return isset(Cache::$local[$key]);
  345. }
  346. public static function clean($key)
  347. {
  348. if (strpos($key, '*') !== false) {
  349. $regexp = str_replace('\\*', '.*', preg_quote($key, '#'));
  350. foreach (array_keys(Cache::$local) as $key) {
  351. if (preg_match('#^'.$regexp.'$#', $key)) {
  352. unset(Cache::$local[$key]);
  353. }
  354. }
  355. } else {
  356. unset(Cache::$local[$key]);
  357. }
  358. }
  359. }