dao_cache;如不存在,则使用 memcached->default;如仍不存在,则不启用缓存. * @var string */ private $memcacheClusterFlag; /** * * 插入行为 * @var string */ const PARAM_CREATE_ACTION_INSERT = 'INSERT INTO'; /** * * 插入,但重复时忽略 * @var string */ const PARAM_CREATE_ACTION_INSERT_IGNORE = 'INSERT IGNORE'; /** * * 插入,但重复时完全replace * @var string */ const PARAM_CREATE_ACTION_REPLACE = 'REPLACE INTO'; /** * * 插入,但重复时自动转为update * @var string */ const PARAM_CREATE_ACTION_ONDUPLICATE = 'ON DUPLICATE KEY UPDATE'; /** * 数据库实例,可被动态切换到主库或从库 * * @var KIF\Db\MySQLi */ protected $db; /** * * mysql集群标志 * @var string */ private $cluster_flag; /** * * @param string $cluster_flag mysql集群标志 * */ public function __construct($cluster_flag = 'default') { $appConfig = Config::getInstance()->current(); $dbConfig = $appConfig['db']; if (!$dbConfig || !isset($dbConfig[$cluster_flag]) || !is_string($dbConfig[$cluster_flag])) { throw new ParamsException("load config error:{$dbConfig}"); } $this->cluster_flag = $cluster_flag; if (isset($appConfig['memcached']['dao_cache'])) { $this->memcacheClusterFlag = 'dao_cache'; } elseif (isset($appConfig['memcached']['default'])) { $this->memcacheClusterFlag = 'default'; } else {# 没有缓存配置,不启用缓存 $this->useCache(false); } $dsn = $dbConfig[$cluster_flag]; $this->db = new MySQLi($dsn); } /** * * 获取设置的主键名 * @return string */ public function getPrimaryKey() { return $this->primaryKey; } /** * 设置表名 * 动态设置表名是为了拆表 * @param $tableName */ public function setTableName($tableName) { $this->tableName = $tableName; } /** * * 控制是否使用缓存。使用缓存,会将数据表的行记录缓存起来,极大的提高效率和高可用;同时也会在记录删除、更新时,清空对应的缓存。 * @param boolean $useCache */ public function useCache($useCache = true) { $this->useCache = (bool) $useCache; } /** * 解析条件 * @param array|string $condition * @throws Exception parameter errors * @return string */ public function parseCondition($condition = null) { if(empty($condition)) return ''; // 如果条件语句是字符串 直接拼接 if(is_string($condition)){ return " WHERE ". $condition ." "; } // 如果不是字符串 强制传递数组 如果不是数组抛异常 if(!is_array($condition)){ throw new Exception('parameter errors'); } $condition = $this->quote($condition); $where = array(); foreach($condition as $tmpK => $tmpV) { # 避免条件字段重复,支持二维数组。比如$conditon = array( # array( # 'publish_time' => SqlHelper::addCompareOperator(">=", $s_ts), # ), # array( # 'publish_time' => SqlHelper::addCompareOperator("<", $e_ts), # ), # ); # 以前只能这样变相支持:$condition = array( # 'publish_time' => SqlHelper::addCompareOperator(">=", $s_ts), # '`publish_time`' => SqlHelper::addCompareOperator("<", $e_ts), # ); if (Verify::naturalNumber($tmpK)) { list($key, $val) = each($tmpV); } else { $key = $tmpK; $val = $tmpV; } if (strpos($key, '`') === false // 没用使用 ` 字符,比如 `status` && strpos($key, '(') === false) // 也不含有括号(即不是函数),比如 last_insert_id(status) { $key = "`{$key}`"; } if(is_scalar($val)) { $where[] = "{$key} " . SqlHelper::explodeCompareOperator($val); } elseif(is_array($val)) { $where[] = "{$key} IN (".join(',', $val).")"; } else { throw new Exception('parameter errors'); } } return " WHERE ".join(' && ', $where)." "; } /** * 根据条件查找主键id列表集 * 做这个方法,是出于架构上的考虑。只取出主键id集,然后通过主键id集才去取数据(内存或数据库) * @param array|String $condition 条件 * @param string | int $limit 指定分页 * @param string $order 指定排序 * @return array 如:array(123, 124)。无值时返回空数组 */ public function findIdsBy($condition = null, $limit = null, $order = null) { $result = $this->findBy($condition, $this->primaryKey, $limit, $this->primaryKey, $order); if (! $result) { return array(); } return array_keys($result); } /** * 查找的底层方法。对查找只提供有限支持,太复杂的查询请手动sql * final 让该方法禁止被继承 * 1、根据条件查找,多条件只支持 与(&&),不支持或之类的 * 2、支持limit * 3、group by * @param array|string $condition * @param string $returnAssociateKey 如果指定该指,返回的值不再以0,1,2为key,而是以其对应的值为key * @param string | int $limit * @param string $selectCols 要获取的列。语法:id, uid ,默认为 * * @param string $order 指定排序 * @return array */ final public function findBy($condition = null, $returnAssociateKey = null, $limit = null, $selectCols = '*', $order = null) { $where = $this->parseCondition($condition); if (!isset($limit) || !preg_match('#^(?:\d+\s*,\s*)?\d+$#', $limit)) { $strLimit = ' '; } else { $strLimit = " LIMIT $limit "; } $strOrder = ''; if(!empty($order)) { $strOrder = " ORDER BY {$order} "; } if(!isset($selectCols)) { $selectCols = '*'; } $sql = "SELECT {$selectCols} FROM {$this->tableName} {$where} {$strOrder} {$strLimit} ; "; return $this->db->fetchAll($sql, $returnAssociateKey); } public function fetchOne($condition = null, $selectCols = '*', $order = null) { $result = self::findBy($condition, null, 1, $selectCols, $order); if (!$result) { return false; } return array_pop($result); } /** * 获取所有 * @param string $order 排序方式。默认以主键倒序;有效的格式:"create_time desc" * @param string $limit 该参数用于支持分页,默认为不使用分页。格式 "offset, length" * @return array */ public function getsAll($order = null, $limit = null) { $ids = self::getsAllIds($order, $limit); return $this->gets($ids); } /** * 获取所有id列表集 * @param string $order 排序方式。默认以主键倒序;有效的格式:"create_time desc" * @param string $limit 该参数用于支持分页,默认为不使用分页。格式 "offset, length" * @return array */ public function getsAllIds($order = null, $limit = null) { if (is_null($order)) { $order = "{$this->primaryKey} desc"; } if (!is_string($order)) { throw new Exception('$order 必须是字符串或null'); } $condition = null; $ids = self::findIdsBy($condition, $limit, $order); return $ids; } /** * 获取指定$id * @param int | string $id * @return false | array */ public function get($id) { if (Verify::int($id)) { if ($id < 1) { return false; } } elseif (is_string($id)) { if (strlen($id) == 0) { return false; } } else { return false; } $result = self::gets(array($id));// 调用该类自身的gets方法,而不是继承者的gets方法! if (!$result) { return false; } return array_pop($result); } /** * 批量获取信息 * @param array $ids id组成的数组 * @return array 无结果时返回空数组 */ public function gets(array $ids) { $return = array(); if (empty($ids)) { return $return; } $ids = array_unique($ids); # 从缓存取 $cache_return = self::getsFromCache($ids); $non_cache_ids = array_diff($ids, array_keys($cache_return)); if (!$non_cache_ids) { return $cache_return; } # 从DB取 $db_return = self::getsFromDB($non_cache_ids); if ($db_return) { self::setToCache($db_return); } foreach ($ids as $id) { if (isset($cache_return[$id])) { $return[$id] = $cache_return[$id]; continue; } if (isset($db_return[$id])) { $return[$id] = $db_return[$id]; continue; } } return $return; } /** * * 从后端数据库取结果 * @param array $ids * @return array 格式:array( * (int) id => (mixed) value, * ... , * ) * */ private function getsFromDB(array $ids) { $return = array(); $condition = array( $this->primaryKey => $ids, ); $result = $this->findBy($condition, $this->primaryKey); if (!$result) { return $return; } foreach ($ids as $id) { $id = (string) $id;// php的数组下标是int整时,如果超出了操作系统平台的最大有符号正整数后,会取不到值。转成字符串型,解决这个bug if (array_key_exists($id, $result)) { $return[$id] = $result[$id]; } } return $return; } /** * * 获取表缓存的标志。后续这个表里每行的缓存,都会关联上这个表缓存标志。 * 所以只要清空这个标志,即可使整个表记录的缓存失效。 * @Exception DaoException::CACHE_SET_TABLE_FLAG_ERROR * @Exception DaoException::CACHE_SERVICE_UNAVAILABLE * @return String */ public function getTableCacheFlag() { if ($this->tableCacheFlag) { return $this->tableCacheFlag; } $dbconfig = Config::getInstance()->get('db'); $cacheKey_elements = array( serialize($dbconfig[$this->cluster_flag]), $this->tableName, 'table_cache_flag', ); $key = Math::md5_16(join($cacheKey_elements, '::')); $objMemcached = new Memcached($this->memcacheClusterFlag); $tmpGetResult = $objMemcached->get($key); if ($tmpGetResult) { $this->tableCacheFlag = $tmpGetResult; return $this->tableCacheFlag; } if (is_null($tmpGetResult)) {// 缓存不存在,建立 $tmpVal = microtime(true); if ($objMemcached->set($key, $tmpVal, 0)) {// 永不过期 $this->tableCacheFlag = $tmpVal; return $this->tableCacheFlag; } # TODO 设置缓存失败的处理 throw new DaoException(DaoException::CACHE_SET_TABLE_FLAG_ERROR); } if (!$tmpGetResult) { # TODO memcache服务不可用的处理 throw new DaoException(DaoException::CACHE_SERVICE_UNAVAILABLE); } } /** * * 获取指定ids集对应的缓存key集 * @param array $ids array(13343, 9939); * @Exception DaoException::CACHE_SET_TABLE_FLAG_ERROR * @Exception DaoException::CACHE_SERVICE_UNAVAILABLE * @return array 格式:array( * (int) id => (string) cacheKey, * ... , * * ) */ public function getsCacheKeys(array $ids) { $tableCacheFlag = $this->getTableCacheFlag(); $cacheKeys = array(); $dbconfig = Config::getInstance()->get('db'); foreach ($ids as $id) { $cacheKey_elements = array( $tableCacheFlag, serialize($dbconfig[$this->cluster_flag]), $this->tableName, $id, 'row_cache',// 这是针对数据库记录行的缓存 ); $cacheKeys[$id] = Math::md5_16(join($cacheKey_elements, '::')); } return $cacheKeys; } /** * * 从缓存里取数据表对应的行记录 * @param array $ids * @return array 只返回缓存里有的值,如果缓存里没有任何值,则返回空数组 */ private function getsFromCache(array $ids) { $return = array(); if (!$this->useCache) { return $return; } try { $cacheKeys = self::getsCacheKeys($ids); } catch (DaoException $e) { return $return; } $objMemcached = new Memcached($this->memcacheClusterFlag); $cacheVals = $objMemcached->gets($cacheKeys); $cache_return = array_combine(array_keys($cacheKeys), $cacheVals); foreach ($cacheVals as $tmpK => $tmpV) { if (is_null($tmpV)) { continue; } $return[array_search($tmpK, $cacheKeys)] = $tmpV; } return $return; } /** * * 从缓存里将$id对应的行记录删除 * @param int $id * @throws Exception * @return Boolean */ private function deleteFromCache($id) { if (!$this->useCache) { return false; } $objMemcached = new Memcached($this->memcacheClusterFlag); if (Verify::unsignedInt($id)) { try { $cacheKeys = self::getsCacheKeys(array($id)); } catch (DaoException $e) { return false; } return $objMemcached->delete(array_pop($cacheKeys)); } if (is_array($id) && !empty($id)) { try { $cacheKeys = self::getsCacheKeys($id); } catch (DaoException $e) { return false; } if (count($cacheKeys) == 1) { return $objMemcached->delete(array_pop($cacheKeys)); } return $objMemcached->deletes($cacheKeys); } return false; } /** * * 清除缓存的这张表的所有记录 * @return Boolean */ public function deleteAllCaches() { if (!$this->useCache) { return false; } try { $tableCacheFlag = $this->getTableCacheFlag(); } catch (DaoException $e) { return false; } $objMemcached = new Memcached($this->memcacheClusterFlag); return $objMemcached->delete($tableCacheFlag); } /** * * 将gets出来的结果集,存到缓存 * @param array $results * @return boolean */ private function setToCache(array $results) { if (!$this->useCache) { return false; } if (!$results) { return false; } try { $cacheKeys = self::getsCacheKeys(array_keys($results)); } catch (DaoException $e) { return false; } $objMemcached = new Memcached($this->memcacheClusterFlag); return $objMemcached->sets(array_combine($cacheKeys, $results), $this->cache_expiration); } /** * 创建一条记录 * @param array $tableInfo 待插入的数据 * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true * @return boolean | int */ private function _create(array $tableInfo, $isAutoIncrement = true, $action = self::PARAM_CREATE_ACTION_INSERT) { if(empty($tableInfo)) return false; switch($action) { case self::PARAM_CREATE_ACTION_INSERT : case self::PARAM_CREATE_ACTION_INSERT_IGNORE : case self::PARAM_CREATE_ACTION_REPLACE : break; default: throw new Exception('error insert action'); } $sql = "{$action} {$this->tableName} SET "; $sqlSets = ''; $tableInfo = $this->quote($tableInfo); foreach($tableInfo as $key => $val) { if($sqlSets != '') $sqlSets .= ' ,'; $sqlSets .= " `{$key}` = {$val} "; } $sql .= $sqlSets; if($this->db->query($sql)) { if($isAutoIncrement) { $id = $this->db->insertId(); # 清空这条记录的缓存 self::deleteFromCache($id); return $id > 0 ? $id : true; } else { return true; } } return false; } /** * 创建一条记录,如果重复,则替换 * @param array $tableInfo 待插入的数据 * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true * @return boolean | int */ public function replace(array $tableInfo, $isAutoIncrement = true) { return $this->_create($tableInfo, $isAutoIncrement, self::PARAM_CREATE_ACTION_REPLACE); } /** * 创建一条记录 * @param array $tableInfo 待插入的数据 * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true * @return boolean | int */ public function create(array $tableInfo, $isAutoIncrement = true) { return $this->_create($tableInfo, $isAutoIncrement, self::PARAM_CREATE_ACTION_INSERT); } /** * 创建一条记录,如果重复,则忽略 * @param array $tableInfo 待插入的数据 * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true * @return boolean | int PS:$isAutoIncrement = true时:1、如果插入了,返回自动id值;2、如果已存在,返回true。 */ public function insertIgnore(array $tableInfo, $isAutoIncrement = true) { return $this->_create($tableInfo, $isAutoIncrement, self::PARAM_CREATE_ACTION_INSERT_IGNORE); } /** * * 插入一条记录,如果重复,自动转为更新语句 * @param array $tableInfo * @param array $onDuplicate 如果重复时,需要更新的信息。如果不指定,则使用$tableInfo的值,即认为要全部更新 * @return int | Boolean * int:只要存在,无论之前记录是否存在,都会返回记录的id; * true:执行成功,但获取记录id时失败; * false:执行失败 */ public function insertDuplicate(array $tableInfo, array $onDuplicate = array()) { if (!$tableInfo) { return false; } $tmpArrKeys = array(); foreach ($tableInfo as $tmpKey => $tmpV) { $tmpArrKeys[] = "`{$tmpKey}`"; } $sql = "INSERT INTO {$this->tableName} (" . join(', ', $tmpArrKeys). ") VALUES "; $tmpArrValues = array(); $new_tableInfo = $this->quote($tableInfo); foreach ($new_tableInfo as $tmpKey => $tmpV) { $tmpArrValues[] = $tmpV; } $sql .= " ( " . join(', ', $tmpArrValues) . " ) "; $sql .= " ON DUPLICATE KEY UPDATE "; $tmpArrDps = array(); if (empty($onDuplicate)) { $onDuplicate = $tableInfo; } $new_onDuplicate = $this->quote($onDuplicate); foreach ($new_onDuplicate as $tmpKey => $tmpV) { $tmpArrDps[] = " `{$tmpKey}` = {$tmpV} "; } $sql .= join(', ', $tmpArrDps); if (!$this->db->query($sql)) { return false; } $id = $this->db->insertId(); # 清空这条记录的缓存 self::deleteFromCache($id); return $id > 0 ? $id : true; } /** * 根据条件更新指定数据 * @param array $tableInfo 待更新的数据(与数据库字段对应的数据) * @param array $condition 条件(与数据库字段对应的数据) * @return boolean */ public function update(array $tableInfo, array $condition) { if(empty($tableInfo)) return false; $sql = "UPDATE {$this->tableName} SET "; $sqlSets = ''; foreach($tableInfo as $key => $val) { if($sqlSets != '') $sqlSets .= ' ,'; $sqlSets .= " `{$key}` = {$this->quote($val)} "; } $sql .= $sqlSets; $where = $this->parseCondition($condition); $sql .= " {$where} ; "; $tmpQueryResult = $this->db->query($sql); if ($tmpQueryResult) { if (isset($condition[$this->primaryKey])) { # 清空这条记录的缓存,条件里有可能是一批ids self::deleteFromCache($condition[$this->primaryKey]); } else { $this->deleteAllCaches(); } } return $tmpQueryResult; } /** * 根据条件删除数据 * @param Array|String $condition 条件 * @return boolean */ public function delete($condition) { $where = $this->parseCondition($condition); $sql = "DELETE FROM {$this->tableName} {$where} ; "; $tmpQueryResult = $this->db->query($sql); if ($tmpQueryResult) { if (isset($condition[$this->primaryKey])) { # 清空这条记录的缓存,条件里有可能是一批ids self::deleteFromCache($condition[$this->primaryKey]); } else { $this->deleteAllCaches(); } } return $tmpQueryResult; } /** * 转义数据 * @param mixed $data */ public function quote($data) { return SqlHelper::escape($data, true); } /** * 判断给定的值是否是有效的自增主键值 * @param mixid $pk * @return boolean */ static protected function isValidPK($pk) { return SqlHelper::isValidPK($pk); } /** * 判断给定数组的某个key的值,是否是有效的自增主键值 * @param array $arr * @param mixid $key * @return boolean */ static protected function isValidPKWithArray(array $arr, $key) { return SqlHelper::isValidPKWithArray($arr, $key); } /** * 获取指定条件的记录数 * @return int */ public function totals($condtion = null){ $result = $this->findBy($condtion, null, null, "count(*)"); if (!$result) { return 0; } return (int) array_pop(array_pop($result)); } }