| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 | <?phpnamespace 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);    }}
 |