DBAgileDev.class.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <?php
  2. namespace KIF\Dao;
  3. use KIF\Verify;
  4. use KIF\Exception\ParamsException;
  5. use KIF\Data\ResultWrapper;
  6. /**
  7. * 数据库操作的敏捷开发模式抽象类。 敏捷开发(Agile development)
  8. * 目的:统一数据库表的设计模式,为 增、删、改 提供便捷
  9. * PS: 通过 cas_token 字段,提供冲突检测机制(乐观锁),保证高并发下的数据安全
  10. * 这是模仿自 php 的 Memcached 扩展,非常好的功能,故借鉴过来
  11. * @author gaoxiaogang@gmail.com
  12. *
  13. */
  14. abstract class DBAgileDev extends AbstractDao {
  15. /**
  16. * 除敏捷模式必须要求的字段外,多增加的字段
  17. * 该实例变量由继承类填充
  18. * @var array
  19. */
  20. protected $other_field = array();
  21. /**
  22. * 数据库里的真实字段
  23. * 这个实例变量的值是自动计算出来的
  24. * @var array
  25. */
  26. private $real_field;
  27. /**
  28. *
  29. * Enter description here ...
  30. * @param unknown_type $master_flag
  31. * @return KIF\Dao\DBAgileDev
  32. */
  33. public function __construct($master_flag = 'default') {
  34. parent::__construct($master_flag);
  35. # 固定字段
  36. $fixed_field = array(
  37. $this->primaryKey,
  38. 'extend',
  39. 'create_time',
  40. 'update_time',
  41. 'cas_token',// 通过这个字段,提供冲突检测机制(乐观锁),保证高并发下的数据安全
  42. );
  43. $this->real_field = array_merge($fixed_field, $this->other_field);
  44. }
  45. /**
  46. *
  47. * 添加一条记录
  48. * @param array $info
  49. * @param string $actionType 动态类型,这个参数控制以下行为中的一种: create、replace、insertIgnore
  50. *
  51. * ## 当$actionType的值为parent::PARAM_CREATE_ACTION_ONDUPLICATE时,该值才有意义。##
  52. * !!! 无法通过指定 $onDuplicate 来修改存储在 extend 字段里的值,因为没法把已存在的 extend 的值取出来,做一个merge。 !!!
  53. * @param array $onDuplicate 如果重复时,需要更新的信息。如果不指定,则使用$tableInfo的值,即认为要全部更新
  54. * ##
  55. *
  56. * @throws ParamsException actionType 参数非法
  57. * @return int | boolean
  58. */
  59. public function add(array $info, $actionType = parent::PARAM_CREATE_ACTION_INSERT, array $onDuplicate = array()) {
  60. if (!isset($info['create_time']) || !Verify::unsignedInt($info['create_time'])) {
  61. $info['create_time'] = time();
  62. }
  63. if (!isset($info['update_time']) || !Verify::unsignedInt($info['update_time'])) {
  64. $info['update_time'] = time();
  65. }
  66. $extend = array();
  67. foreach ($info as $tmpK => $tmpV) {
  68. if (!in_array($tmpK, $this->real_field)) {
  69. $extend[$tmpK] = $tmpV;
  70. unset($info[$tmpK]);
  71. }
  72. }
  73. # 从0开始。本来打算从1开始的,但考虑到 继续类可能会绕过 add 方法而实现自身的增加记录方法,将 cas_token默认值设为,有助于简化
  74. $info['cas_token'] = 0;
  75. $tableInfo = $info;
  76. $tableInfo['extend'] = serialize($extend);
  77. switch ($actionType) {
  78. case parent::PARAM_CREATE_ACTION_INSERT:
  79. $id = $this->create($tableInfo);
  80. break;
  81. case parent::PARAM_CREATE_ACTION_INSERT_IGNORE:
  82. $id = $this->insertIgnore($tableInfo);
  83. break;
  84. case parent::PARAM_CREATE_ACTION_REPLACE:
  85. $id = $this->replace($tableInfo);
  86. break;
  87. case parent::PARAM_CREATE_ACTION_ONDUPLICATE:
  88. if (empty($onDuplicate)) {
  89. $onDuplicate = $tableInfo;
  90. unset($onDuplicate['create_time']);
  91. unset($onDuplicate['cas_token']);
  92. } else {
  93. # 确保不允许修改 extend 字段里存储的值。因为没法把已存在的 extend 的值取出来,做一个merge。
  94. foreach ($onDuplicate as $tmpK => $tmpV) {
  95. if (!in_array($tmpK, $this->real_field)) {
  96. unset($onDuplicate[$tmpK]);
  97. }
  98. }
  99. }
  100. $onDuplicate['update_time'] = $info['update_time'];
  101. $onDuplicate['cas_token'] = SqlHelper::wrapperNoQuote("`cas_token` + 1");
  102. $id = $this->insertDuplicate($tableInfo, $onDuplicate);
  103. break;
  104. default:
  105. throw new ParamsException("invalid actionType");
  106. }
  107. return $id;
  108. }
  109. /**
  110. * 修改
  111. * !!!!!!
  112. * !!! $cas_token 提供了一种“检查并修改”机制,非常重要的优势, 在这样一个冲突检测机制(乐观锁)下, 我们才能保证高并发下的数据安全。
  113. * !!!!!!
  114. * 注:$info 或 $condition 里,一定要有一个指定 key 为 $this->primaryKey 对应的值
  115. * @param array $info
  116. * @param array $condition 条件
  117. * @param int $cas_token 如果$cas_token不为null,会执行一个“检查并修改”的操作。因此,仅当 $cas_token 与数据库里的值一致时,才会修改记录。
  118. * @return ResultWrapper 成功:修改并影响到了记录;
  119. * 失败:数据里含失败描述。
  120. * 另:cas_token检查不通过时,返回'CAS_TOKEN_NOT_MATCH'。这个返回值并不是严格意义上的正确,当$condition指定了条件,并且未匹配时,
  121. * 也会返回这个值。
  122. */
  123. public function modify(array $info, array $condition = null, $cas_token = null) {
  124. if (!isset($info[$this->primaryKey])) {
  125. if (!isset($condition[$this->primaryKey])) {
  126. return ResultWrapper::fail("请指定要修改的记录");
  127. }
  128. $info[$this->primaryKey] = $condition[$this->primaryKey];
  129. }
  130. if (!Verify::unsignedInt($info[$this->primaryKey])) {
  131. return ResultWrapper::fail("请指定有效的记录");
  132. }
  133. if (!isset($condition)) {
  134. $condition = array();
  135. }
  136. $useMasterFlag = $this->db->beginUseMaster();
  137. $oldSpecialSale = parent::get($info[$this->primaryKey]);
  138. $this->db->restore($useMasterFlag);
  139. if (!$oldSpecialSale) {
  140. return ResultWrapper::fail("不存在的记录:{$info[$this->primaryKey]}");;
  141. }
  142. # 找出待更新的扩展字段
  143. $extend = unserialize($oldSpecialSale['extend']);
  144. foreach ($info as $tmpK => $tmpV) {
  145. if (!in_array($tmpK, $this->real_field)) {
  146. $extend[$tmpK] = $tmpV;
  147. unset($info[$tmpK]);
  148. }
  149. }
  150. $info['cas_token'] = SqlHelper::wrapperNoQuote('`cas_token` + 1');
  151. $tableInfo = $info;
  152. $tableInfo['extend'] = serialize($extend);
  153. $tableInfo['update_time'] = time();
  154. $condition[$this->primaryKey] = $info[$this->primaryKey];
  155. if (Verify::naturalNumber($cas_token)) {
  156. $condition['cas_token'] = $cas_token;
  157. }
  158. $result = $this->update($tableInfo, $condition);
  159. if (!$result) {
  160. return ResultWrapper::fail("数据库update操作失败");
  161. }
  162. $affectedRows = $this->db->affectedRows();
  163. if (!Verify::unsignedInt($affectedRows)) {
  164. if (Verify::naturalNumber($cas_token)) {
  165. return ResultWrapper::fail("CAS_TOKEN_NOT_MATCH");
  166. } else {
  167. return ResultWrapper::fail("NOT_MATCH");
  168. }
  169. }
  170. return ResultWrapper::success();
  171. }
  172. /**
  173. * 获取一条记录
  174. * @param int $id 记录id
  175. * @param int $cas_token 引用传值,默认为 null
  176. * @return false | array
  177. */
  178. public function get($id, & $cas_token = null) {
  179. if (!Verify::unsignedInt($id)) {
  180. return false;
  181. }
  182. $result = $this->gets(array($id));
  183. if (!$result) {
  184. return false;
  185. }
  186. $tmpResult = array_pop($result);
  187. $cas_token = $tmpResult['cas_token'];
  188. return $tmpResult;
  189. }
  190. /**
  191. * 获取一批记录
  192. * @param array $ids 记录id集
  193. * @param array $cas_tokens 引用传值,默认为 null
  194. * @return false | array
  195. */
  196. public function gets(array $ids, array & $cas_tokens = null) {
  197. $result = parent::gets($ids);
  198. if (!$result) {
  199. return $result;
  200. }
  201. foreach ($result as & $tmpV) {
  202. $tmpExtend = array();
  203. $tmpExtend = unserialize($tmpV['extend']);
  204. unset($tmpV['extend']);
  205. # 将存在扩展字段里的信息提出来
  206. if (is_array($tmpExtend)) foreach ($tmpExtend as $tmpKK => $tmpVV) {
  207. if (!in_array($tmpKK, $this->real_field)) {
  208. $tmpV[$tmpKK] = $tmpVV;
  209. }
  210. }
  211. $cas_tokens[$tmpV[$this->primaryKey]] = $tmpV['cas_token'];
  212. }
  213. return $result;
  214. }
  215. /**
  216. *
  217. * 获取所有id集
  218. * @param string $order
  219. * @param mixed $limit
  220. * @return array
  221. */
  222. public function getsIdsAll($order = null, $limit = null) {
  223. $ids = parent::getsAllIds($order, $limit);
  224. return $ids;
  225. }
  226. /**
  227. *
  228. * 获取指定时间$update_time后的最新id集
  229. * @param mixed $limit
  230. * @return array
  231. */
  232. public function getsLatestIdsByUpdateTime($update_time, $limit = null) {
  233. $order = 'update_time ASC';
  234. $condition = array(
  235. 'update_time' => SqlHelper::addCompareOperator('>=', $update_time),
  236. );
  237. return $this->findIdsBy($condition, $limit, $order);
  238. }
  239. }