cache.class.php 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097
  1. <?php
  2. /**
  3. * The cache library of zentaopms.
  4. *
  5. * @copyright Copyright 2009-2024 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  6. * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Gang Liu <liugang@chandao.com>
  8. * @package cache
  9. * @link https://www.zentao.net
  10. */
  11. namespace Zentao\Cache;
  12. \helper::import(dirname(__FILE__) . DS . 'simple-cache' . DS . 'CacheInterface.php');
  13. \helper::import(dirname(__FILE__) . DS . 'simple-cache' . DS . 'CacheException.php');
  14. \helper::import(dirname(__FILE__) . DS . 'simple-cache' . DS . 'InvalidArgumentException.php');
  15. \helper::import(dirname(__FILE__) . DS . 'driver' . DS . 'ApcuDriver.php');
  16. \helper::import(dirname(__FILE__) . DS . 'driver' . DS . 'RedisDriver.php');
  17. \helper::import(dirname(__FILE__) . DS . 'driver' . DS . 'YacDriver.php');
  18. \helper::import(dirname(__FILE__) . DS . 'driver' . DS . 'FileDriver.php');
  19. use ZenTao\Cache\SimpleCache\InvalidArgumentException;
  20. class cache
  21. {
  22. const DRIVER_APCU = 'Apcu';
  23. const DRIVER_FILE = 'File';
  24. const DRIVER_YAC = 'Yac';
  25. const DRIVER_REDIS = 'Redis';
  26. const DRIVER_LIST = [self::DRIVER_APCU, self::DRIVER_FILE, self::DRIVER_YAC, self::DRIVER_REDIS];
  27. /**
  28. * 全局应用程序对象。
  29. * Global application object.
  30. *
  31. * @access private
  32. * @var object
  33. */
  34. private $app;
  35. /**
  36. * 全局数据库操作对象。
  37. * Global database operation object.
  38. *
  39. * @access private
  40. * @var object
  41. */
  42. private $dao;
  43. /**
  44. * 全局数据库操作句柄。
  45. * Global database operation handler.
  46. *
  47. * @access private
  48. * @var object
  49. */
  50. private $dbh;
  51. /**
  52. * 全局配置对象。
  53. * Global configuration object.
  54. *
  55. * @access private
  56. * @var object
  57. */
  58. private $config;
  59. /**
  60. * 全局缓存对象。
  61. * Global cache object.
  62. *
  63. * @access private
  64. * @var object
  65. */
  66. private $cache;
  67. /**
  68. * 缓存状态。
  69. * Cache status.
  70. *
  71. * @access private
  72. * @var string
  73. */
  74. private $status = 'enabled';
  75. /**
  76. * 缓存命名空间。
  77. * Cache namespace.
  78. *
  79. * @access private
  80. * @var string
  81. */
  82. private $namespace;
  83. /**
  84. * 缓存键连接符。
  85. * Cache key connector.
  86. *
  87. * @access private
  88. * @var string
  89. */
  90. private $connector;
  91. /**
  92. * 缓存键。
  93. * Cache key.
  94. *
  95. * @access private
  96. * @var string
  97. */
  98. private $key = '';
  99. /**
  100. * 缓存标签。
  101. * Cache label.
  102. *
  103. * @access private
  104. * @var array
  105. */
  106. private $labels = [];
  107. /**
  108. * 影响缓存的表名。
  109. * Table name that affects cache.
  110. *
  111. * @access private
  112. * @var string
  113. */
  114. private $table = '';
  115. /**
  116. * 影响缓存的事件。
  117. * Event that affects cache.
  118. *
  119. * @access private
  120. * @var string
  121. */
  122. private $event = '';
  123. /**
  124. * 影响缓存的 WHERE 子句。
  125. * WHERE clause that affects cache.
  126. *
  127. * @access private
  128. * @var string
  129. */
  130. private $where = '';
  131. /**
  132. * 受影响的对象列表。
  133. * Affected object list.
  134. *
  135. * @access private
  136. * @var array
  137. */
  138. private $objects = [];
  139. /**
  140. * 构造函数,根据配置文件初始化缓存对象。
  141. * Constructor, initialize cache object according to the configuration file.
  142. *
  143. * @param object $app 全局应用程序对象。
  144. * @access public
  145. * @return void
  146. */
  147. public function __construct($app)
  148. {
  149. $this->app = $app;
  150. $this->dao = $app->dao;
  151. $this->dbh = $app->dbh;
  152. $this->config = $app->config;
  153. if(empty($this->config->cache->enable)) return $this->log('The cache is not enabled', __FILE__, __LINE__);
  154. $driver = ucfirst(strtolower($this->config->cache->driver));
  155. if(!in_array($driver, self::DRIVER_LIST)) return $this->log("Driver {$driver} is not supported.", __FILE__, __LINE__);
  156. if($driver != self::DRIVER_FILE && !extension_loaded($driver)) return $this->log("Driver ext-{$driver} is not loaded.", __FILE__, __LINE__);
  157. $connector = $driver == self::DRIVER_REDIS ? ':' : '-';
  158. $className = "ZenTao\Cache\Driver\\{$driver}Driver";
  159. $scope = $this->config->cache->scope;
  160. $namespace = $this->config->cache->namespace;
  161. $lifetime = $this->config->cache->lifetime;
  162. $redis = $this->config->redis;
  163. $this->setNamespace($namespace);
  164. $this->setConnector($connector);
  165. if($driver == self::DRIVER_APCU) return $this->cache = new $className($namespace, $lifetime, $scope, $connector);
  166. if($driver == self::DRIVER_REDIS) return $this->cache = new $className($namespace, $lifetime, $scope, $connector, $redis);
  167. if($driver == self::DRIVER_YAC) return $this->cache = new $className($namespace, $lifetime);
  168. if($driver == self::DRIVER_FILE) return $this->cache = new $className($namespace, $lifetime, $app->getCacheRoot());
  169. }
  170. /**
  171. * 设置缓存状态。
  172. * Set cache status.
  173. *
  174. * @param string $status 缓存状态。enabled: 启用缓存;disabled: 禁用缓存。
  175. * @access private
  176. * @return void
  177. */
  178. private function setStatus($status)
  179. {
  180. $this->status = $status;
  181. }
  182. /**
  183. * 设置缓存命名空间。
  184. * Set cache namespace.
  185. *
  186. * @param string $namespace 缓存命名空间。
  187. * @access private
  188. * @return void
  189. */
  190. private function setNamespace($namespace)
  191. {
  192. $this->namespace = $namespace;
  193. }
  194. /**
  195. * 设置缓存键连接符。
  196. * Set cache key connector.
  197. *
  198. * @param string $connector 缓存键连接符。
  199. * @access private
  200. * @return void
  201. */
  202. private function setConnector($connector)
  203. {
  204. $this->connector = $connector;
  205. }
  206. /**
  207. * 设置缓存键。
  208. * Set cache key.
  209. *
  210. * @param string $key 缓存键。
  211. * @access private
  212. * @return void
  213. */
  214. private function setKey($key)
  215. {
  216. $this->key = $key;
  217. }
  218. /**
  219. * 设置影响缓存的表名。
  220. * Set the table name that affects the cache.
  221. *
  222. * @param string $table 表名。
  223. * @access private
  224. * @return void
  225. */
  226. private function setTable($table)
  227. {
  228. $this->table = $table;
  229. }
  230. /**
  231. * 设置影响缓存的事件。
  232. * Set the event that affects the cache.
  233. *
  234. * @param string $event 事件。
  235. * @access private
  236. * @return void
  237. */
  238. private function setEvent($event)
  239. {
  240. $this->event = $event;
  241. }
  242. /**
  243. * 设置影响缓存的 WHERE 子句。
  244. * Set the WHERE clause that affects the cache.
  245. *
  246. * @param string $where WHERE 子句。
  247. * @access private
  248. * @return void
  249. */
  250. private function setWhere($where)
  251. {
  252. $this->where = $where;
  253. }
  254. /**
  255. * 设置受影响的对象列表。
  256. * Set the list of affected objects.
  257. *
  258. * @param array $objects 对象列表。
  259. * @access private
  260. * @return void
  261. */
  262. private function setObjects($objects)
  263. {
  264. $this->objects = $objects;
  265. }
  266. /**
  267. * 获取表的主键字段。
  268. * Get the primary key field of the table.
  269. *
  270. * @access private
  271. * @return string
  272. */
  273. public function getTableField()
  274. {
  275. return $this->config->cache->raw[$this->table];
  276. }
  277. /**
  278. * 获取表的缓存代号。
  279. * Get the cache code of the table.
  280. *
  281. * @access private
  282. * @return string
  283. */
  284. private function getTableCode()
  285. {
  286. return str_replace(['`', $this->config->db->prefix], '', $this->table);
  287. }
  288. /**
  289. * 获取原始数据类型缓存的键。该缓存用于保存表的原始数据。
  290. * Get the key of the raw data type cache. This cache is used to save the original data of the table.
  291. *
  292. * @param string $code 缓存代号。
  293. * @param int|string $id 主键值。
  294. * @access private
  295. * @return string
  296. */
  297. private function getRawCacheKey($code, $id)
  298. {
  299. return $this->createKey('raw', $code, $id);
  300. }
  301. /**
  302. * 获取集合类型缓存的键。该缓存用于保存表的主键字段。
  303. * Get the key of the set type cache. This cache is used to save the primary key field of the table.
  304. *
  305. * @param string $code 缓存代号。
  306. * @access private
  307. * @return string
  308. */
  309. private function getSetCacheKey($code)
  310. {
  311. return $this->createKey('set', $code, 'list');
  312. }
  313. /**
  314. * 获取结果类型缓存的键。该缓存用于保存表的计算结果。
  315. * Get the key of the result type cache. This cache is used to save the calculation results of the table.
  316. *
  317. * @param string $key 缓存键。
  318. * @access private
  319. * @return string
  320. */
  321. private function getResCacheKey($key)
  322. {
  323. $args = explode('_', str_replace('cache', 'res', strtolower($key)));
  324. return $this->createKey(...$args);
  325. }
  326. /**
  327. * 新增数据时更新缓存。
  328. * Update cache when adding data.
  329. *
  330. * @access private
  331. * @return void
  332. */
  333. private function create()
  334. {
  335. $code = $this->getTableCode();
  336. $setCacheKey = $this->getSetCacheKey($code);
  337. $objectIdList = $this->cache->get($setCacheKey);
  338. if(!is_array($objectIdList)) return;
  339. $objectID = $this->dbh->lastInsertID();
  340. if(!$objectID) return $this->log('Failed to fetch last insert id.', __FILE__, __LINE__);
  341. /* 获取新增的数据。Get the new data. */
  342. $sql = $this->dao->select('*')->from($this->table)->where('id')->eq($objectID)->sqlobj->get();
  343. $object = $this->query($sql, 'fetch');
  344. if(!$object) return $this->log('Failed to fetch new object. The sql is: ' . $sql, __FILE__, __LINE__);
  345. /* 把新增的数据保存到缓存中。Save the new data to cache. */
  346. $field = $this->getTableField();
  347. $rawCacheKey = $this->getRawCacheKey($code, $object->$field);
  348. $this->cache->set($rawCacheKey, $object);
  349. /* 把新增的数据的 id 保存到缓存中。Save the id of the new data to cache. */
  350. $this->cache->set($setCacheKey, $objectIdList ? array_merge($objectIdList, [$object->$field]) : [$object->$field]);
  351. if(empty($this->config->cache->res[$this->table])) return;
  352. /* 删除受影响的缓存。Delete the affected cache. */
  353. $this->deleteAffectedCache([$object]);
  354. }
  355. /**
  356. * 更新数据时更新缓存。
  357. * Update cache when updating data.
  358. *
  359. * @access private
  360. * @return void
  361. */
  362. private function update()
  363. {
  364. if(empty($this->objects)) return $this->log('No objects to update.', __FILE__, __LINE__);
  365. $field = $this->getTableField();
  366. $code = $this->getTableCode();
  367. /* 获取被更新数据的 id 列表。Get the id list of the updated data. */
  368. $objectIdList = array_map(function($object) use ($field) { return $object->$field; }, $this->objects);
  369. /* 获取更新后的数据。Get the updated data. */
  370. $sql = $this->dao->select('*')->from($this->table)->where($field)->in($objectIdList)->sqlobj->get();
  371. $objects = $this->query($sql, 'fetchAll', $field);
  372. if(!$objects) return $this->log('Failed to fetch updated objects. The sql is: ' . $sql, __FILE__, __LINE__);
  373. /* 把更新后的数据保存到缓存中。Save the updated data to cache. */
  374. $values = [];
  375. foreach($objects as $object)
  376. {
  377. $rawCacheKey = $this->getRawCacheKey($code, $object->$field);
  378. $values[$rawCacheKey] = $object;
  379. }
  380. $this->cache->setMultiple($values);
  381. if(empty($this->config->cache->res[$this->table])) return;
  382. /* 获取受影响的数据。Get the affected data. */
  383. foreach($objectIdList as $objectID)
  384. {
  385. if(isset($this->objects[$objectID]) && isset($objects[$objectID]) && $this->objects[$objectID] == $objects[$objectID]) unset($this->objects[$objectID], $objects[$objectID]);
  386. }
  387. $affectedObjects = array_merge($this->objects, $objects);
  388. /* 删除受影响的缓存。Delete the affected cache. */
  389. $this->deleteAffectedCache($affectedObjects);
  390. }
  391. /**
  392. * 删除数据时更新缓存。
  393. * Update cache when deleting data.
  394. *
  395. * @access private
  396. * @return void
  397. */
  398. private function delete()
  399. {
  400. if(empty($this->objects)) return $this->log('No objects to delete.', __FILE__, __LINE__);
  401. $code = $this->getTableCode();
  402. $setCacheKey = $this->getSetCacheKey($code);
  403. $objectIdList = $this->cache->get($setCacheKey);
  404. if(!is_array($objectIdList)) return;
  405. $field = $this->getTableField();
  406. /* 把被删除的数据从缓存中删除。Delete the deleted data from cache. */
  407. $affectedKeys = [];
  408. foreach($this->objects as $object) $affectedKeys[] = $this->getRawCacheKey($code, $object->$field);
  409. $this->cache->deleteMultiple($affectedKeys);
  410. /* 把被删除的数据的 id 从缓存中删除。Delete the id of the deleted data from cache. */
  411. $this->cache->set($setCacheKey, array_diff($objectIdList, array_map(function($object) use ($field) { return $object->$field; }, $this->objects)));
  412. if(empty($this->config->cache->res[$this->table])) return;
  413. /* 删除受影响的缓存。Delete the affected cache. */
  414. $this->deleteAffectedCache($this->objects);
  415. }
  416. /**
  417. * 删除受影响的缓存。
  418. * Delete the affected cache.
  419. *
  420. * @param array $objects 受影响的对象列表。
  421. * @access private
  422. * @return void
  423. */
  424. private function deleteAffectedCache($objects)
  425. {
  426. /* 根据受影响的数据查找受影响的缓存。Find the affected cache by the affected data. */
  427. $keys = [];
  428. foreach($this->config->cache->res[$this->table] as $res)
  429. {
  430. $res = (object)$res;
  431. /* 如果没有设置关联字段则整个缓存都受影响。If no associated fields are set, the entire cache is affected. */
  432. if(empty($res->fields))
  433. {
  434. $keys[] = $this->getResCacheKey($res->name);
  435. continue;
  436. }
  437. /* 根据关联字段查找受影响的缓存。Find the affected cache by the associated fields. */
  438. foreach($objects as $object)
  439. {
  440. $key = $this->getResCacheKey($res->name);
  441. foreach($res->fields as $field)
  442. {
  443. if(!isset($object->$field)) return $this->log("Field {$field} does not exist in table {$this->table}.", __FILE__, __LINE__);
  444. $key .= $this->connector . $object->$field;
  445. }
  446. $keys[] = $key;
  447. }
  448. }
  449. /* 删除受影响的缓存。Delete the affected cache. */
  450. $this->cache->deleteMultiple($keys);
  451. }
  452. /**
  453. * 执行数据库查询操作。
  454. * Execute database query operation.
  455. *
  456. * @param string $sql SQL语句。
  457. * @param string $function 查询函数。
  458. * @param string $field 字段名。
  459. * @access private
  460. * @return array|object
  461. */
  462. private function query($sql, $function = 'fetchAll', $field = '')
  463. {
  464. $rows = $this->dao->query($sql)->$function();
  465. if($function == 'fetchAll' && !empty($field))
  466. {
  467. $objects = [];
  468. foreach($rows as $row) $objects[$row->$field] = $row;
  469. return $objects;
  470. }
  471. return $rows;
  472. }
  473. /**
  474. * 检查表是否有缓存设置。
  475. * Check if the table has cache settings.
  476. *
  477. * @param string $table 表名。
  478. * @access private
  479. * @return bool
  480. */
  481. private function checkTable($table = '')
  482. {
  483. if(empty($table)) $table = $this->table;
  484. return !empty($this->config->cache->raw[$table]);
  485. }
  486. /**
  487. * 记录日志信息。
  488. * Record log information.
  489. *
  490. * @param string $message 日志信息。
  491. * @param string $file 文件名。
  492. * @param string $line 行号。
  493. * @access private
  494. * @return false
  495. */
  496. private function log($message, $file, $line)
  497. {
  498. if(!$this->config->debug) return false;
  499. $runMode = PHP_SAPI == 'cli' ? '_cli' : '';
  500. $logFile = $this->app->getLogRoot() . 'cache' . $runMode . '.' . date('Ymd') . '.log.php';
  501. if(!file_exists($logFile)) file_put_contents($logFile, '<?php die(); ?' . ">\n");
  502. $content = date('Ymd H:i:s') . ': ' . $this->getURI() . "\nError: {$message} in $file on line $line\n";
  503. file_put_contents($logFile, $content, FILE_APPEND);
  504. if($this->config->debug >= 2) $this->app->triggerError($message, __FILE__, __LINE__, true);
  505. return false;
  506. }
  507. /**
  508. * 获取 URI。
  509. * Get URI.
  510. *
  511. * @access private
  512. * @return string
  513. */
  514. private function getURI()
  515. {
  516. $uri = $this->app->getURI();
  517. if($uri) return $uri;
  518. if($this->config->requestType == 'GET') return $_SERVER['REQUEST_URI'];
  519. if($this->config->requestType == 'PATH_INFO' || $this->config->requestType == 'PATH_INFO2')
  520. {
  521. $pathInfo = $this->app->getPathInfo();
  522. if(empty($pathInfo)) return '';
  523. $dotPos = strrpos($pathInfo, '.');
  524. if($dotPos) return substr($pathInfo, 0, $dotPos);
  525. return $pathInfo;
  526. }
  527. return '';
  528. }
  529. /**
  530. * 初始化指定表的缓存。
  531. * Initialize the cache of the specified table.
  532. *
  533. * @access private
  534. * @return array
  535. */
  536. private function initTableCache()
  537. {
  538. $field = $this->getTableField();
  539. $sql = $this->dao->select('*')->from($this->table)->sqlobj->get();
  540. $objects = $this->query($sql, 'fetchAll', $field);
  541. if(!$objects) return [];
  542. $values = [];
  543. $code = $this->getTableCode();
  544. foreach($objects as $key => $object)
  545. {
  546. $rawCacheKey = $this->getRawCacheKey($code, $key);
  547. $values[$rawCacheKey] = $object;
  548. }
  549. $this->cache->setMultiple($values);
  550. $setCacheKey = $this->getSetCacheKey($code);
  551. $this->cache->set($setCacheKey, array_keys($objects));
  552. return $objects;
  553. }
  554. /**
  555. * 生成缓存键。
  556. * Generate cache key.
  557. *
  558. * @param mixed ...$args 缓存键的参数。
  559. * @access public
  560. * @return string
  561. */
  562. public function createKey(...$args)
  563. {
  564. return $this->namespace . $this->connector . implode($this->connector, $args);
  565. }
  566. /**
  567. * 从缓存中获取指定表指定 id 的数据。
  568. * Get the data of the specified table and id from the cache.
  569. *
  570. * @param string $table 表名。
  571. * @param int|string $id 主键值。
  572. * @access public
  573. * @return object|false
  574. */
  575. public function fetch($table, $id)
  576. {
  577. if($this->status == 'disabled') return null;
  578. if(!$this->checkTable($table)) return $this->log("Table {$table} is not set in the cache configuration", __FILE__, __LINE__);
  579. if(empty($table)) return $this->log('The table name is empty', __FILE__, __LINE__);
  580. if(empty($id)) return $this->log('The id is empty', __FILE__, __LINE__);
  581. $this->setTable($table);
  582. $code = $this->getTableCode();
  583. $key = $this->getRawCacheKey($code, $id);
  584. $object = $this->cache->get($key);
  585. if($object) return $object;
  586. $objects = $this->initTableCache();
  587. return isset($objects[$id]) ? $objects[$id] : null;
  588. }
  589. /**
  590. * 从缓存中获取指定表的所有数据。
  591. * Get all data of the specified table from the cache.
  592. *
  593. * @param string $table 表名。
  594. * @param array $objectIdList 主键值列表。
  595. * @access public
  596. * @return array
  597. */
  598. public function fetchAll($table, $objectIdList = [])
  599. {
  600. if($this->status == 'disabled') return [];
  601. if(!$this->checkTable($table)) return $this->log("Table {$table} is not set in the cache configuration", __FILE__, __LINE__);
  602. if(empty($table)) return $this->log('The table name is empty', __FILE__, __LINE__);
  603. $this->setTable($table);
  604. $code = $this->getTableCode();
  605. /* 尝试获取指定表的所有主键字段的值。Try to get the values of all primary key fields of the specified table. */
  606. $setCacheKey = $this->getSetCacheKey($code);
  607. $allObjectIdList = $this->cache->get($setCacheKey);
  608. /* 如果主键字段的值为空,则初始化指定表的缓存。If the value of the primary key field is empty, initialize the cache of the specified table. */
  609. if(!$allObjectIdList)
  610. {
  611. $allData = $this->initTableCache();
  612. if(empty($objectIdList)) return $allData;
  613. return array_intersect_key($allData, $objectIdList);
  614. }
  615. /* 如果主键字段的值不为空,则从缓存中获取数据。If the value of the primary key field is not empty, get the data from the cache. */
  616. if(empty($objectIdList)) $objectIdList = $allObjectIdList;
  617. $keys = [];
  618. foreach($objectIdList as $objectID)
  619. {
  620. if(!$objectID) continue;
  621. $keys[$objectID] = $this->getRawCacheKey($code, $objectID);
  622. }
  623. $objects = $this->cache->getMultiple(array_values($keys));
  624. /* 如果缓存中没有全部的数据,则从数据库中获取缺失的数据。If not all data in cache, get the missing data from the database. */
  625. if(count($keys) > count($objects))
  626. {
  627. $lostObjects = [];
  628. $diffIdList = array_keys(array_diff($keys, array_keys($objects)));
  629. $sql = $this->dao->select('*')->from($table)->where('id')->in($diffIdList)->sqlobj->get();
  630. $diffObjects = $this->query($sql, 'fetchAll');
  631. foreach($diffObjects as $object)
  632. {
  633. $rawCacheKey = $this->getRawCacheKey($code, $object->id);
  634. $lostObjects[$rawCacheKey] = $object;
  635. }
  636. /* 把缺失的数据保存到缓存中。Save the missing data to cache. */
  637. $this->cache->setMultiple($lostObjects);
  638. $objects += $lostObjects;
  639. }
  640. if(!$objects) return [];
  641. $result = [];
  642. $field = $this->getTableField();
  643. foreach($objects as $object) $result[$object->$field] = $object;
  644. return $result;
  645. }
  646. /**
  647. * 从自动缓存的计算结果中获取符合查询条件的数据。
  648. * Get data that meets the query conditions from the calculation results of the automatic cache.
  649. *
  650. * @param string $table 表名。
  651. * @param array $conditions 查询条件。
  652. * @access public
  653. * @return array
  654. */
  655. public function fetchAutoCache($table, $conditions)
  656. {
  657. if($this->status == 'disabled') return [];
  658. if(!$this->checkTable($table)) return $this->log("Table {$table} is not set in the cache configuration", __FILE__, __LINE__);
  659. $conditions = array_filter($conditions, function($condition)
  660. {
  661. return $condition['operator'] == 'eq';
  662. });
  663. if(empty($conditions)) return [];
  664. $this->setTable($table);
  665. /* 按字段排序查询条件以确保缓存的唯一性。Sort the query conditions by field to ensure the uniqueness of the cache. */
  666. usort($conditions, function($a, $b)
  667. {
  668. return strcmp($a['field'], $b['field']);
  669. });
  670. /* 根据查询条件生成缓存键。Generate a cache key based on the query conditions. */
  671. $values = array_column($conditions, 'value');
  672. $code = $this->getTableCode();
  673. $cacheKey = $this->createKey('cache', $code, 'auto', ...$values);
  674. /* 从缓存中获取符合查询条件的主键字段的值。Get the value of the primary key field that meets the query conditions from the cache. */
  675. $idList = $this->getByKey($cacheKey);
  676. if(is_null($idList))
  677. {
  678. /* 如果没有缓存,从数据库中获取符合查询条件的主键字段的值并更新缓存。If there is no cache, get the value of the primary key field that meets the query conditions from the database and update the cache. */
  679. $field = $this->getTableField();
  680. $dao = $this->dao->select($field)->from($table)->where('1=1');
  681. foreach($conditions as $condition) $dao->andWhere($condition['field'])->eq($condition['value']);
  682. $sql = $dao->sqlobj->get();
  683. $objects = $this->query($sql, 'fetchAll');
  684. foreach($objects as $object) $idList[] = $object->$field;
  685. $this->saveByKey($cacheKey, $idList);
  686. /* 把缓存键写入数据库以便执行 dao::exec() 时自动更新。Write the cache key to the database for automatic update when executing dao::exec(). */
  687. $fields = implode(',', array_column($conditions, 'field'));
  688. $sql = $this->dao->select('1')->from(TABLE_AUTOCACHE)->where('code')->eq($code)->andWhere('fields')->eq($fields)->sqlobj->get();
  689. $autoCache = $this->query($sql, 'fetch');
  690. if(!$autoCache)
  691. {
  692. $autoCache = new \stdClass();
  693. $autoCache->code = $code;
  694. $autoCache->fields = $fields;
  695. $sql = $this->dao->insert(TABLE_AUTOCACHE)->data($autoCache)->get();
  696. try
  697. {
  698. $this->dbh->exec($sql);
  699. }
  700. catch(PDOException $e)
  701. {
  702. $this->dao->sqlError($e);
  703. }
  704. }
  705. }
  706. if(!$idList) return [];
  707. /* 根据主键字段的值从缓存中获取数据。Get data from the cache according to the value of the primary key field. */
  708. return $this->fetchAll($table, $idList);
  709. }
  710. /**
  711. * 设置缓存键。
  712. * Set cache key.
  713. *
  714. * @param string $key 缓存键。
  715. * @param mixed ...$args 缓存键的参数。
  716. * @access public
  717. * @return object
  718. */
  719. public function key($key, ...$args)
  720. {
  721. if(empty($this->config->cache->keys[$key])) return $this->log("Key {$key} is not defined", __FILE__, __LINE__);
  722. $cache = $this->config->cache->keys[$key];
  723. if(!empty($cache->fields) && !empty($args))
  724. {
  725. $tableFields = $this->dao->descTable($cache->table);
  726. foreach($cache->fields as $index => $field)
  727. {
  728. if(!isset($tableFields[$field])) return $this->log("Field {$field} does not exist in table {$cache->table}", __FILE__, __LINE__);
  729. if(!isset($args[$index])) continue;
  730. $tableField = $tableFields[$field];
  731. if(stripos($tableField->type, 'int') !== false) $args[$index] = (int) $args[$index];
  732. if(stripos($tableField->type, 'float') !== false) $args[$index] = (float)$args[$index];
  733. if(stripos($tableField->type, 'decimal') !== false) $args[$index] = (float)$args[$index];
  734. if(stripos($tableField->type, 'double') !== false) $args[$index] = (float)$args[$index];
  735. }
  736. }
  737. $key = $this->getResCacheKey(constant($key));
  738. foreach($args as $arg) $key .= $this->connector . $arg;
  739. $this->setKey($key);
  740. return $this;
  741. }
  742. /**
  743. * 设置缓存标签。
  744. * Set cache label.
  745. *
  746. * @param string $label 缓存标签。
  747. * @access public
  748. * @return object
  749. */
  750. public function label($label)
  751. {
  752. if(empty($label)) return $this->log('The label is empty', __FILE__, __LINE__);
  753. if(empty($this->key)) return $this->log('The key is empty', __FILE__, __LINE__);
  754. if(isset($this->labels[$label])) return $this->log("Label {$label} already used", __FILE__, __LINE__);
  755. $this->labels[$label] = $this->key;
  756. return $this;
  757. }
  758. /**
  759. * 根据当前缓存键获取缓存。
  760. * Get cache according to the current cache key.
  761. *
  762. * @access public
  763. * @return mixed
  764. */
  765. public function get()
  766. {
  767. if($this->status == 'disabled') return null;
  768. if(empty($this->key)) return $this->log('The key is empty', __FILE__, __LINE__);
  769. return $this->cache->get($this->key);
  770. }
  771. /**
  772. * 根据指定缓存键获取缓存。
  773. * Get cache according to the specified cache key.
  774. *
  775. * @param string $key 缓存键。
  776. * @access public
  777. * @return mixed
  778. */
  779. public function getByKey($key)
  780. {
  781. if($this->status == 'disabled') return null;
  782. if(empty($key)) return $this->log('The key is empty', __FILE__, __LINE__);
  783. return $this->cache->get($key);
  784. }
  785. /**
  786. * 根据当前缓存键保存缓存。
  787. * Save cache according to the current cache key.
  788. *
  789. * @param mixed $value 缓存值。
  790. * @access public
  791. * @return bool
  792. */
  793. public function save($value)
  794. {
  795. if($this->status == 'disabled') return false;
  796. if(empty($this->key)) return $this->log('The key is empty', __FILE__, __LINE__);
  797. return $this->cache->set($this->key, $value);
  798. }
  799. /**
  800. * 根据指定缓存建保存缓存。
  801. * Save cache according to the specified cache key.
  802. *
  803. * @param string $key 缓存键。
  804. * @param mixed $value 缓存值。
  805. * @param int $ttl 缓存时间。
  806. * @access public
  807. * @return bool
  808. */
  809. public function saveByKey($key, $value, $ttl = 0)
  810. {
  811. if($this->status == 'disabled') return false;
  812. if(empty($key)) return $this->log('The key is empty', __FILE__, __LINE__);
  813. return $this->cache->set($key, $value, $ttl);
  814. }
  815. /**
  816. * 根据指定缓存标签保存缓存。
  817. * Save cache according to the specified cache label.
  818. *
  819. * @param string $label 缓存标签。
  820. * @param mixed $value 缓存值。
  821. * @access public
  822. * @return bool
  823. */
  824. public function saveByLabel($label, $value)
  825. {
  826. if($this->status == 'disabled') return false;
  827. if(empty($label)) return $this->log('The label is empty', __FILE__, __LINE__);
  828. if(empty($this->labels[$label])) return $this->log("Label {$label} does not exist", __FILE__, __LINE__);
  829. return $this->cache->set($this->labels[$label], $value);
  830. }
  831. /**
  832. * 重置缓存相关设置项。
  833. * Reset cache related settings.
  834. *
  835. * @access public
  836. * @return void
  837. */
  838. public function reset()
  839. {
  840. $this->setKey('');
  841. $this->setTable('');
  842. $this->setEvent('');
  843. $this->setWhere('');
  844. $this->setObjects([]);
  845. }
  846. /**
  847. * 执行数据库操作前准备更新缓存需要的设置项。
  848. * Prepare the settings required to update the cache before executing the database operation.
  849. *
  850. * @param string $table 表名。
  851. * @param string $event 事件。
  852. * @param string $sql SQL 语句。
  853. * @access public
  854. * @return void
  855. */
  856. public function prepare($table, $event, $sql)
  857. {
  858. $this->reset();
  859. if($this->status == 'disabled') return;
  860. if(!$this->checkTable($table)) return;
  861. if(empty($table)) return $this->log('Table name is required.', __FILE__, __LINE__);
  862. if(empty($event)) return $this->log('Event type is required.', __FILE__, __LINE__);
  863. if(empty($sql)) return $this->log('SQL statement is required.', __FILE__, __LINE__);
  864. $this->setTable($table);
  865. $this->setEvent($event);
  866. if($event == 'update' || $event == 'delete')
  867. {
  868. /* 获取 WHERE 子句的内容。Get the content of WHERE clause. */
  869. $whereLen = strlen(\DAO::WHERE);
  870. $wherePOS = strrpos($sql, \DAO::WHERE);
  871. $groupPOS = strrpos($sql, \DAO::GROUPBY);
  872. $havingPOS = strrpos($sql, \DAO::HAVING);
  873. $orderPOS = strrpos($sql, \DAO::ORDERBY);
  874. $limitPOS = strrpos($sql, \DAO::LIMIT);
  875. $splitPOS = $orderPOS ? $orderPOS : $limitPOS;
  876. $splitPOS = $havingPOS ? $havingPOS : $splitPOS;
  877. $splitPOS = $groupPOS ? $groupPOS : $splitPOS;
  878. $where = '';
  879. if($wherePOS)
  880. {
  881. if($splitPOS)
  882. {
  883. $where = substr($sql, $wherePOS + $whereLen, $splitPOS - $wherePOS - $whereLen);
  884. }
  885. else
  886. {
  887. $where = substr($sql, $wherePOS + $whereLen);
  888. }
  889. }
  890. /* 执行操作后数据已经被修改,所以需要提前获取被影响的数据。*/
  891. $field = $this->getTableField();
  892. $sql = $this->dao->select('*')->from($table)->beginIF($where)->where($where)->fi()->sqlobj->get();
  893. $objects = $this->query($sql, 'fetchAll', $field);
  894. $this->setWhere($where);
  895. $this->setObjects($objects);
  896. }
  897. }
  898. /**
  899. * 执行数据库操作后更新缓存。
  900. * Update the cache after executing the database operation.
  901. *
  902. * @access public
  903. * @return void
  904. */
  905. public function sync()
  906. {
  907. if($this->status == 'disabled') return;
  908. if(!$this->checkTable()) return;
  909. if(empty($this->table)) return $this->log('Table name is required.', __FILE__, __LINE__);
  910. if(empty($this->event)) return $this->log('Event type is required.', __FILE__, __LINE__);
  911. if($this->event == 'insert') $this->create();
  912. if($this->event == 'update') $this->update();
  913. if($this->event == 'delete') $this->delete();
  914. $this->reset();
  915. }
  916. /**
  917. * 清空缓存。
  918. * Clear cache.
  919. *
  920. * @access public
  921. * @return void
  922. */
  923. public function clear()
  924. {
  925. $this->setStatus('disabled');
  926. $this->cache->clear();
  927. $this->setStatus('normal');
  928. }
  929. /**
  930. * 关闭缓存连接。
  931. * Close cache connection.
  932. *
  933. * @access public
  934. * @return void
  935. */
  936. public function close()
  937. {
  938. if(method_exists($this->cache, 'close')) $this->cache->close();
  939. }
  940. /**
  941. * 获取内存使用情况。
  942. * Get memory usage.
  943. *
  944. * @param string $type
  945. * @return string
  946. */
  947. public function memory($type)
  948. {
  949. return $this->cache->memory($type);
  950. }
  951. }