diff --git a/Library/Think/Db/Driver.php b/Library/Think/Db/Driver.php index 4252e0d1..9466fa7f 100644 --- a/Library/Think/Db/Driver.php +++ b/Library/Think/Db/Driver.php @@ -1,982 +1,984 @@ - -// +---------------------------------------------------------------------- - -namespace Think\Db; -use Think\Config; -use Think\Debug; -use Think\Log; -use PDO; - -abstract class Driver { - // PDO操作实例 - protected $PDOStatement = null; - // 当前操作所属的模型名 - protected $model = '_think_'; - // 当前SQL指令 - protected $queryStr = ''; - protected $modelSql = []; - // 最后插入ID - protected $lastInsID = null; - // 返回或者影响记录数 - protected $numRows = 0; - // 事务指令数 - protected $transTimes = 0; - // 错误信息 - protected $error = ''; - // 数据库连接ID 支持多个连接 - protected $linkID = []; - // 当前连接ID - protected $_linkID = null; - // 数据库连接参数配置 - protected $config = [ - 'dbms' => '', // 数据库类型 - 'connection' => [ - 'hostname' => '127.0.0.1', // 服务器地址 - 'database' => '', // 数据库名 - 'username' => 'root', // 用户名 - 'password' => '', // 密码 - 'hostport' => '', // 端口 - 'socket' => '', - 'dsn' => '', // - ], - 'params' => [], // 数据库连接参数 - 'charset' => 'utf8', // 数据库编码默认采用utf8 - 'prefix' => '', // 数据库表前缀 - 'debug' => false, // 数据库调试模式 - 'deploy' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) - 'rw_separate' => false, // 数据库读写是否分离 主从式有效 - 'master_num' => 1, // 读写分离后 主服务器数量 - 'slave_no' => '', // 指定从服务器序号 - ]; - // 数据库表达式 - protected $comparison = ['eq'=>'=','neq'=>'<>','gt'=>'>','egt'=>'>=','lt'=>'<','elt'=>'<=','notlike'=>'NOT LIKE','like'=>'LIKE','in'=>'IN','notin'=>'NOT IN']; - // 查询表达式 - protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%COMMENT%'; - // 查询次数 - protected $queryTimes = 0; - // 执行次数 - protected $executeTimes = 0; - // PDO连接参数 - protected $options = [ - PDO::ATTR_CASE => PDO::CASE_LOWER, - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, - PDO::ATTR_STRINGIFY_FETCHES => false, - ]; - - /** - * 架构函数 读取数据库配置信息 - * @access public - * @param array $config 数据库配置数组 - */ - public function __construct($config=''){ - if(!empty($config)) { - $this->config = array_merge($this->config,$config); - $this->config['params'] = $this->options+$this->config['params']; - } - } - - /** - * 连接数据库方法 - * @access public - */ - public function connect($config='',$linkNum=0) { - if ( !isset($this->linkID[$linkNum]) ) { - if(empty($config)) $config = $this->config['connection']; - try{ - if(empty($config['dsn'])) { - $config['dsn'] = $this->config['dbms'].':dbname='.$config['database'].';host='.$config['hostname']; - if(!empty($config['hostport'])) { - $config['dsn'] .= ';port='.$config['hostport']; - }elseif(!empty($config['socket'])){ - $config['dsn'] .= ';unix_socket='.$config['socket']; - } - } - $this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$this->config['params']); - }catch (\PDOException $e) { - E($e->getMessage()); - } - if(!empty($this->config['charset'])) { - $this->linkID[$linkNum]->exec('SET NAMES '.$this->config['charset']); - } - } - return $this->linkID[$linkNum]; - } - - /** - * 释放查询结果 - * @access public - */ - public function free() { - $this->PDOStatement = null; - } - - /** - * 执行查询 返回数据集 - * @access public - * @param string $str sql指令 - * @return mixed - */ - public function query($str,$bind=[]) { - $this->initConnect(false); - if ( !$this->_linkID ) return false; - $this->queryStr = $str; - //释放前次的查询结果 - if ( !empty($this->PDOStatement) ) $this->free(); - $this->queryTimes++; - // 调试开始 - $this->debug(true); - $this->PDOStatement = $this->_linkID->prepare($str); - if(false === $this->PDOStatement) - E($this->error()); - $result = $this->PDOStatement->execute($bind); - // 调试结束 - $this->debug(false); - if ( false === $result ) { - $this->error(); - return false; - } else { - return $this->getResult(); - } - } - - /** - * 执行语句 - * @access public - * @param string $str sql指令 - * @return integer - */ - public function execute($str,$bind=[]) { - $this->initConnect(true); - if ( !$this->_linkID ) return false; - $this->queryStr = $str; - //释放前次的查询结果 - if ( !empty($this->PDOStatement) ) $this->free(); - $this->executeTimes++; - // 记录开始执行时间 - $this->debug(true); - $this->PDOStatement = $this->_linkID->prepare($str); - if(false === $this->PDOStatement) { - E($this->error()); - } - $result = $this->PDOStatement->execute($bind); - $this->debug(false); - if ( false === $result) { - $this->error(); - return false; - } else { - $this->numRows = $this->PDOStatement->rowCount(); - if(preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) { - $this->lastInsID = $this->getLastInsertId(); - } - return $this->numRows; - } - } - - /** - * 获取最后插入id - * @access public - * @return integer - */ - public function getLastInsertId() { - return $this->_linkID->lastInsertId(); - } - - /** - * 启动事务 - * @access public - * @return void - */ - public function startTrans() { - $this->initConnect(true); - if ( !$this->_linkID ) return false; - //数据rollback 支持 - if ($this->transTimes == 0) { - $this->_linkID->beginTransaction(); - } - $this->transTimes++; - return ; - } - - /** - * 用于非自动提交状态下面的查询提交 - * @access public - * @return boolen - */ - public function commit() { - if ($this->transTimes > 0) { - $result = $this->_linkID->commit(); - $this->transTimes = 0; - if(!$result){ - $this->error(); - return false; - } - } - return true; - } - - /** - * 事务回滚 - * @access public - * @return boolen - */ - public function rollback() { - if ($this->transTimes > 0) { - $result = $this->_linkID->rollback(); - $this->transTimes = 0; - if(!$result){ - $this->error(); - return false; - } - } - return true; - } - - /** - * 获得所有的查询数据 - * @access private - * @return array - */ - private function getResult() { - //返回数据集 - $result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC); - $this->numRows = count( $result ); - return $result; - } - - /** - * 获得查询次数 - * @access public - * @param boolean $execute 是否包含所有查询 - * @return integer - */ - public function getQueryTimes($execute=false){ - return $execute?$this->queryTimes+$this->executeTimes:$this->queryTimes; - } - - /** - * 获得执行次数 - * @access public - * @return integer - */ - public function getExecuteTimes(){ - return $this->executeTimes; - } - - /** - * 关闭数据库 - * @access public - */ - public function close() { - $this->_linkID = null; - } - - /** - * 数据库错误信息 - * 并显示当前的SQL语句 - * @access public - * @return string - */ - public function error() { - if($this->PDOStatement) { - $error = $this->PDOStatement->errorInfo(); - $this->error = $error[1].':'.$error[2]; - }else{ - $this->error = ''; - } - if('' != $this->queryStr){ - $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; - } - Log::record($this->error,'ERR'); - return $this->error; - } - - /** - * 设置锁机制 - * @access protected - * @return string - */ - protected function parseLock($lock=false) { - return $lock? ' FOR UPDATE ' : ''; - } - - /** - * set分析 - * @access protected - * @param array $data - * @return string - */ - protected function parseSet($data) { - foreach ($data as $key=>$val){ - $value = $this->parseValue($val); - if(is_scalar($value)) // 过滤非标量数据 - $set[] = $this->parseKey($key).'='.$value; - } - return ' SET '.implode(',',$set); - } - - /** - * 字段名分析 - * @access protected - * @param string $key - * @return string - */ - protected function parseKey(&$key) { - return $key; - } - - /** - * value分析 - * @access protected - * @param mixed $value - * @return string - */ - protected function parseValue($value) { - if(is_string($value)) { - $value = '\''.$this->escapeString($value).'\''; - }elseif(isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == 'exp'){ - $value = $this->escapeString($value[1]); - }elseif(is_array($value)) { - $value = array_map([$this, 'parseValue'],$value); - }elseif(is_bool($value)){ - $value = $value ? '1' : '0'; - }elseif(is_null($value)){ - $value = 'null'; - } - return $value; - } - - /** - * field分析 - * @access protected - * @param mixed $fields - * @return string - */ - protected function parseField($fields) { - if(is_string($fields) && strpos($fields,',')) { - $fields = explode(',',$fields); - } - if(is_array($fields)) { - // 完善数组方式传字段名的支持 - // 支持 'field1'=>'field2' 这样的字段别名定义 - $array = []; - foreach ($fields as $key=>$field){ - if(!is_numeric($key)) - $array[] = $this->parseKey($key).' AS '.$this->parseKey($field); - else - $array[] = $this->parseKey($field); - } - $fieldsStr = implode(',', $array); - }elseif(is_string($fields) && !empty($fields)) { - $fieldsStr = $this->parseKey($fields); - }else{ - $fieldsStr = '*'; - } - //TODO 如果是查询全部字段,并且是join的方式,那么就把要查的表加个别名,以免字段被覆盖 - return $fieldsStr; - } - - /** - * table分析 - * @access protected - * @param mixed $table - * @return string - */ - protected function parseTable($tables) { - if(is_array($tables)) {// 支持别名定义 - $array = []; - foreach ($tables as $table=>$alias){ - if(!is_numeric($table)) - $array[] = $this->parseKey($table).' '.$this->parseKey($alias); - else - $array[] = $this->parseKey($table); - } - $tables = $array; - }elseif(is_string($tables)){ - $tables = explode(',',$tables); - array_walk($tables, [&$this, 'parseKey']); - } - return implode(',',$tables); - } - - /** - * where分析 - * @access protected - * @param mixed $where - * @return string - */ - protected function parseWhere($where) { - $whereStr = ''; - if(is_string($where)) { - // 直接使用字符串条件 - $whereStr = $where; - }else{ // 使用数组表达式 - $operate = isset($where['_logic'])?strtoupper($where['_logic']):''; - if(in_array($operate,['AND','OR','XOR'])){ - // 定义逻辑运算规则 例如 OR XOR AND NOT - $operate = ' '.$operate.' '; - unset($where['_logic']); - }else{ - // 默认进行 AND 运算 - $operate = ' AND '; - } - foreach ($where as $key=>$val){ - $whereStr .= '( '; - if(0===strpos($key,'_')) { - // 解析特殊条件表达式 - $whereStr .= $this->parseThinkWhere($key,$val); - }else{ - // 查询字段的安全过滤 - if(!preg_match('/^[A-Z_\|\&\-.a-z0-9\(\)\,]+$/',trim($key))){ - E(L('_EXPRESS_ERROR_').':'.$key); - } - // 多条件支持 - $multi = is_array($val) && isset($val['_multi']); - $key = trim($key); - if(strpos($key,'|')) { // 支持 name|title|nickname 方式定义查询字段 - $array = explode('|',$key); - $str = []; - foreach ($array as $m=>$k){ - $v = $multi?$val[$m]:$val; - $str[] = '('.$this->parseWhereItem($this->parseKey($k),$v).')'; - } - $whereStr .= implode(' OR ',$str); - }elseif(strpos($key,'&')){ - $array = explode('&',$key); - $str = []; - foreach ($array as $m=>$k){ - $v = $multi?$val[$m]:$val; - $str[] = '('.$this->parseWhereItem($this->parseKey($k),$v).')'; - } - $whereStr .= implode(' AND ',$str); - }else{ - $whereStr .= $this->parseWhereItem($this->parseKey($key),$val); - } - } - $whereStr .= ' )'.$operate; - } - $whereStr = substr($whereStr,0,-strlen($operate)); - } - return empty($whereStr)?'':' WHERE '.$whereStr; - } - - // where子单元分析 - protected function parseWhereItem($key,$val) { - $whereStr = ''; - if(is_array($val)) { - if(is_string($val[0])) { - if(preg_match('/^(EQ|NEQ|GT|EGT|LT|ELT)$/i',$val[0])) { // 比较运算 - $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]); - }elseif(preg_match('/^(NOTLIKE|LIKE)$/i',$val[0])){// 模糊查找 - if(is_array($val[1])) { - $likeLogic = isset($val[2])?strtoupper($val[2]):'OR'; - if(in_array($likeLogic,['AND','OR','XOR'])){ - $likeStr = $this->comparison[strtolower($val[0])]; - $like = []; - foreach ($val[1] as $item){ - $like[] = $key.' '.$likeStr.' '.$this->parseValue($item); - } - $whereStr .= '('.implode(' '.$likeLogic.' ',$like).')'; - } - }else{ - $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]); - } - }elseif('exp'==strtolower($val[0])){ // 使用表达式 - $whereStr .= ' ('.$key.' '.$val[1].') '; - }elseif(preg_match('/IN/i',$val[0])){ // IN 运算 - if(isset($val[2]) && 'exp'==$val[2]) { - $whereStr .= $key.' '.strtoupper($val[0]).' '.$val[1]; - }else{ - if(is_string($val[1])) { - $val[1] = explode(',',$val[1]); - } - $zone = implode(',',$this->parseValue($val[1])); - $whereStr .= $key.' '.strtoupper($val[0]).' ('.$zone.')'; - } - }elseif(preg_match('/BETWEEN/i',$val[0])){ // BETWEEN运算 - $data = is_string($val[1])? explode(',',$val[1]):$val[1]; - $whereStr .= ' ('.$key.' '.strtoupper($val[0]).' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]).' )'; - }else{ - E(L('_EXPRESS_ERROR_').':'.$val[0]); - } - }else { - $count = count($val); - $rule = isset($val[$count-1])?strtoupper($val[$count-1]):''; - if(in_array($rule,['AND','OR','XOR'])) { - $count = $count -1; - }else{ - $rule = 'AND'; - } - for($i=0;$i<$count;$i++) { - $data = is_array($val[$i])?$val[$i][1]:$val[$i]; - if('exp'==strtolower($val[$i][0])) { - $whereStr .= '('.$key.' '.$data.') '.$rule.' '; - }else{ - $op = is_array($val[$i])?$this->comparison[strtolower($val[$i][0])]:'='; - $whereStr .= '('.$key.' '.$op.' '.$this->parseValue($data).') '.$rule.' '; - } - } - $whereStr = substr($whereStr,0,-4); - } - }else { - //对字符串类型字段采用模糊匹配 - if($this->conf['db_like_fields'] && preg_match('/('.$this->conf['db_like_fields'].')/i',$key)) { - $val = '%'.$val.'%'; - $whereStr .= $key.' LIKE '.$this->parseValue($val); - }else { - $whereStr .= $key.' = '.$this->parseValue($val); - } - } - return $whereStr; - } - - /** - * 特殊条件分析 - * @access protected - * @param string $key - * @param mixed $val - * @return string - */ - protected function parseThinkWhere($key,$val) { - $whereStr = ''; - switch($key) { - case '_string': - // 字符串模式查询条件 - $whereStr = $val; - break; - case '_complex': - // 复合查询条件 - $whereStr = substr($this->parseWhere($val),6); - break; - case '_query': - // 字符串模式查询条件 - parse_str($val,$where); - if(isset($where['_logic'])) { - $op = ' '.strtoupper($where['_logic']).' '; - unset($where['_logic']); - }else{ - $op = ' AND '; - } - $array = []; - foreach ($where as $field=>$data) - $array[] = $this->parseKey($field).' = '.$this->parseValue($data); - $whereStr = implode($op,$array); - break; - } - return $whereStr; - } - - /** - * limit分析 - * @access protected - * @param mixed $lmit - * @return string - */ - protected function parseLimit($limit) { - return !empty($limit)? ' LIMIT '.$limit.' ':''; - } - - /** - * join分析 - * @access protected - * @param mixed $join - * @return string - */ - protected function parseJoin($join) { - $joinStr = ''; - if(!empty($join)) { - if(is_array($join)) { - foreach ($join as $key=>$_join){ - if(false !== stripos($_join,'JOIN')) - $joinStr .= ' '.$_join; - else - $joinStr .= ' LEFT JOIN ' .$_join; - } - }else{ - $joinStr .= ' LEFT JOIN ' .$join; - } - } - //将__TABLE_NAME__这样的字符串替换成正规的表名,并且带上前缀和后缀 - $joinStr = preg_replace("/__([A-Z_-]+)__/esU",$this->config['prefix'].".strtolower('$1')",$joinStr); - return $joinStr; - } - - /** - * order分析 - * @access protected - * @param mixed $order - * @return string - */ - protected function parseOrder($order) { - if(is_array($order)) { - $array = []; - foreach ($order as $key=>$val){ - if(is_numeric($key)) { - $array[] = $this->parseKey($val); - }else{ - $array[] = $this->parseKey($key).' '.$val; - } - } - $order = implode(',',$array); - } - return !empty($order)? ' ORDER BY '.$order:''; - } - - /** - * group分析 - * @access protected - * @param mixed $group - * @return string - */ - protected function parseGroup($group) { - return !empty($group)? ' GROUP BY '.$group:''; - } - - /** - * having分析 - * @access protected - * @param string $having - * @return string - */ - protected function parseHaving($having) { - return !empty($having)? ' HAVING '.$having:''; - } - - /** - * comment分析 - * @access protected - * @param string $comment - * @return string - */ - protected function parseComment($comment) { - return !empty($comment)? ' /* '.$comment.' */':''; - } - - /** - * distinct分析 - * @access protected - * @param mixed $distinct - * @return string - */ - protected function parseDistinct($distinct) { - return !empty($distinct)? ' DISTINCT ' :''; - } - - /** - * union分析 - * @access protected - * @param mixed $union - * @return string - */ - protected function parseUnion($union) { - if(empty($union)) return ''; - if(isset($union['_all'])) { - $str = 'UNION ALL '; - unset($union['_all']); - }else{ - $str = 'UNION '; - } - foreach ($union as $u){ - $sql[] = $str.(is_array($u)?$this->buildSelectSql($u):$u); - } - return implode(' ',$sql); - } - - /** - * 插入记录 - * @access public - * @param mixed $data 数据 - * @param array $options 参数表达式 - * @param boolean $replace 是否replace - * @return false | integer - */ - public function insert($data,$options=[],$replace=false) { - $values = $fields = []; - $this->model = $options['model']; - foreach ($data as $key=>$val){ - $value = $this->parseValue($val); - if(is_scalar($value)) { // 过滤非标量数据 - $values[] = $value; - $fields[] = $this->parseKey($key); - } - } - $sql = ($replace?'REPLACE':'INSERT').' INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES ('.implode(',', $values).')'; - $sql .= $this->parseLock(isset($options['lock'])?$options['lock']:false); - $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); - return $this->execute($sql,!empty($options['bind'])?$options['bind']:[]); - } - - /** - * 通过Select方式插入记录 - * @access public - * @param string $fields 要插入的数据表字段名 - * @param string $table 要插入的数据表名 - * @param array $option 查询数据参数 - * @return false | integer - */ - public function selectInsert($fields,$table,$options=[]) { - $this->model = $options['model']; - if(is_string($fields)) $fields = explode(',',$fields); - array_walk($fields, [$this, 'parseKey']); - $sql = 'INSERT INTO '.$this->parseTable($table).' ('.implode(',', $fields).') '; - $sql .= $this->buildSelectSql($options); - return $this->execute($sql); - } - - /** - * 更新记录 - * @access public - * @param mixed $data 数据 - * @param array $options 表达式 - * @return false | integer - */ - public function update($data,$options) { - $this->model = $options['model']; - $sql = 'UPDATE ' - .$this->parseTable($options['table']) - .$this->parseSet($data) - .$this->parseWhere(!empty($options['where'])?$options['where']:'') - .$this->parseOrder(!empty($options['order'])?$options['order']:'') - .$this->parseLimit(!empty($options['limit'])?$options['limit']:'') - .$this->parseLock(isset($options['lock'])?$options['lock']:false) - .$this->parseComment(!empty($options['comment'])?$options['comment']:''); - return $this->execute($sql,!empty($options['bind'])?$options['bind']:[]); - } - - /** - * 删除记录 - * @access public - * @param array $options 表达式 - * @return false | integer - */ - public function delete($options=[]) { - $this->model = $options['model']; - $sql = 'DELETE FROM ' - .$this->parseTable($options['table']) - .$this->parseWhere(!empty($options['where'])?$options['where']:'') - .$this->parseOrder(!empty($options['order'])?$options['order']:'') - .$this->parseLimit(!empty($options['limit'])?$options['limit']:'') - .$this->parseLock(isset($options['lock'])?$options['lock']:false) - .$this->parseComment(!empty($options['comment'])?$options['comment']:''); - return $this->execute($sql,!empty($options['bind'])?$options['bind']:[]); - } - - /** - * 查找记录 - * @access public - * @param array $options 表达式 - * @return mixed - */ - public function select($options=[]) { - $this->model = $options['model']; - $sql = $this->buildSelectSql($options); - $cache = isset($options['cache'])?$options['cache']:false; - if($cache) { // 查询缓存检测 - $key = is_string($cache['key'])?$cache['key']:md5($sql); - $value = S($key,'',$cache); - if(false !== $value) { - return $value; - } - } - $result = $this->query($sql,!empty($options['bind'])?$options['bind']:[]); - if($cache && false !== $result ) { // 查询缓存写入 - S($key,$result,$cache); - } - return $result; - } - - /** - * 生成查询SQL - * @access public - * @param array $options 表达式 - * @return string - */ - public function buildSelectSql($options=[]) { - if(isset($options['page'])) { - // 根据页数计算limit - if(strpos($options['page'],',')) { - list($page,$listRows) = explode(',',$options['page']); - }else{ - $page = $options['page']; - } - $page = $page?$page:1; - $listRows= isset($listRows)?$listRows:(is_numeric($options['limit'])?$options['limit']:20); - $offset = $listRows*((int)$page-1); - $options['limit'] = $offset.','.$listRows; - } - $sql = $this->parseSql($this->selectSql,$options); - $sql .= $this->parseLock(isset($options['lock'])?$options['lock']:false); - return $sql; - } - - /** - * 替换SQL语句中表达式 - * @access public - * @param array $options 表达式 - * @return string - */ - public function parseSql($sql,$options=[]){ - $sql = str_replace( - ['%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%COMMENT%'], - [ - $this->parseTable($options['table']), - $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false), - $this->parseField(!empty($options['field'])?$options['field']:'*'), - $this->parseJoin(!empty($options['join'])?$options['join']:''), - $this->parseWhere(!empty($options['where'])?$options['where']:''), - $this->parseGroup(!empty($options['group'])?$options['group']:''), - $this->parseHaving(!empty($options['having'])?$options['having']:''), - $this->parseOrder(!empty($options['order'])?$options['order']:''), - $this->parseLimit(!empty($options['limit'])?$options['limit']:''), - $this->parseUnion(!empty($options['union'])?$options['union']:''), - $this->parseComment(!empty($options['comment'])?$options['comment']:'') - ],$sql); - return $sql; - } - - /** - * 获取最近一次查询的sql语句 - * @param string $model 模型名 - * @access public - * @return string - */ - public function getLastSql($model='') { - return $model?$this->modelSql[$model]:$this->queryStr; - } - - /** - * 获取最近插入的ID - * @access public - * @return string - */ - public function getLastInsID() { - return $this->lastInsID; - } - - /** - * 获取最近的错误信息 - * @access public - * @return string - */ - public function getError() { - return $this->error; - } - - /** - * SQL指令安全过滤 - * @access public - * @param string $str SQL字符串 - * @return string - */ - public function escapeString($str) { - return addslashes($str); - } - - /** - * 设置当前操作模型 - * @access public - * @param string $model 模型名 - * @return void - */ - public function setModel($model){ - $this->model = $model; - } - - /** - * 数据库调试 记录当前SQL - * @access protected - * @param boolean $start 调试开始标记 true 开始 false 结束 - */ - protected function debug($start) { - if($this->config['debug']) {// 开启数据库调试模式 - if($start) { - Debug::remark('queryStartTime','time'); - }else{ - $this->modelSql[$this->model] = $this->queryStr; - $this->model = '_think_'; - // 记录操作结束时间 - Debug::remark('queryEndTime','time'); - Log::record($this->queryStr.' [ RunTime:'.Debug::getUseTime('queryStartTime','queryEndTime').'s ]','SQL'); - } - } - } - - /** - * 初始化数据库连接 - * @access protected - * @param boolean $master 主服务器 - * @return void - */ - protected function initConnect($master=true) { - if(!empty($this->config['deploy'])) - // 采用分布式数据库 - $this->_linkID = $this->multiConnect($master); - else - // 默认单数据库 - if ( !$this->_linkID ) $this->_linkID = $this->connect(); - } - - /** - * 连接分布式服务器 - * @access protected - * @param boolean $master 主服务器 - * @return void - */ - protected function multiConnect($master=false) { - static $_config = []; - if(empty($_config)) { - // 缓存分布式数据库配置解析 - foreach ($this->config['connection'] as $key=>$val){ - $_config[$key] = explode(',',$val); - } - } - // 数据库读写是否分离 - if($this->config['rw_separate']){ - // 主从式采用读写分离 - if($master) - // 主服务器写入 - $r = floor(mt_rand(0,$this->config['master_num']-1)); - else{ - if(is_numeric($this->config['slave_no'])) {// 指定服务器读 - $r = $this->config['slave_no']; - }else{ - // 读操作连接从服务器 - $r = floor(mt_rand($this->config['master_num'],count($_config['hostname'])-1)); // 每次随机连接的数据库 - } - } - }else{ - // 读写操作不区分服务器 - $r = floor(mt_rand(0,count($_config['hostname'])-1)); // 每次随机连接的数据库 - } - $db_config = [ - 'username' => isset($_config['username'][$r])?$_config['username'][$r]:$_config['username'][0], - 'password' => isset($_config['password'][$r])?$_config['password'][$r]:$_config['password'][0], - 'hostname' => isset($_config['hostname'][$r])?$_config['hostname'][$r]:$_config['hostname'][0], - 'hostport' => isset($_config['hostport'][$r])?$_config['hostport'][$r]:$_config['hostport'][0], - 'database' => isset($_config['database'][$r])?$_config['database'][$r]:$_config['database'][0], - 'dsn' => isset($_config['dsn'][$r])?$_config['dsn'][$r]:$_config['dsn'][0], - ]; - return $this->connect($db_config,$r); - } - - /** - * 析构方法 - * @access public - */ - public function __destruct() { - // 释放查询 - if ($this->PDOStatement){ - $this->free(); - } - // 关闭连接 - $this->close(); - } -} + +// +---------------------------------------------------------------------- + +namespace Think\Db; +use Think\Config; +use Think\Debug; +use Think\Log; +use PDO; + +abstract class Driver { + // PDO操作实例 + protected $PDOStatement = null; + // 当前操作所属的模型名 + protected $model = '_think_'; + // 当前SQL指令 + protected $queryStr = ''; + protected $modelSql = []; + // 最后插入ID + protected $lastInsID = null; + // 返回或者影响记录数 + protected $numRows = 0; + // 事务指令数 + protected $transTimes = 0; + // 错误信息 + protected $error = ''; + // 数据库连接ID 支持多个连接 + protected $linkID = []; + // 当前连接ID + protected $_linkID = null; + // 数据库连接参数配置 + protected $config = [ + 'dbms' => '', // 数据库类型 + 'connection' => [ + 'hostname' => '127.0.0.1', // 服务器地址 + 'database' => '', // 数据库名 + 'username' => 'root', // 用户名 + 'password' => '', // 密码 + 'hostport' => '', // 端口 + 'socket' => '', + 'dsn' => '', // + ], + 'params' => [], // 数据库连接参数 + 'charset' => 'utf8', // 数据库编码默认采用utf8 + 'prefix' => '', // 数据库表前缀 + 'debug' => false, // 数据库调试模式 + 'deploy' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'rw_separate' => false, // 数据库读写是否分离 主从式有效 + 'master_num' => 1, // 读写分离后 主服务器数量 + 'slave_no' => '', // 指定从服务器序号 + ]; + // 数据库表达式 + protected $comparison = ['eq'=>'=','neq'=>'<>','gt'=>'>','egt'=>'>=','lt'=>'<','elt'=>'<=','notlike'=>'NOT LIKE','like'=>'LIKE','in'=>'IN','notin'=>'NOT IN']; + // 查询表达式 + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%COMMENT%'; + // 查询次数 + protected $queryTimes = 0; + // 执行次数 + protected $executeTimes = 0; + // PDO连接参数 + protected $options = [ + PDO::ATTR_CASE => PDO::CASE_LOWER, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config=''){ + if(!empty($config)) { + $this->config = array_merge($this->config,$config); + $this->config['params'] = $this->options+$this->config['params']; + } + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config['connection']; + try{ + if(empty($config['dsn'])) { + $config['dsn'] = $this->config['dbms'].':dbname='.$config['database'].';host='.$config['hostname']; + if(!empty($config['hostport'])) { + $config['dsn'] .= ';port='.$config['hostport']; + }elseif(!empty($config['socket'])){ + $config['dsn'] .= ';unix_socket='.$config['socket']; + } + } + $this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$this->config['params']); + }catch (\PDOException $e) { + E($e->getMessage()); + } + if(!empty($this->config['charset'])) { + $this->linkID[$linkNum]->exec('SET NAMES '.$this->config['charset']); + } + } + return $this->linkID[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + $this->PDOStatement = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @return mixed + */ + public function query($str,$bind=[]) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + $this->queryTimes++; + // 调试开始 + $this->debug(true); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement) + E($this->error()); + $result = $this->PDOStatement->execute($bind); + // 调试结束 + $this->debug(false); + if ( false === $result ) { + $this->error(); + return false; + } else { + return $this->getResult(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @return integer + */ + public function execute($str,$bind=[]) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + $this->executeTimes++; + // 记录开始执行时间 + $this->debug(true); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement) { + E($this->error()); + } + $result = $this->PDOStatement->execute($bind); + $this->debug(false); + if ( false === $result) { + $this->error(); + return false; + } else { + $this->numRows = $this->PDOStatement->rowCount(); + if(preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) { + $this->lastInsID = $this->getLastInsertId(); + } + return $this->numRows; + } + } + + /** + * 获取最后插入id + * @access public + * @return integer + */ + public function getLastInsertId() { + return $this->_linkID->lastInsertId(); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + $this->_linkID->beginTransaction(); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolen + */ + public function commit() { + if ($this->transTimes > 0) { + $result = $this->_linkID->commit(); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolen + */ + public function rollback() { + if ($this->transTimes > 0) { + $result = $this->_linkID->rollback(); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getResult() { + //返回数据集 + $result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC); + $this->numRows = count( $result ); + return $result; + } + + /** + * 获得查询次数 + * @access public + * @param boolean $execute 是否包含所有查询 + * @return integer + */ + public function getQueryTimes($execute=false){ + return $execute?$this->queryTimes+$this->executeTimes:$this->queryTimes; + } + + /** + * 获得执行次数 + * @access public + * @return integer + */ + public function getExecuteTimes(){ + return $this->executeTimes; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error() { + if($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $this->error = $error[1].':'.$error[2]; + }else{ + $this->error = ''; + } + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + Log::record($this->error,'ERR'); + return $this->error; + } + + /** + * 设置锁机制 + * @access protected + * @return string + */ + protected function parseLock($lock=false) { + return $lock? ' FOR UPDATE ' : ''; + } + + /** + * set分析 + * @access protected + * @param array $data + * @return string + */ + protected function parseSet($data) { + foreach ($data as $key=>$val){ + $value = $this->parseValue($val); + if(is_scalar($value)) // 过滤非标量数据 + $set[] = $this->parseKey($key).'='.$value; + } + return ' SET '.implode(',',$set); + } + + /** + * 字段名分析 + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(&$key) { + return $key; + } + + /** + * value分析 + * @access protected + * @param mixed $value + * @return string + */ + protected function parseValue($value) { + if(is_string($value)) { + $value = strpos($value,':') === 0 ? $this->escapeString($value) : '\''.$this->escapeString($value).'\''; + }elseif(isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == 'exp'){ + $value = $this->escapeString($value[1]); + }elseif(is_array($value)) { + $value = array_map([$this, 'parseValue'],$value); + }elseif(is_bool($value)){ + $value = $value ? '1' : '0'; + }elseif(is_null($value)){ + $value = 'null'; + } + return $value; + } + + /** + * field分析 + * @access protected + * @param mixed $fields + * @return string + */ + protected function parseField($fields) { + if(is_string($fields) && strpos($fields,',')) { + $fields = explode(',',$fields); + } + if(is_array($fields)) { + // 完善数组方式传字段名的支持 + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + foreach ($fields as $key=>$field){ + if(!is_numeric($key)) + $array[] = $this->parseKey($key).' AS '.$this->parseKey($field); + else + $array[] = $this->parseKey($field); + } + $fieldsStr = implode(',', $array); + }elseif(is_string($fields) && !empty($fields)) { + $fieldsStr = $this->parseKey($fields); + }else{ + $fieldsStr = '*'; + } + //TODO 如果是查询全部字段,并且是join的方式,那么就把要查的表加个别名,以免字段被覆盖 + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param mixed $table + * @return string + */ + protected function parseTable($tables) { + if(is_array($tables)) {// 支持别名定义 + $array = []; + foreach ($tables as $table=>$alias){ + if(!is_numeric($table)) + $array[] = $this->parseKey($table).' '.$this->parseKey($alias); + else + $array[] = $this->parseKey($table); + } + $tables = $array; + }elseif(is_string($tables)){ + $tables = explode(',',$tables); + array_walk($tables, [&$this, 'parseKey']); + } + return implode(',',$tables); + } + + /** + * where分析 + * @access protected + * @param mixed $where + * @return string + */ + protected function parseWhere($where) { + $whereStr = ''; + if(is_string($where)) { + // 直接使用字符串条件 + $whereStr = $where; + }else{ // 使用数组表达式 + $operate = isset($where['_logic'])?strtoupper($where['_logic']):''; + if(in_array($operate,['AND','OR','XOR'])){ + // 定义逻辑运算规则 例如 OR XOR AND NOT + $operate = ' '.$operate.' '; + unset($where['_logic']); + }else{ + // 默认进行 AND 运算 + $operate = ' AND '; + } + foreach ($where as $key=>$val){ + $whereStr .= '( '; + if(0===strpos($key,'_')) { + // 解析特殊条件表达式 + $whereStr .= $this->parseThinkWhere($key,$val); + }else{ + // 查询字段的安全过滤 + if(!preg_match('/^[A-Z_\|\&\-.a-z0-9\(\)\,]+$/',trim($key))){ + E(L('_EXPRESS_ERROR_').':'.$key); + } + // 多条件支持 + $multi = is_array($val) && isset($val['_multi']); + $key = trim($key); + if(strpos($key,'|')) { // 支持 name|title|nickname 方式定义查询字段 + $array = explode('|',$key); + $str = []; + foreach ($array as $m=>$k){ + $v = $multi?$val[$m]:$val; + $str[] = '('.$this->parseWhereItem($this->parseKey($k),$v).')'; + } + $whereStr .= implode(' OR ',$str); + }elseif(strpos($key,'&')){ + $array = explode('&',$key); + $str = []; + foreach ($array as $m=>$k){ + $v = $multi?$val[$m]:$val; + $str[] = '('.$this->parseWhereItem($this->parseKey($k),$v).')'; + } + $whereStr .= implode(' AND ',$str); + }else{ + $whereStr .= $this->parseWhereItem($this->parseKey($key),$val); + } + } + $whereStr .= ' )'.$operate; + } + $whereStr = substr($whereStr,0,-strlen($operate)); + } + return empty($whereStr)?'':' WHERE '.$whereStr; + } + + // where子单元分析 + protected function parseWhereItem($key,$val) { + $whereStr = ''; + if(is_array($val)) { + if(is_string($val[0])) { + if(preg_match('/^(EQ|NEQ|GT|EGT|LT|ELT)$/i',$val[0])) { // 比较运算 + $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]); + }elseif(preg_match('/^(NOTLIKE|LIKE)$/i',$val[0])){// 模糊查找 + if(is_array($val[1])) { + $likeLogic = isset($val[2])?strtoupper($val[2]):'OR'; + if(in_array($likeLogic,['AND','OR','XOR'])){ + $likeStr = $this->comparison[strtolower($val[0])]; + $like = []; + foreach ($val[1] as $item){ + $like[] = $key.' '.$likeStr.' '.$this->parseValue($item); + } + $whereStr .= '('.implode(' '.$likeLogic.' ',$like).')'; + } + }else{ + $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]); + } + }elseif('bind'==strtolower($val[0])){ // 使用表达式 + $whereStr .= ' ('.$key.' = :'.$val[1].') '; + }elseif('exp'==strtolower($val[0])){ // 使用表达式 + $whereStr .= ' ('.$key.' '.$val[1].') '; + }elseif(preg_match('/IN/i',$val[0])){ // IN 运算 + if(isset($val[2]) && 'exp'==$val[2]) { + $whereStr .= $key.' '.strtoupper($val[0]).' '.$val[1]; + }else{ + if(is_string($val[1])) { + $val[1] = explode(',',$val[1]); + } + $zone = implode(',',$this->parseValue($val[1])); + $whereStr .= $key.' '.strtoupper($val[0]).' ('.$zone.')'; + } + }elseif(preg_match('/BETWEEN/i',$val[0])){ // BETWEEN运算 + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $whereStr .= ' ('.$key.' '.strtoupper($val[0]).' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]).' )'; + }else{ + E(L('_EXPRESS_ERROR_').':'.$val[0]); + } + }else { + $count = count($val); + $rule = isset($val[$count-1])?strtoupper($val[$count-1]):''; + if(in_array($rule,['AND','OR','XOR'])) { + $count = $count -1; + }else{ + $rule = 'AND'; + } + for($i=0;$i<$count;$i++) { + $data = is_array($val[$i])?$val[$i][1]:$val[$i]; + if('exp'==strtolower($val[$i][0])) { + $whereStr .= '('.$key.' '.$data.') '.$rule.' '; + }else{ + $op = is_array($val[$i])?$this->comparison[strtolower($val[$i][0])]:'='; + $whereStr .= '('.$key.' '.$op.' '.$this->parseValue($data).') '.$rule.' '; + } + } + $whereStr = substr($whereStr,0,-4); + } + }else { + //对字符串类型字段采用模糊匹配 + if($this->conf['db_like_fields'] && preg_match('/('.$this->conf['db_like_fields'].')/i',$key)) { + $val = '%'.$val.'%'; + $whereStr .= $key.' LIKE '.$this->parseValue($val); + }else { + $whereStr .= $key.' = '.$this->parseValue($val); + } + } + return $whereStr; + } + + /** + * 特殊条件分析 + * @access protected + * @param string $key + * @param mixed $val + * @return string + */ + protected function parseThinkWhere($key,$val) { + $whereStr = ''; + switch($key) { + case '_string': + // 字符串模式查询条件 + $whereStr = $val; + break; + case '_complex': + // 复合查询条件 + $whereStr = substr($this->parseWhere($val),6); + break; + case '_query': + // 字符串模式查询条件 + parse_str($val,$where); + if(isset($where['_logic'])) { + $op = ' '.strtoupper($where['_logic']).' '; + unset($where['_logic']); + }else{ + $op = ' AND '; + } + $array = []; + foreach ($where as $field=>$data) + $array[] = $this->parseKey($field).' = '.$this->parseValue($data); + $whereStr = implode($op,$array); + break; + } + return $whereStr; + } + + /** + * limit分析 + * @access protected + * @param mixed $lmit + * @return string + */ + protected function parseLimit($limit) { + return !empty($limit)? ' LIMIT '.$limit.' ':''; + } + + /** + * join分析 + * @access protected + * @param mixed $join + * @return string + */ + protected function parseJoin($join) { + $joinStr = ''; + if(!empty($join)) { + if(is_array($join)) { + foreach ($join as $key=>$_join){ + if(false !== stripos($_join,'JOIN')) + $joinStr .= ' '.$_join; + else + $joinStr .= ' LEFT JOIN ' .$_join; + } + }else{ + $joinStr .= ' LEFT JOIN ' .$join; + } + } + //将__TABLE_NAME__这样的字符串替换成正规的表名,并且带上前缀和后缀 + $joinStr = preg_replace("/__([A-Z_-]+)__/esU",$this->config['prefix'].".strtolower('$1')",$joinStr); + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @return string + */ + protected function parseOrder($order) { + if(is_array($order)) { + $array = []; + foreach ($order as $key=>$val){ + if(is_numeric($key)) { + $array[] = $this->parseKey($val); + }else{ + $array[] = $this->parseKey($key).' '.$val; + } + } + $order = implode(',',$array); + } + return !empty($order)? ' ORDER BY '.$order:''; + } + + /** + * group分析 + * @access protected + * @param mixed $group + * @return string + */ + protected function parseGroup($group) { + return !empty($group)? ' GROUP BY '.$group:''; + } + + /** + * having分析 + * @access protected + * @param string $having + * @return string + */ + protected function parseHaving($having) { + return !empty($having)? ' HAVING '.$having:''; + } + + /** + * comment分析 + * @access protected + * @param string $comment + * @return string + */ + protected function parseComment($comment) { + return !empty($comment)? ' /* '.$comment.' */':''; + } + + /** + * distinct分析 + * @access protected + * @param mixed $distinct + * @return string + */ + protected function parseDistinct($distinct) { + return !empty($distinct)? ' DISTINCT ' :''; + } + + /** + * union分析 + * @access protected + * @param mixed $union + * @return string + */ + protected function parseUnion($union) { + if(empty($union)) return ''; + if(isset($union['_all'])) { + $str = 'UNION ALL '; + unset($union['_all']); + }else{ + $str = 'UNION '; + } + foreach ($union as $u){ + $sql[] = $str.(is_array($u)?$this->buildSelectSql($u):$u); + } + return implode(' ',$sql); + } + + /** + * 插入记录 + * @access public + * @param mixed $data 数据 + * @param array $options 参数表达式 + * @param boolean $replace 是否replace + * @return false | integer + */ + public function insert($data,$options=[],$replace=false) { + $values = $fields = []; + $this->model = $options['model']; + foreach ($data as $key=>$val){ + $value = $this->parseValue($val); + if(is_scalar($value)) { // 过滤非标量数据 + $values[] = $value; + $fields[] = $this->parseKey($key); + } + } + $sql = ($replace?'REPLACE':'INSERT').' INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES ('.implode(',', $values).')'; + $sql .= $this->parseLock(isset($options['lock'])?$options['lock']:false); + $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql,!empty($options['bind'])?$options['bind']:[]); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @param array $option 查询数据参数 + * @return false | integer + */ + public function selectInsert($fields,$table,$options=[]) { + $this->model = $options['model']; + if(is_string($fields)) $fields = explode(',',$fields); + array_walk($fields, [$this, 'parseKey']); + $sql = 'INSERT INTO '.$this->parseTable($table).' ('.implode(',', $fields).') '; + $sql .= $this->buildSelectSql($options); + return $this->execute($sql); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @return false | integer + */ + public function update($data,$options) { + $this->model = $options['model']; + $sql = 'UPDATE ' + .$this->parseTable($options['table']) + .$this->parseSet($data) + .$this->parseWhere(!empty($options['where'])?$options['where']:'') + .$this->parseOrder(!empty($options['order'])?$options['order']:'') + .$this->parseLimit(!empty($options['limit'])?$options['limit']:'') + .$this->parseLock(isset($options['lock'])?$options['lock']:false) + .$this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql,!empty($options['bind'])?$options['bind']:[]); + } + + /** + * 删除记录 + * @access public + * @param array $options 表达式 + * @return false | integer + */ + public function delete($options=[]) { + $this->model = $options['model']; + $sql = 'DELETE FROM ' + .$this->parseTable($options['table']) + .$this->parseWhere(!empty($options['where'])?$options['where']:'') + .$this->parseOrder(!empty($options['order'])?$options['order']:'') + .$this->parseLimit(!empty($options['limit'])?$options['limit']:'') + .$this->parseLock(isset($options['lock'])?$options['lock']:false) + .$this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql,!empty($options['bind'])?$options['bind']:[]); + } + + /** + * 查找记录 + * @access public + * @param array $options 表达式 + * @return mixed + */ + public function select($options=[]) { + $this->model = $options['model']; + $sql = $this->buildSelectSql($options); + $cache = isset($options['cache'])?$options['cache']:false; + if($cache) { // 查询缓存检测 + $key = is_string($cache['key'])?$cache['key']:md5($sql); + $value = S($key,'',$cache); + if(false !== $value) { + return $value; + } + } + $result = $this->query($sql,!empty($options['bind'])?$options['bind']:[]); + if($cache && false !== $result ) { // 查询缓存写入 + S($key,$result,$cache); + } + return $result; + } + + /** + * 生成查询SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function buildSelectSql($options=[]) { + if(isset($options['page'])) { + // 根据页数计算limit + if(strpos($options['page'],',')) { + list($page,$listRows) = explode(',',$options['page']); + }else{ + $page = $options['page']; + } + $page = $page?$page:1; + $listRows= isset($listRows)?$listRows:(is_numeric($options['limit'])?$options['limit']:20); + $offset = $listRows*((int)$page-1); + $options['limit'] = $offset.','.$listRows; + } + $sql = $this->parseSql($this->selectSql,$options); + $sql .= $this->parseLock(isset($options['lock'])?$options['lock']:false); + return $sql; + } + + /** + * 替换SQL语句中表达式 + * @access public + * @param array $options 表达式 + * @return string + */ + public function parseSql($sql,$options=[]){ + $sql = str_replace( + ['%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%COMMENT%'], + [ + $this->parseTable($options['table']), + $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false), + $this->parseField(!empty($options['field'])?$options['field']:'*'), + $this->parseJoin(!empty($options['join'])?$options['join']:''), + $this->parseWhere(!empty($options['where'])?$options['where']:''), + $this->parseGroup(!empty($options['group'])?$options['group']:''), + $this->parseHaving(!empty($options['having'])?$options['having']:''), + $this->parseOrder(!empty($options['order'])?$options['order']:''), + $this->parseLimit(!empty($options['limit'])?$options['limit']:''), + $this->parseUnion(!empty($options['union'])?$options['union']:''), + $this->parseComment(!empty($options['comment'])?$options['comment']:'') + ],$sql); + return $sql; + } + + /** + * 获取最近一次查询的sql语句 + * @param string $model 模型名 + * @access public + * @return string + */ + public function getLastSql($model='') { + return $model?$this->modelSql[$model]:$this->queryStr; + } + + /** + * 获取最近插入的ID + * @access public + * @return string + */ + public function getLastInsID() { + return $this->lastInsID; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError() { + return $this->error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL字符串 + * @return string + */ + public function escapeString($str) { + return addslashes($str); + } + + /** + * 设置当前操作模型 + * @access public + * @param string $model 模型名 + * @return void + */ + public function setModel($model){ + $this->model = $model; + } + + /** + * 数据库调试 记录当前SQL + * @access protected + * @param boolean $start 调试开始标记 true 开始 false 结束 + */ + protected function debug($start) { + if($this->config['debug']) {// 开启数据库调试模式 + if($start) { + Debug::remark('queryStartTime','time'); + }else{ + $this->modelSql[$this->model] = $this->queryStr; + //$this->model = '_think_'; + // 记录操作结束时间 + Debug::remark('queryEndTime','time'); + Log::record($this->queryStr.' [ RunTime:'.Debug::getUseTime('queryStartTime','queryEndTime').'s ]','SQL'); + } + } + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 主服务器 + * @return void + */ + protected function initConnect($master=true) { + if(!empty($this->config['deploy'])) + // 采用分布式数据库 + $this->_linkID = $this->multiConnect($master); + else + // 默认单数据库 + if ( !$this->_linkID ) $this->_linkID = $this->connect(); + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return void + */ + protected function multiConnect($master=false) { + static $_config = []; + if(empty($_config)) { + // 缓存分布式数据库配置解析 + foreach ($this->config['connection'] as $key=>$val){ + $_config[$key] = explode(',',$val); + } + } + // 数据库读写是否分离 + if($this->config['rw_separate']){ + // 主从式采用读写分离 + if($master) + // 主服务器写入 + $r = floor(mt_rand(0,$this->config['master_num']-1)); + else{ + if(is_numeric($this->config['slave_no'])) {// 指定服务器读 + $r = $this->config['slave_no']; + }else{ + // 读操作连接从服务器 + $r = floor(mt_rand($this->config['master_num'],count($_config['hostname'])-1)); // 每次随机连接的数据库 + } + } + }else{ + // 读写操作不区分服务器 + $r = floor(mt_rand(0,count($_config['hostname'])-1)); // 每次随机连接的数据库 + } + $db_config = [ + 'username' => isset($_config['username'][$r])?$_config['username'][$r]:$_config['username'][0], + 'password' => isset($_config['password'][$r])?$_config['password'][$r]:$_config['password'][0], + 'hostname' => isset($_config['hostname'][$r])?$_config['hostname'][$r]:$_config['hostname'][0], + 'hostport' => isset($_config['hostport'][$r])?$_config['hostport'][$r]:$_config['hostport'][0], + 'database' => isset($_config['database'][$r])?$_config['database'][$r]:$_config['database'][0], + 'dsn' => isset($_config['dsn'][$r])?$_config['dsn'][$r]:$_config['dsn'][0], + ]; + return $this->connect($db_config,$r); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() { + // 释放查询 + if ($this->PDOStatement){ + $this->free(); + } + // 关闭连接 + $this->close(); + } +} diff --git a/Library/Think/Model.php b/Library/Think/Model.php index b1e9bbfb..5a2136cc 100644 --- a/Library/Think/Model.php +++ b/Library/Think/Model.php @@ -1,943 +1,954 @@ - -// +---------------------------------------------------------------------- - -namespace Think; - -class Model { - // 操作状态 - const MODEL_INSERT = 1; // 插入模型数据 - const MODEL_UPDATE = 2; // 更新模型数据 - const MODEL_BOTH = 3; // 包含上面两种方式 - // 当前数据库操作对象 - protected $db = null; - // 主键名称 - protected $pk = 'id'; - // 数据表前缀 - protected $tablePrefix = ''; - // 模型名称 - protected $name = ''; - // 数据库名称 - protected $dbName = ''; - //数据库配置 - protected $connection = ''; - // 数据表名(不包含表前缀) - protected $tableName = ''; - // 实际数据表名(包含表前缀) - protected $trueTableName = ''; - // 最近错误信息 - protected $error = ''; - // 字段信息 - protected $fields = []; - // 数据信息 - protected $data = []; - // 查询表达式参数 - protected $options = []; - // 命名范围定义 - protected $_scope = []; - // 是否自动检测数据表字段信息 - protected $autoCheckFields = false; - - /** - * 架构函数 - * 取得DB类的实例对象 字段检查 - * @access public - * @param string $name 模型名称 - * @param array $config 模型配置 - */ - public function __construct($name='',$config=[]) { - // 模型初始化 - $this->_initialize(); - // 传入模型参数 - if(!empty($name)){ - $this->name = $name; - }elseif(empty($this->name)){ - $this->name = $this->getModelName(); - } - if(strpos($this->name,'.')) { // 支持 数据库名.模型名的 定义 - list($this->dbName,$this->name) = explode('.',$this->name); - } - - if(isset($config['table_prefix'])) { - $this->tablePrefix = $config['table_prefix']; - } - if(isset($config['connection'])) { - $this->connection = $config['connection']; - } - if(isset($config['table_name'])) { - $this->tableName = $config['table_name']; - } - if(isset($config['true_table_name'])) { - $this->trueTableName = $config['true_table_name']; - } - if(isset($config['db_name'])) { - $this->dbName = $config['db_name']; - } - - // 设置表前缀 - if(empty($this->tablePrefix)) { - $this->tablePrefix = is_null($this->tablePrefix)?'':C('database.prefix'); - } - - // 数据库初始化操作 - // 获取数据库操作对象 - // 当前模型有独立的数据库连接信息 - $this->db(0,$this->connection); - } - - /** - * 设置数据对象的值 - * @access public - * @param string $name 名称 - * @param mixed $value 值 - * @return void - */ - public function __set($name,$value) { - // 设置数据对象属性 - $this->data[$name] = $value; - } - - /** - * 获取数据对象的值 - * @access public - * @param string $name 名称 - * @return mixed - */ - public function __get($name) { - return isset($this->data[$name])?$this->data[$name]:null; - } - - /** - * 检测数据对象的值 - * @access public - * @param string $name 名称 - * @return boolean - */ - public function __isset($name) { - return isset($this->data[$name]); - } - - /** - * 销毁数据对象的值 - * @access public - * @param string $name 名称 - * @return void - */ - public function __unset($name) { - unset($this->data[$name]); - } - - // 回调方法 初始化模型 - protected function _initialize() {} - - /** - * 对保存到数据库的数据进行处理 - * @access protected - * @param mixed $data 要操作的数据 - * @return boolean - */ - protected function _facade($data) { - // 检查非数据字段 - if(!empty($this->fields)) { - foreach ($data as $key=>$val){ - if(!in_array($key,$this->fields,true)){ - unset($data[$key]); - }elseif(is_scalar($val)) { - // 字段类型检查 - $this->_parseType($data,$key); - } - } - } - // 安全过滤 - if(!empty($this->options['filter'])) { - $data = array_map($this->options['filter'],$data); - unset($this->options['filter']); - } - $this->_before_write($data); - return $data; - } - - // 写入数据前的回调方法 包括新增和更新 - protected function _before_write(&$data) {} - - /** - * 新增数据 - * @access public - * @param mixed $data 数据 - * @param boolean $replace 是否replace - * @return mixed - */ - public function add($data='',$replace=false) { - if(empty($data)) { - // 没有传递数据,获取当前数据对象的值 - if(!empty($this->data)) { - $data = $this->data; - // 重置数据 - $this->data = []; - }else{ - $this->error = L('_DATA_TYPE_INVALID_'); - return false; - } - } - // 分析表达式 - $options = $this->_parseOptions(); - // 数据处理 - $data = $this->_facade($data); - if(false === $this->_before_insert($data,$options)) { - return false; - } - // 写入数据到数据库 - $result = $this->db->insert($data,$options,$replace); - if(false !== $result ) { - $insertId = $this->getLastInsID(); - if($insertId) { - // 自增主键返回插入ID - $data[$this->getPk()] = $insertId; - $this->_after_insert($data,$options); - return $insertId; - } - $this->_after_insert($data,$options); - } - return $result; - } - // 插入数据前的回调方法 - protected function _before_insert(&$data,$options) {} - // 插入成功后的回调方法 - protected function _after_insert($data,$options) {} - - /** - * 保存数据 - * @access public - * @param mixed $data 数据 - * @return boolean - */ - public function save($data='') { - if(empty($data)) { - // 没有传递数据,获取当前数据对象的值 - if(!empty($this->data)) { - $data = $this->data; - // 重置数据 - $this->data = []; - }else{ - $this->error = L('_DATA_TYPE_INVALID_'); - return false; - } - } - // 数据处理 - $data = $this->_facade($data); - // 分析表达式 - $options = $this->_parseOptions(); - if(false === $this->_before_update($data,$options)) { - return false; - } - if(!isset($options['where']) ) { - // 如果存在主键数据 则自动作为更新条件 - if(isset($data[$this->getPk()])) { - $pk = $this->getPk(); - $where[$pk] = $data[$pk]; - $options['where'] = $where; - $pkValue = $data[$pk]; - unset($data[$pk]); - }else{ - // 如果没有任何更新条件则不执行 - $this->error = Lang::get('_OPERATION_WRONG_'); - return false; - } - } - $result = $this->db->update($data,$options); - if(false !== $result) { - if(isset($pkValue)) $data[$pk] = $pkValue; - $this->_after_update($data,$options); - } - return $result; - } - // 更新数据前的回调方法 - protected function _before_update(&$data,$options) {} - // 更新成功后的回调方法 - protected function _after_update($data,$options) {} - - /** - * 删除数据 - * @access public - * @param mixed $options 表达式 - * @return mixed - */ - public function delete($options=[]) { - if(empty($options) && empty($this->options['where'])) { - // 如果删除条件为空 则删除当前数据对象所对应的记录 - if(!empty($this->data) && isset($this->data[$this->getPk()])) - return $this->delete($this->data[$this->getPk()]); - else - return false; - } - if(is_numeric($options) || is_string($options)) { - // 根据主键删除记录 - $pk = $this->getPk(); - if(strpos($options,',')) { - $where[$pk] = ['IN', $options]; - }else{ - $where[$pk] = $options; - } - $pkValue = $where[$pk]; - $options = []; - $options['where'] = $where; - } - // 分析表达式 - $options = $this->_parseOptions($options); - $result= $this->db->delete($options); - if(false !== $result) { - $data = []; - if(isset($pkValue)) $data[$pk] = $pkValue; - $this->_after_delete($data,$options); - } - // 返回删除记录个数 - return $result; - } - // 删除成功后的回调方法 - protected function _after_delete($data,$options) {} - - /** - * 查询数据集 - * @access public - * @param mixed $options 表达式参数 - * @return mixed - */ - public function select($options=[]) { - if(is_string($options) || is_numeric($options)) { - // 根据主键查询 - $pk = $this->getPk(); - if(strpos($options,',')) { - $where[$pk] = ['IN',$options]; - }else{ - $where[$pk] = $options; - } - $options = []; - $options['where'] = $where; - }elseif(false === $options){ // 用于子查询 不查询只返回SQL - $options = []; - // 分析表达式 - $options = $this->_parseOptions($options); - return '( '.$this->db->buildSelectSql($options).' )'; - } - // 分析表达式 - $options = $this->_parseOptions($options); - $resultSet = $this->db->select($options); - if(false === $resultSet) { - return false; - } - if(empty($resultSet)) { // 查询结果为空 - return null; - } - $this->_after_select($resultSet,$options); - return $resultSet; - } - // 查询成功后的回调方法 - protected function _after_select(&$resultSet,$options) {} - - /** - * 生成查询SQL 可用于子查询 - * @access public - * @param array $options 表达式参数 - * @return string - */ - public function buildSql($options=[]) { - // 分析表达式 - $options = $this->_parseOptions($options); - return '( '.$this->db->buildSelectSql($options).' )'; - } - - /** - * 分析表达式 - * @access protected - * @param array $options 表达式参数 - * @return array - */ - protected function _parseOptions($options=[]) { - if(is_array($options)) - $options = array_merge($this->options,$options); - // 查询过后清空sql表达式组装 避免影响下次查询 - $this->options = []; - - if(!empty($options['alias'])) { - $options['table'] .= ' '.$options['alias']; - } - // 记录操作的模型名称 - $options['model'] = $this->name; - - if(isset($options['table'])) {// 动态指定表名 - $fields = $this->db->getFields($this->options['table']); - $fields = $fields?array_keys($fields):false; - }else{ - $options['table'] = $this->getTableName(); - $fields = $this->getDbFields(); - } - // 字段类型验证 - if(isset($options['where']) && is_array($options['where']) && !empty($fields)) { - // 对数组查询条件进行字段类型检查 - foreach ($options['where'] as $key=>$val){ - $key = trim($key); - if(in_array($key,$fields,true)){ - if(is_scalar($val)) { - $this->_parseType($options['where'],$key); - } - }elseif('_' != substr($key,0,1) && false === strpos($key,'.') && false === strpos($key,'(') && false === strpos($key,'|') && false === strpos($key,'&')){ - unset($options['where'][$key]); - } - } - } - - - // 表达式过滤 - $this->_options_filter($options); - return $options; - } - // 表达式过滤回调方法 - protected function _options_filter(&$options) {} - - /** - * 数据类型检测 - * @access protected - * @param mixed $data 数据 - * @param string $key 字段名 - * @return void - */ - protected function _parseType(&$data,$key) { - if(isset($this->fields['_type'][$key])) { - $fieldType = strtolower($this->fields['_type'][$key]); - if(false === strpos($fieldType,'bigint') && false !== strpos($fieldType,'int')) { - $data[$key] = intval($data[$key]); - }elseif(false !== strpos($fieldType,'float') || false !== strpos($fieldType,'double')){ - $data[$key] = floatval($data[$key]); - }elseif(false !== strpos($fieldType,'bool')){ - $data[$key] = (bool)$data[$key]; - } - } - } - - /** - * 查询数据 - * @access public - * @param mixed $options 表达式参数 - * @return mixed - */ - public function find($options=[]) { - if(is_numeric($options) || is_string($options)) { - $where[$this->getPk()] = $options; - $options = []; - $options['where'] = $where; - } - // 总是查找一条记录 - $options['limit'] = 1; - // 分析表达式 - $options = $this->_parseOptions($options); - $resultSet = $this->db->select($options); - if(false === $resultSet) { - return false; - } - if(empty($resultSet)) {// 查询结果为空 - return null; - } - $this->data = $resultSet[0]; - $this->_after_find($this->data,$options); - return $this->data; - } - // 查询成功的回调方法 - protected function _after_find(&$result,$options) {} - - /** - * 创建数据对象 但不保存到数据库 - * @access public - * @param mixed $data 创建数据 - * @param string $type 状态 - * @return mixed - */ - public function create($data='',$type='') { - // 如果没有传值默认取POST数据 - if(empty($data)) { - $data = $_POST; - }elseif(is_object($data)){ - $data = get_object_vars($data); - } - // 验证数据 - if(empty($data) || !is_array($data)) { - $this->error = L('_DATA_TYPE_INVALID_'); - return false; - } - - // 状态 - $type = $type?$type:(!empty($data[$this->getPk()])?self::MODEL_UPDATE:self::MODEL_INSERT); - - // 检测提交字段的合法性 - if(isset($this->options['field'])) { // $this->field('field1,field2...')->create() - $fields = $this->options['field']; - unset($this->options['field']); - }elseif($type == self::MODEL_INSERT && isset($this->insertFields)) { - $fields = $this->insertFields; - }elseif($type == self::MODEL_UPDATE && isset($this->updateFields)) { - $fields = $this->updateFields; - } - if(isset($fields)) { - if(is_string($fields)) { - $fields = explode(',',$fields); - } - foreach ($data as $key=>$val){ - if(!in_array($key,$fields)) { - unset($data[$key]); - } - } - } - // 过滤创建的数据 - $this->_create_filter($data); - // 赋值当前数据对象 - $this->data = $data; - // 返回创建的数据以供其他调用 - return $data; - } - // 数据对象创建后的回调方法 - protected function _create_filter(&$data){} - - /** - * 切换当前的数据库连接 - * @access public - * @param integer $linkNum 连接序号 - * @param mixed $config 数据库连接信息 - * @return Model - */ - public function db($linkNum='',$config=''){ - if(''===$linkNum && $this->db) { - return $this->db; - } - static $_linkNum = []; - static $_db = []; - if(!isset($_db[$linkNum]) || (isset($_db[$linkNum]) && $config && $_linkNum[$linkNum]!=$config) ) { - // 创建一个新的实例 - if(!empty($config) && is_string($config) && false === strpos($config,'/')) { // 支持读取配置参数 - $config = C($config); - } - $_db[$linkNum] = Db::instance($config); - }elseif(NULL === $config){ - $_db[$linkNum]->close(); // 关闭数据库连接 - unset($_db[$linkNum]); - return ; - } - - // 记录连接信息 - $_linkNum[$linkNum] = $config; - // 切换数据库连接 - $this->db = $_db[$linkNum]; - $this->_after_db(); - return $this; - } - // 数据库切换后回调方法 - protected function _after_db() {} - - /** - * 得到当前的数据对象名称 - * @access public - * @return string - */ - public function getModelName() { - if(empty($this->name)) - $this->name = substr(get_class($this),0,-5); - return $this->name; - } - - /** - * 得到完整的数据表名 - * @access public - * @return string - */ - public function getTableName() { - if(empty($this->trueTableName)) { - $tableName = !empty($this->tablePrefix) ? $this->tablePrefix : ''; - if(!empty($this->tableName)) { - $tableName .= $this->tableName; - }else{ - $tableName .= parse_name($this->name); - } - $this->trueTableName = strtolower($tableName); - } - return (!empty($this->dbName)?$this->dbName.'.':'').$this->trueTableName; - } - - /** - * 返回模型的错误信息 - * @access public - * @return string - */ - public function getError(){ - return $this->error; - } - - /** - * 返回数据库的错误信息 - * @access public - * @return string - */ - public function getDbError() { - return $this->db->getError(); - } - - /** - * 返回最后插入的ID - * @access public - * @return string - */ - public function getLastInsID() { - return $this->db->getLastInsID(); - } - - /** - * 返回最后执行的sql语句 - * @access public - * @return string - */ - public function getLastSql() { - return $this->db->getLastSql($this->name); - } - - /** - * 获取主键名称 - * @access public - * @return string - */ - public function getPk() { - return isset($this->fields['_pk'])?$this->fields['_pk']:$this->pk; - } - - /** - * 获取数据表字段信息 - * @access public - * @return array - */ - public function getDbFields(){ - if($this->fields) { - $fields = $this->fields; - unset($fields['_pk'],$fields['_type']); - return $fields; - }else{ - $fields = Cache::get(md5($this->getTableName())); - if(!$fields) { - $fields = $this->db->getFields($this->getTableName()); - $this->fields = array_keys($fields); - foreach ($fields as $key=>$val){ - // 记录字段类型 - $type[$key] = $val['type']; - if($val['primary']) { - $this->fields['_pk'] = $key; - } - } - // 记录字段类型信息 - $this->fields['_type'] = $type; - Cache::set(md5($this->trueTableName),$this->fields); - return array_keys($fields); - }else{ - $this->fields = $fields; - unset($fields['_pk'],$fields['_type']); - return array_keys($fields); - } - } - } - - /** - * 设置数据对象值 - * @access public - * @param mixed $data 数据 - * @return Model - */ - public function data($data=''){ - if('' === $data && !empty($this->data)) { - return $this->data; - } - if(is_object($data)){ - $data = get_object_vars($data); - }elseif(is_string($data)){ - parse_str($data,$data); - }elseif(!is_array($data)){ - E(L('_DATA_TYPE_INVALID_')); - } - $this->data = $data; - return $this; - } - - /** - * 查询SQL组装 join - * @access public - * @param mixed $join - * @return Model - */ - public function join($join) { - if(is_array($join)) { - $this->options['join'] = $join; - }elseif(!empty($join)) { - $this->options['join'][] = $join; - } - return $this; - } - - /** - * 查询SQL组装 union - * @access public - * @param mixed $union - * @param boolean $all - * @return Model - */ - public function union($union,$all=false) { - if(empty($union)) return $this; - if($all) { - $this->options['union']['_all'] = true; - } - if(is_object($union)) { - $union = get_object_vars($union); - } - // 转换union表达式 - if(is_string($union) ) { - $options = $union; - }elseif(is_array($union)){ - if(isset($union[0])) { - $this->options['union'] = array_merge($this->options['union'],$union); - return $this; - }else{ - $options = $union; - } - }else{ - E(L('_DATA_TYPE_INVALID_')); - } - $this->options['union'][] = $options; - return $this; - } - - /** - * 查询缓存 - * @access public - * @param mixed $key - * @param integer $expire - * @param string $type - * @return Model - */ - public function cache($key=true,$expire=null,$type=''){ - if(false !== $key) - $this->options['cache'] = ['key'=>$key,'expire'=>$expire,'type'=>$type]; - return $this; - } - - /** - * 指定查询字段 支持字段排除 - * @access public - * @param mixed $field - * @param boolean $except 是否排除 - * @return Model - */ - public function field($field,$except=false){ - if(true === $field) {// 获取全部字段 - $fields = $this->getDbFields(); - $field = $fields?$fields:'*'; - }elseif($except) {// 字段排除 - if(is_string($field)) { - $field = explode(',',$field); - } - $fields = $this->getDbFields(); - $field = $fields?array_diff($fields,$field):$field; - } - $this->options['field'] = $field; - return $this; - } - - /** - * 调用命名范围 - * @access public - * @param mixed $scope 命名范围名称 支持多个 和直接定义 - * @param array $args 参数 - * @return Model - */ - public function scope($scope='',$args=NULL){ - if('' === $scope) { - if(isset($this->_scope['default'])) { - // 默认的命名范围 - $options = $this->_scope['default']; - }else{ - return $this; - } - }elseif(is_string($scope)){ // 支持多个命名范围调用 用逗号分割 - $scopes = explode(',',$scope); - $options = []; - foreach ($scopes as $name){ - if(!isset($this->_scope[$name])) continue; - $options = array_merge($options,$this->_scope[$name]); - } - if(!empty($args) && is_array($args)) { - $options = array_merge($options,$args); - } - }elseif(is_array($scope)){ // 直接传入命名范围定义 - $options = $scope; - } - - if(is_array($options) && !empty($options)){ - $this->options = array_merge($this->options,array_change_key_case($options)); - } - return $this; - } - - /** - * 指定查询条件 支持安全过滤 - * @access public - * @param mixed $where 条件表达式 - * @param mixed $parse 预处理参数 - * @return Model - */ - public function where($where,$parse=null){ - if(!is_null($parse) && is_string($where)) { - if(!is_array($parse)) { - $parse = func_get_args(); - array_shift($parse); - } - $parse = array_map([$this->db,'escapeString'],$parse); - $where = vsprintf($where,$parse); - }elseif(is_object($where)){ - $where = get_object_vars($where); - } - if(is_string($where) && '' != $where){ - $map = []; - $map['_string'] = $where; - $where = $map; - } - if(isset($this->options['where'])){ - $this->options['where'] = array_merge($this->options['where'],$where); - }else{ - $this->options['where'] = $where; - } - - return $this; - } - - /** - * 指定查询数量 - * @access public - * @param mixed $offset 起始位置 - * @param mixed $length 查询数量 - * @return Model - */ - public function limit($offset,$length=null){ - $this->options['limit'] = is_null($length)?$offset:$offset.','.$length; - return $this; - } - - /** - * 指定分页 - * @access public - * @param mixed $page 页数 - * @param mixed $listRows 每页数量 - * @return Model - */ - public function page($page,$listRows=null){ - $this->options['page'] = is_null($listRows)?$page:$page.','.$listRows; - return $this; - } - - /** - * 指定数据表 - * @access public - * @param string $table 表名 - * @return Model - */ - public function table($table){ - $this->options['table'] = $table; - return $this; - } - - /** - * 指定排序 - * @access public - * @param string $order 排序 - * @return Model - */ - public function order($order){ - $this->options['order'] = $order; - return $this; - } - - /** - * 指定group查询 - * @access public - * @param string $group GROUP - * @return Model - */ - public function group($group){ - $this->options['group'] = $group; - return $this; - } - - /** - * 指定having查询 - * @access public - * @param string $having having - * @return Model - */ - public function having($table){ - $this->options['having'] = $having; - return $this; - } - - /** - * 指定查询lock - * @access public - * @param boolean $lock 是否lock - * @return Model - */ - public function lock($lock=false){ - $this->options['lock'] = $lock; - return $this; - } - - /** - * 指定distinct查询 - * @access public - * @param string $distinct 是否唯一 - * @return Model - */ - public function distinct($distinct){ - $this->options['distinct'] = $distinct; - return $this; - } - - /** - * 指定数据表别名 - * @access public - * @param string $alias 数据表别名 - * @return Model - */ - public function alias($alias){ - $this->options['alias'] = $alias; - return $this; - } - - /** - * 指定写入过滤方法 - * @access public - * @param string $filter 指定过滤方法 - * @return Model - */ - public function filter($filter){ - $this->options['filter'] = $filter; - return $this; - } - - /** - * 查询注释 - * @access public - * @param string $comment 注释 - * @return Model - */ - public function comment($comment){ - $this->options['comment'] = $comment; - return $this; - } - + +// +---------------------------------------------------------------------- + +namespace Think; + +class Model { + // 操作状态 + const MODEL_INSERT = 1; // 插入模型数据 + const MODEL_UPDATE = 2; // 更新模型数据 + const MODEL_BOTH = 3; // 包含上面两种方式 + // 当前数据库操作对象 + protected $db = null; + // 主键名称 + protected $pk = 'id'; + // 数据表前缀 + protected $tablePrefix = ''; + // 模型名称 + protected $name = ''; + // 数据库名称 + protected $dbName = ''; + //数据库配置 + protected $connection = ''; + // 数据表名(不包含表前缀) + protected $tableName = ''; + // 实际数据表名(包含表前缀) + protected $trueTableName = ''; + // 最近错误信息 + protected $error = ''; + // 字段信息 + protected $fields = []; + // 数据信息 + protected $data = []; + // 查询表达式参数 + protected $options = []; + // 命名范围定义 + protected $_scope = []; + // 是否自动检测数据表字段信息 + protected $autoCheckFields = false; + + /** + * 架构函数 + * 取得DB类的实例对象 字段检查 + * @access public + * @param string $name 模型名称 + * @param array $config 模型配置 + */ + public function __construct($name='',$config=[]) { + // 模型初始化 + $this->_initialize(); + // 传入模型参数 + if(!empty($name)){ + $this->name = $name; + }elseif(empty($this->name)){ + $this->name = $this->getModelName(); + } + if(strpos($this->name,'.')) { // 支持 数据库名.模型名的 定义 + list($this->dbName,$this->name) = explode('.',$this->name); + } + + if(isset($config['table_prefix'])) { + $this->tablePrefix = $config['table_prefix']; + } + if(isset($config['connection'])) { + $this->connection = $config['connection']; + } + if(isset($config['table_name'])) { + $this->tableName = $config['table_name']; + } + if(isset($config['true_table_name'])) { + $this->trueTableName = $config['true_table_name']; + } + if(isset($config['db_name'])) { + $this->dbName = $config['db_name']; + } + + // 设置表前缀 + if(empty($this->tablePrefix)) { + $this->tablePrefix = is_null($this->tablePrefix)?'':C('database.prefix'); + } + + // 数据库初始化操作 + // 获取数据库操作对象 + // 当前模型有独立的数据库连接信息 + $this->db(0,$this->connection); + } + + /** + * 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name,$value) { + // 设置数据对象属性 + $this->data[$name] = $value; + } + + /** + * 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) { + return isset($this->data[$name])?$this->data[$name]:null; + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) { + return isset($this->data[$name]); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) { + unset($this->data[$name]); + } + + // 回调方法 初始化模型 + protected function _initialize() {} + + /** + * 对保存到数据库的数据进行处理 + * @access protected + * @param mixed $data 要操作的数据 + * @return boolean + */ + protected function _facade($data) { + // 检查非数据字段 + if(!empty($this->fields)) { + foreach ($data as $key=>$val){ + if(!in_array($key,$this->fields,true)){ + unset($data[$key]); + }elseif(is_scalar($val)) { + // 字段类型检查 + $this->_parseType($data,$key); + } + } + } + // 安全过滤 + if(!empty($this->options['filter'])) { + $data = array_map($this->options['filter'],$data); + unset($this->options['filter']); + } + $this->_before_write($data); + return $data; + } + + // 写入数据前的回调方法 包括新增和更新 + protected function _before_write(&$data) {} + + /** + * 新增数据 + * @access public + * @param mixed $data 数据 + * @param boolean $replace 是否replace + * @return mixed + */ + public function add($data='',$replace=false) { + if(empty($data)) { + // 没有传递数据,获取当前数据对象的值 + if(!empty($this->data)) { + $data = $this->data; + // 重置数据 + $this->data = []; + }else{ + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + } + // 分析表达式 + $options = $this->_parseOptions(); + // 数据处理 + $data = $this->_facade($data); + if(false === $this->_before_insert($data,$options)) { + return false; + } + // 写入数据到数据库 + $result = $this->db->insert($data,$options,$replace); + if(false !== $result ) { + $insertId = $this->getLastInsID(); + if($insertId) { + // 自增主键返回插入ID + $data[$this->getPk()] = $insertId; + $this->_after_insert($data,$options); + return $insertId; + } + $this->_after_insert($data,$options); + } + return $result; + } + // 插入数据前的回调方法 + protected function _before_insert(&$data,$options) {} + // 插入成功后的回调方法 + protected function _after_insert($data,$options) {} + + /** + * 保存数据 + * @access public + * @param mixed $data 数据 + * @return boolean + */ + public function save($data='') { + if(empty($data)) { + // 没有传递数据,获取当前数据对象的值 + if(!empty($this->data)) { + $data = $this->data; + // 重置数据 + $this->data = []; + }else{ + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + } + // 数据处理 + $data = $this->_facade($data); + // 分析表达式 + $options = $this->_parseOptions(); + if(false === $this->_before_update($data,$options)) { + return false; + } + if(!isset($options['where']) ) { + // 如果存在主键数据 则自动作为更新条件 + if(isset($data[$this->getPk()])) { + $pk = $this->getPk(); + $where[$pk] = $data[$pk]; + $options['where'] = $where; + $pkValue = $data[$pk]; + unset($data[$pk]); + }else{ + // 如果没有任何更新条件则不执行 + $this->error = Lang::get('_OPERATION_WRONG_'); + return false; + } + } + $result = $this->db->update($data,$options); + if(false !== $result) { + if(isset($pkValue)) $data[$pk] = $pkValue; + $this->_after_update($data,$options); + } + return $result; + } + // 更新数据前的回调方法 + protected function _before_update(&$data,$options) {} + // 更新成功后的回调方法 + protected function _after_update($data,$options) {} + + /** + * 删除数据 + * @access public + * @param mixed $options 表达式 + * @return mixed + */ + public function delete($options=[]) { + if(empty($options) && empty($this->options['where'])) { + // 如果删除条件为空 则删除当前数据对象所对应的记录 + if(!empty($this->data) && isset($this->data[$this->getPk()])) + return $this->delete($this->data[$this->getPk()]); + else + return false; + } + if(is_numeric($options) || is_string($options)) { + // 根据主键删除记录 + $pk = $this->getPk(); + if(strpos($options,',')) { + $where[$pk] = ['IN', $options]; + }else{ + $where[$pk] = $options; + } + $pkValue = $where[$pk]; + $options = []; + $options['where'] = $where; + } + // 分析表达式 + $options = $this->_parseOptions($options); + $result= $this->db->delete($options); + if(false !== $result) { + $data = []; + if(isset($pkValue)) $data[$pk] = $pkValue; + $this->_after_delete($data,$options); + } + // 返回删除记录个数 + return $result; + } + // 删除成功后的回调方法 + protected function _after_delete($data,$options) {} + + /** + * 查询数据集 + * @access public + * @param mixed $options 表达式参数 + * @return mixed + */ + public function select($options=[]) { + if(is_string($options) || is_numeric($options)) { + // 根据主键查询 + $pk = $this->getPk(); + if(strpos($options,',')) { + $where[$pk] = ['IN',$options]; + }else{ + $where[$pk] = $options; + } + $options = []; + $options['where'] = $where; + }elseif(false === $options){ // 用于子查询 不查询只返回SQL + $options = []; + // 分析表达式 + $options = $this->_parseOptions($options); + return '( '.$this->db->buildSelectSql($options).' )'; + } + // 分析表达式 + $options = $this->_parseOptions($options); + $resultSet = $this->db->select($options); + if(false === $resultSet) { + return false; + } + if(empty($resultSet)) { // 查询结果为空 + return null; + } + $this->_after_select($resultSet,$options); + return $resultSet; + } + // 查询成功后的回调方法 + protected function _after_select(&$resultSet,$options) {} + + /** + * 生成查询SQL 可用于子查询 + * @access public + * @param array $options 表达式参数 + * @return string + */ + public function buildSql($options=[]) { + // 分析表达式 + $options = $this->_parseOptions($options); + return '( '.$this->db->buildSelectSql($options).' )'; + } + + /** + * 分析表达式 + * @access protected + * @param array $options 表达式参数 + * @return array + */ + protected function _parseOptions($options=[]) { + if(is_array($options)) + $options = array_merge($this->options,$options); + // 查询过后清空sql表达式组装 避免影响下次查询 + $this->options = []; + + if(!empty($options['alias'])) { + $options['table'] .= ' '.$options['alias']; + } + // 记录操作的模型名称 + $options['model'] = $this->name; + + if(isset($options['table'])) {// 动态指定表名 + $fields = $this->db->getFields($this->options['table']); + $fields = $fields?array_keys($fields):false; + }else{ + $options['table'] = $this->getTableName(); + $fields = $this->getDbFields(); + } + // 字段类型验证 + if(isset($options['where']) && is_array($options['where']) && !empty($fields)) { + // 对数组查询条件进行字段类型检查 + foreach ($options['where'] as $key=>$val){ + $key = trim($key); + if(in_array($key,$fields,true)){ + if(is_scalar($val)) { + $this->_parseType($options['where'],$key); + } + }elseif('_' != substr($key,0,1) && false === strpos($key,'.') && false === strpos($key,'(') && false === strpos($key,'|') && false === strpos($key,'&')){ + unset($options['where'][$key]); + } + } + } + + + // 表达式过滤 + $this->_options_filter($options); + return $options; + } + // 表达式过滤回调方法 + protected function _options_filter(&$options) {} + + /** + * 数据类型检测 + * @access protected + * @param mixed $data 数据 + * @param string $key 字段名 + * @return void + */ + protected function _parseType(&$data,$key) { + if(isset($this->fields['_type'][$key])) { + $fieldType = strtolower($this->fields['_type'][$key]); + if(false === strpos($fieldType,'bigint') && false !== strpos($fieldType,'int')) { + $data[$key] = intval($data[$key]); + }elseif(false !== strpos($fieldType,'float') || false !== strpos($fieldType,'double')){ + $data[$key] = floatval($data[$key]); + }elseif(false !== strpos($fieldType,'bool')){ + $data[$key] = (bool)$data[$key]; + } + } + } + + /** + * 查询数据 + * @access public + * @param mixed $options 表达式参数 + * @return mixed + */ + public function find($options=[]) { + if(is_numeric($options) || is_string($options)) { + $where[$this->getPk()] = $options; + $options = []; + $options['where'] = $where; + } + // 总是查找一条记录 + $options['limit'] = 1; + // 分析表达式 + $options = $this->_parseOptions($options); + $resultSet = $this->db->select($options); + if(false === $resultSet) { + return false; + } + if(empty($resultSet)) {// 查询结果为空 + return null; + } + $this->data = $resultSet[0]; + $this->_after_find($this->data,$options); + return $this->data; + } + // 查询成功的回调方法 + protected function _after_find(&$result,$options) {} + + /** + * 创建数据对象 但不保存到数据库 + * @access public + * @param mixed $data 创建数据 + * @param string $type 状态 + * @return mixed + */ + public function create($data='',$type='') { + // 如果没有传值默认取POST数据 + if(empty($data)) { + $data = $_POST; + }elseif(is_object($data)){ + $data = get_object_vars($data); + } + // 验证数据 + if(empty($data) || !is_array($data)) { + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + + // 状态 + $type = $type?$type:(!empty($data[$this->getPk()])?self::MODEL_UPDATE:self::MODEL_INSERT); + + // 检测提交字段的合法性 + if(isset($this->options['field'])) { // $this->field('field1,field2...')->create() + $fields = $this->options['field']; + unset($this->options['field']); + }elseif($type == self::MODEL_INSERT && isset($this->insertFields)) { + $fields = $this->insertFields; + }elseif($type == self::MODEL_UPDATE && isset($this->updateFields)) { + $fields = $this->updateFields; + } + if(isset($fields)) { + if(is_string($fields)) { + $fields = explode(',',$fields); + } + foreach ($data as $key=>$val){ + if(!in_array($key,$fields)) { + unset($data[$key]); + } + } + } + // 过滤创建的数据 + $this->_create_filter($data); + // 赋值当前数据对象 + $this->data = $data; + // 返回创建的数据以供其他调用 + return $data; + } + // 数据对象创建后的回调方法 + protected function _create_filter(&$data){} + + /** + * 切换当前的数据库连接 + * @access public + * @param integer $linkNum 连接序号 + * @param mixed $config 数据库连接信息 + * @return Model + */ + public function db($linkNum='',$config=''){ + if(''===$linkNum && $this->db) { + return $this->db; + } + static $_linkNum = []; + static $_db = []; + if(!isset($_db[$linkNum]) || (isset($_db[$linkNum]) && $config && $_linkNum[$linkNum]!=$config) ) { + // 创建一个新的实例 + if(!empty($config) && is_string($config) && false === strpos($config,'/')) { // 支持读取配置参数 + $config = C($config); + } + $_db[$linkNum] = Db::instance($config); + }elseif(NULL === $config){ + $_db[$linkNum]->close(); // 关闭数据库连接 + unset($_db[$linkNum]); + return ; + } + + // 记录连接信息 + $_linkNum[$linkNum] = $config; + // 切换数据库连接 + $this->db = $_db[$linkNum]; + $this->_after_db(); + return $this; + } + // 数据库切换后回调方法 + protected function _after_db() {} + + /** + * 得到当前的数据对象名称 + * @access public + * @return string + */ + public function getModelName() { + if(empty($this->name)) + $this->name = substr(get_class($this),0,-5); + return $this->name; + } + + /** + * 得到完整的数据表名 + * @access public + * @return string + */ + public function getTableName() { + if(empty($this->trueTableName)) { + $tableName = !empty($this->tablePrefix) ? $this->tablePrefix : ''; + if(!empty($this->tableName)) { + $tableName .= $this->tableName; + }else{ + $tableName .= parse_name($this->name); + } + $this->trueTableName = strtolower($tableName); + } + return (!empty($this->dbName)?$this->dbName.'.':'').$this->trueTableName; + } + + /** + * 返回模型的错误信息 + * @access public + * @return string + */ + public function getError(){ + return $this->error; + } + + /** + * 返回数据库的错误信息 + * @access public + * @return string + */ + public function getDbError() { + return $this->db->getError(); + } + + /** + * 返回最后插入的ID + * @access public + * @return string + */ + public function getLastInsID() { + return $this->db->getLastInsID(); + } + + /** + * 返回最后执行的sql语句 + * @access public + * @return string + */ + public function getLastSql() { + return $this->db->getLastSql($this->name); + } + + /** + * 获取主键名称 + * @access public + * @return string + */ + public function getPk() { + return isset($this->fields['_pk'])?$this->fields['_pk']:$this->pk; + } + + /** + * 获取数据表字段信息 + * @access public + * @return array + */ + public function getDbFields(){ + if($this->fields) { + $fields = $this->fields; + unset($fields['_pk'],$fields['_type']); + return $fields; + }else{ + $fields = Cache::get(md5($this->getTableName())); + if(!$fields) { + $fields = $this->db->getFields($this->getTableName()); + $this->fields = array_keys($fields); + foreach ($fields as $key=>$val){ + // 记录字段类型 + $type[$key] = $val['type']; + if($val['primary']) { + $this->fields['_pk'] = $key; + } + } + // 记录字段类型信息 + $this->fields['_type'] = $type; + Cache::set(md5($this->trueTableName),$this->fields); + $fields = $this->fields; + }else{ + $this->fields = $fields; + } + unset($fields['_pk'],$fields['_type']); + return $fields; + } + } + + /** + * 设置数据对象值 + * @access public + * @param mixed $data 数据 + * @return Model + */ + public function data($data=''){ + if('' === $data && !empty($this->data)) { + return $this->data; + } + if(is_object($data)){ + $data = get_object_vars($data); + }elseif(is_string($data)){ + parse_str($data,$data); + }elseif(!is_array($data)){ + E(L('_DATA_TYPE_INVALID_')); + } + $this->data = $data; + return $this; + } + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join + * @return Model + */ + public function join($join) { + if(is_array($join)) { + $this->options['join'] = $join; + }elseif(!empty($join)) { + $this->options['join'][] = $join; + } + return $this; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union + * @param boolean $all + * @return Model + */ + public function union($union,$all=false) { + if(empty($union)) return $this; + if($all) { + $this->options['union']['_all'] = true; + } + if(is_object($union)) { + $union = get_object_vars($union); + } + // 转换union表达式 + if(is_string($union) ) { + $options = $union; + }elseif(is_array($union)){ + if(isset($union[0])) { + $this->options['union'] = array_merge($this->options['union'],$union); + return $this; + }else{ + $options = $union; + } + }else{ + E(L('_DATA_TYPE_INVALID_')); + } + $this->options['union'][] = $options; + return $this; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key + * @param integer $expire + * @param string $type + * @return Model + */ + public function cache($key=true,$expire=null,$type=''){ + if(false !== $key) + $this->options['cache'] = ['key'=>$key,'expire'=>$expire,'type'=>$type]; + return $this; + } + + /** + * 指定查询字段 支持字段排除 + * @access public + * @param mixed $field + * @param boolean $except 是否排除 + * @return Model + */ + public function field($field,$except=false){ + if(true === $field) {// 获取全部字段 + $fields = $this->getDbFields(); + $field = $fields?$fields:'*'; + }elseif($except) {// 字段排除 + if(is_string($field)) { + $field = explode(',',$field); + } + $fields = $this->getDbFields(); + $field = $fields?array_diff($fields,$field):$field; + } + $this->options['field'] = $field; + return $this; + } + + /** + * 调用命名范围 + * @access public + * @param mixed $scope 命名范围名称 支持多个 和直接定义 + * @param array $args 参数 + * @return Model + */ + public function scope($scope='',$args=NULL){ + if('' === $scope) { + if(isset($this->_scope['default'])) { + // 默认的命名范围 + $options = $this->_scope['default']; + }else{ + return $this; + } + }elseif(is_string($scope)){ // 支持多个命名范围调用 用逗号分割 + $scopes = explode(',',$scope); + $options = []; + foreach ($scopes as $name){ + if(!isset($this->_scope[$name])) continue; + $options = array_merge($options,$this->_scope[$name]); + } + if(!empty($args) && is_array($args)) { + $options = array_merge($options,$args); + } + }elseif(is_array($scope)){ // 直接传入命名范围定义 + $options = $scope; + } + + if(is_array($options) && !empty($options)){ + $this->options = array_merge($this->options,array_change_key_case($options)); + } + return $this; + } + + /** + * 指定查询条件 支持安全过滤 + * @access public + * @param mixed $where 条件表达式 + * @param mixed $parse 预处理参数 + * @return Model + */ + public function where($where,$parse=null){ + if(!is_null($parse) && is_string($where)) { + if(!is_array($parse)) { + $parse = func_get_args(); + array_shift($parse); + } + $parse = array_map([$this->db,'escapeString'],$parse); + $where = vsprintf($where,$parse); + }elseif(is_object($where)){ + $where = get_object_vars($where); + } + if(is_string($where) && '' != $where){ + $map = []; + $map['_string'] = $where; + $where = $map; + } + if(isset($this->options['where'])){ + $this->options['where'] = array_merge($this->options['where'],$where); + }else{ + $this->options['where'] = $where; + } + + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 + * @return Model + */ + public function limit($offset,$length=null){ + $this->options['limit'] = is_null($length)?$offset:$offset.','.$length; + return $this; + } + + /** + * 指定分页 + * @access public + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 + * @return Model + */ + public function page($page,$listRows=null){ + $this->options['page'] = is_null($listRows)?$page:$page.','.$listRows; + return $this; + } + + /** + * 指定数据表 + * @access public + * @param string $table 表名 + * @return Model + */ + public function table($table){ + $this->options['table'] = $table; + return $this; + } + + /** + * 指定排序 + * @access public + * @param string $order 排序 + * @return Model + */ + public function order($order){ + $this->options['order'] = $order; + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string $group GROUP + * @return Model + */ + public function group($group){ + $this->options['group'] = $group; + return $this; + } + + /** + * 指定having查询 + * @access public + * @param string $having having + * @return Model + */ + public function having($table){ + $this->options['having'] = $having; + return $this; + } + + /** + * 指定查询lock + * @access public + * @param boolean $lock 是否lock + * @return Model + */ + public function lock($lock=false){ + $this->options['lock'] = $lock; + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param string $distinct 是否唯一 + * @return Model + */ + public function distinct($distinct){ + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param string $alias 数据表别名 + * @return Model + */ + public function alias($alias){ + $this->options['alias'] = $alias; + return $this; + } + + /** + * 指定写入过滤方法 + * @access public + * @param string $filter 指定过滤方法 + * @return Model + */ + public function filter($filter){ + $this->options['filter'] = $filter; + return $this; + } + + /** + * 指定参数绑定 + * @access public + * @param string $filter 指定过滤方法 + * @return Model + */ + public function bind($bind){ + $this->options['bind'] = $bind; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return Model + */ + public function comment($comment){ + $this->options['comment'] = $comment; + return $this; + } + } \ No newline at end of file