123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- <?php
- namespace KIF\Dao;
- use KIF\Verify;
- use KIF\Exception\ParamsException;
- use KIF\Data\ResultWrapper;
- /**
- * 数据库操作的敏捷开发模式抽象类。 敏捷开发(Agile development)
- * 目的:统一数据库表的设计模式,为 增、删、改 提供便捷
- * PS: 通过 cas_token 字段,提供冲突检测机制(乐观锁),保证高并发下的数据安全
- * 这是模仿自 php 的 Memcached 扩展,非常好的功能,故借鉴过来
- * @author gaoxiaogang@gmail.com
- *
- */
- abstract class DBAgileDev extends AbstractDao {
- /**
- * 除敏捷模式必须要求的字段外,多增加的字段
- * 该实例变量由继承类填充
- * @var array
- */
- protected $other_field = array();
- /**
- * 数据库里的真实字段
- * 这个实例变量的值是自动计算出来的
- * @var array
- */
- private $real_field;
- /**
- *
- * Enter description here ...
- * @param unknown_type $master_flag
- * @return KIF\Dao\DBAgileDev
- */
- public function __construct($master_flag = 'default') {
- parent::__construct($master_flag);
- # 固定字段
- $fixed_field = array(
- $this->primaryKey,
- 'extend',
- 'create_time',
- 'update_time',
- 'cas_token',// 通过这个字段,提供冲突检测机制(乐观锁),保证高并发下的数据安全
- );
- $this->real_field = array_merge($fixed_field, $this->other_field);
- }
- /**
- *
- * 添加一条记录
- * @param array $info
- * @param string $actionType 动态类型,这个参数控制以下行为中的一种: create、replace、insertIgnore
- *
- * ## 当$actionType的值为parent::PARAM_CREATE_ACTION_ONDUPLICATE时,该值才有意义。##
- * !!! 无法通过指定 $onDuplicate 来修改存储在 extend 字段里的值,因为没法把已存在的 extend 的值取出来,做一个merge。 !!!
- * @param array $onDuplicate 如果重复时,需要更新的信息。如果不指定,则使用$tableInfo的值,即认为要全部更新
- * ##
- *
- * @throws ParamsException actionType 参数非法
- * @return int | boolean
- */
- public function add(array $info, $actionType = parent::PARAM_CREATE_ACTION_INSERT, array $onDuplicate = array()) {
- if (!isset($info['create_time']) || !Verify::unsignedInt($info['create_time'])) {
- $info['create_time'] = time();
- }
- if (!isset($info['update_time']) || !Verify::unsignedInt($info['update_time'])) {
- $info['update_time'] = time();
- }
- $extend = array();
- foreach ($info as $tmpK => $tmpV) {
- if (!in_array($tmpK, $this->real_field)) {
- $extend[$tmpK] = $tmpV;
- unset($info[$tmpK]);
- }
- }
- # 从0开始。本来打算从1开始的,但考虑到 继续类可能会绕过 add 方法而实现自身的增加记录方法,将 cas_token默认值设为,有助于简化
- $info['cas_token'] = 0;
- $tableInfo = $info;
- $tableInfo['extend'] = serialize($extend);
- switch ($actionType) {
- case parent::PARAM_CREATE_ACTION_INSERT:
- $id = $this->create($tableInfo);
- break;
- case parent::PARAM_CREATE_ACTION_INSERT_IGNORE:
- $id = $this->insertIgnore($tableInfo);
- break;
- case parent::PARAM_CREATE_ACTION_REPLACE:
- $id = $this->replace($tableInfo);
- break;
- case parent::PARAM_CREATE_ACTION_ONDUPLICATE:
- if (empty($onDuplicate)) {
- $onDuplicate = $tableInfo;
- unset($onDuplicate['create_time']);
- unset($onDuplicate['cas_token']);
- } else {
- # 确保不允许修改 extend 字段里存储的值。因为没法把已存在的 extend 的值取出来,做一个merge。
- foreach ($onDuplicate as $tmpK => $tmpV) {
- if (!in_array($tmpK, $this->real_field)) {
- unset($onDuplicate[$tmpK]);
- }
- }
- }
- $onDuplicate['update_time'] = $info['update_time'];
- $onDuplicate['cas_token'] = SqlHelper::wrapperNoQuote("`cas_token` + 1");
- $id = $this->insertDuplicate($tableInfo, $onDuplicate);
- break;
- default:
- throw new ParamsException("invalid actionType");
- }
- return $id;
- }
- /**
- * 修改
- * !!!!!!
- * !!! $cas_token 提供了一种“检查并修改”机制,非常重要的优势, 在这样一个冲突检测机制(乐观锁)下, 我们才能保证高并发下的数据安全。
- * !!!!!!
- * 注:$info 或 $condition 里,一定要有一个指定 key 为 $this->primaryKey 对应的值
- * @param array $info
- * @param array $condition 条件
- * @param int $cas_token 如果$cas_token不为null,会执行一个“检查并修改”的操作。因此,仅当 $cas_token 与数据库里的值一致时,才会修改记录。
- * @return ResultWrapper 成功:修改并影响到了记录;
- * 失败:数据里含失败描述。
- * 另:cas_token检查不通过时,返回'CAS_TOKEN_NOT_MATCH'。这个返回值并不是严格意义上的正确,当$condition指定了条件,并且未匹配时,
- * 也会返回这个值。
- */
- public function modify(array $info, array $condition = null, $cas_token = null) {
- if (!isset($info[$this->primaryKey])) {
- if (!isset($condition[$this->primaryKey])) {
- return ResultWrapper::fail("请指定要修改的记录");
- }
- $info[$this->primaryKey] = $condition[$this->primaryKey];
- }
- if (!Verify::unsignedInt($info[$this->primaryKey])) {
- return ResultWrapper::fail("请指定有效的记录");
- }
- if (!isset($condition)) {
- $condition = array();
- }
- $useMasterFlag = $this->db->beginUseMaster();
- $oldSpecialSale = parent::get($info[$this->primaryKey]);
- $this->db->restore($useMasterFlag);
- if (!$oldSpecialSale) {
- return ResultWrapper::fail("不存在的记录:{$info[$this->primaryKey]}");;
- }
- # 找出待更新的扩展字段
- $extend = unserialize($oldSpecialSale['extend']);
- foreach ($info as $tmpK => $tmpV) {
- if (!in_array($tmpK, $this->real_field)) {
- $extend[$tmpK] = $tmpV;
- unset($info[$tmpK]);
- }
- }
- $info['cas_token'] = SqlHelper::wrapperNoQuote('`cas_token` + 1');
- $tableInfo = $info;
- $tableInfo['extend'] = serialize($extend);
- $tableInfo['update_time'] = time();
- $condition[$this->primaryKey] = $info[$this->primaryKey];
- if (Verify::naturalNumber($cas_token)) {
- $condition['cas_token'] = $cas_token;
- }
- $result = $this->update($tableInfo, $condition);
- if (!$result) {
- return ResultWrapper::fail("数据库update操作失败");
- }
- $affectedRows = $this->db->affectedRows();
- if (!Verify::unsignedInt($affectedRows)) {
- if (Verify::naturalNumber($cas_token)) {
- return ResultWrapper::fail("CAS_TOKEN_NOT_MATCH");
- } else {
- return ResultWrapper::fail("NOT_MATCH");
- }
- }
- return ResultWrapper::success();
- }
- /**
- * 获取一条记录
- * @param int $id 记录id
- * @param int $cas_token 引用传值,默认为 null
- * @return false | array
- */
- public function get($id, & $cas_token = null) {
- if (!Verify::unsignedInt($id)) {
- return false;
- }
- $result = $this->gets(array($id));
- if (!$result) {
- return false;
- }
- $tmpResult = array_pop($result);
- $cas_token = $tmpResult['cas_token'];
- return $tmpResult;
- }
- /**
- * 获取一批记录
- * @param array $ids 记录id集
- * @param array $cas_tokens 引用传值,默认为 null
- * @return false | array
- */
- public function gets(array $ids, array & $cas_tokens = null) {
- $result = parent::gets($ids);
- if (!$result) {
- return $result;
- }
- foreach ($result as & $tmpV) {
- $tmpExtend = array();
- $tmpExtend = unserialize($tmpV['extend']);
- unset($tmpV['extend']);
- # 将存在扩展字段里的信息提出来
- if (is_array($tmpExtend)) foreach ($tmpExtend as $tmpKK => $tmpVV) {
- if (!in_array($tmpKK, $this->real_field)) {
- $tmpV[$tmpKK] = $tmpVV;
- }
- }
- $cas_tokens[$tmpV[$this->primaryKey]] = $tmpV['cas_token'];
- }
- return $result;
- }
- /**
- *
- * 获取所有id集
- * @param string $order
- * @param mixed $limit
- * @return array
- */
- public function getsIdsAll($order = null, $limit = null) {
- $ids = parent::getsAllIds($order, $limit);
- return $ids;
- }
- /**
- *
- * 获取指定时间$update_time后的最新id集
- * @param mixed $limit
- * @return array
- */
- public function getsLatestIdsByUpdateTime($update_time, $limit = null) {
- $order = 'update_time ASC';
- $condition = array(
- 'update_time' => SqlHelper::addCompareOperator('>=', $update_time),
- );
- return $this->findIdsBy($condition, $limit, $order);
- }
- }
|