http://www.rritw.com/a/shujuku/Mysql/20140329/442735.html
这真是一件很郁闷的事情,项目的一个子项目(cms)中需要实现伪静态并且为了快速完成,选择了Yii,
各方面都已准备好,路由规则,nginx rewrite,mysql slave ...但就是没有注意到,服务器没有pdo_mysql驱动,当时就震惊了! 项目已经上线,再编译已是不怎么实际,只好思考解决档案,也就是在这个时候才发现Yii只提供了PDO连接方式;时间很急,当时想到的解决办法:
第一种方式最简单,但没有这么做,因为需要重启php-fpm,而且很难保证成功和稳定性;
第二种是同事推荐的,代码量是很大,而且这样做了之后就很难回头,还要重新测试;
第三种倒是一个不错的解决方案,并且Yii支持指定pdoClass,对框架和原有代码都没有侵入性,短时间内也可以实现,所以就这个了;
由于对数据库只读,所以没有考虑太多,也不打算所有的方法都实现,就阅读了Yii的 DAO处理部分,简单写了这两个class
<?php /** * 使用mysqli模拟PDO 服务器环境居然不支持PDO_mysql -_-|| * @author xl * create on 2014-3-26 */ class PDO_Mysql{//extends PDO private $handle = NULL; private $tmpParams = array(); const MYSQL_ATTR_USE_BUFFERED_QUERY = 1000; const MYSQL_ATTR_LOCAL_INFILE = 1001; const MYSQL_ATTR_INIT_COMMAND = 1002; const MYSQL_ATTR_READ_DEFAULT_FILE = 1003; const MYSQL_ATTR_READ_DEFAULT_GROUP = 1004; const MYSQL_ATTR_MAX_BUFFER_SIZE = 1005; const MYSQL_ATTR_DIRECT_QUERY = 1006; public function __construct($connectionString,$username,$password,$options=array()){ //简单解析 preg_match('/host=([\w\.]+);dbname=(\w+)/i', $connectionString,$matches); if(count($matches)<3){ throw new PDOException('connectionString is invalid'); } $this->handle = new mysqli($matches[1],$username,$password,$matches[2]); //$options } public function beginTransaction(){ return $this->handle->autocommit(FALSE); } public function commit(){ return $this->handle->commit(); } public function rollBack(){ return $this->handle->rollback(); } public function errorCode(){ return $this->handle->errno; } public function errorInfo(){ return array_values($this->handle->error_list); } public function setAttribute($attribute, $value, &$source = null) { switch($attribute) { case PDO::ATTR_AUTOCOMMIT: $value = $value ? 1 : 0; if(!$this->handle->autocommit($value)) { throw new PDOException('set autocommit faild'); } return true; case PDO::ATTR_TIMEOUT: $value = intval($value); if($value > 1 && $this->handle->options( MYSQLI_OPT_CONNECT_TIMEOUT, $value)) { $source[PDO::ATTR_TIMEOUT] = $value; return true; } break; case self::MYSQL_ATTR_LOCAL_INFILE: $value = $value ? true : false; if($this->handle->options(MYSQLI_OPT_LOCAL_INFILE, $value)) { $source[self::MYSQL_ATTR_LOCAL_INFILE] = $value; return true; } break; case self::MYSQL_ATTR_INIT_COMMAND: if($value && $this->handle->options( MYSQLI_INIT_COMMAND, $value)) { $source[self::MYSQL_ATTR_INIT_COMMAND] = $value; return true; } break; case self::MYSQL_ATTR_READ_DEFAULT_FILE: $value = $value ? true : false; if($this->handle->options(MYSQLI_READ_DEFAULT_FILE, $value)) { $source[self::MYSQL_ATTR_READ_DEFAULT_FILE] = $value; return true; } break; case self::MYSQL_ATTR_READ_DEFAULT_GROUP: $value = $value ? true : false; if($this->handle->options(MYSQLI_READ_DEFAULT_GROUP, $value)) { $source[self::MYSQL_ATTR_READ_DEFAULT_GROUP] = $value; return true; } break; } return false; } public function getAttribute($attribute){ if(PDO::ATTR_DRIVER_NAME == $attribute){ return 'mysql'; } } public function exec($statement){ $result = $this->handle->query($statement); if(is_object($result)){ mysqli_free_result($result); return 0; } return $this->handle->affected_rows; } public static function getAvailableDrivers(){ return array('mysql'); } public function prepare($statement){ $this->tmpParams = array(); $newstatement = preg_replace_callback('/(:\w+)/i', function($matches){ $this->tmpParams[] = $matches[1]; return '?'; }, $statement); $s = $this->handle->prepare($newstatement); if($s==false) { throw new PDOException($this->handle->error); } $ostatement = new PDO_Mysql_Statement($s, $this); $ostatement->setPrepareParams($this->tmpParams); $ostatement->setStateSql($statement); return $ostatement; } public function lastInsertId(){ return $this->handle->insert_id; } public function quote($param,$parameter_type=-1){ switch($parameter_type) { case PDO::PARAM_BOOL:return $param ? 1 : 0; case PDO::PARAM_NULL:return 'NULL'; case PDO::PARAM_INT: return is_null($param) ? 'NULL' : (is_int($param) ? $param : (float)$param); default:return '\'' . $this->handle->real_escape_string($param) . '\''; } } public function close(){ $this->handle->close(); } public function disconnect(){ $this->close(); } public function __destruct() { $this->close(); } } class PDO_Mysql_Statement { private $_statement = NULL; private $_connnection = NULL; private $_pql = 'unknow'; private $_typeMap = array( 'i'=>PDO::PARAM_INT, 's'=>PDO::PARAM_STR, 'd'=>PDO::PARAM_STR ); private $prepareParams =array();// private $readyTypes = array(); private $readyValues = array(); private $_result = NULL; private $_mode = MYSQL_BOTH; public function __construct($_statement,$connnection){ $this->_statement = $_statement; $this->_connnection = $connnection; } public function bindParam($parameter,$value,$type){ $type = array_search($type, $this->_typeMap); $key = array_search($parameter, $this->prepareParams); if($key!==false and $type!==false){ $this->readyTypes[$key] = $type; $this->readyValues[$key] = $value; return true; }else{ return false; } } public function bindValue($parameter,$value,$type){ return $this->bindParam($parameter, $value, $type); } public function setStateSql($sql){ $this->_pql = $sql; } public function execute($options=array()){ if(!empty($this->readyTypes)){ $params =$this->readyValues; ksort($params); array_unshift($params,implode($this->readyTypes)); $tempstatement = $this->_statement; call_user_func_array(array($tempstatement,'bind_param'),$this->refValues($params)); } $this->_statement->execute(); } public function rowCount(){ return $this->_statement->num_rows(); } public function setFetchMode($mode){ $mode = $this->transformFetchMode($mode); if($mode === false){ return false; } $this->_mode = $mode; return true; } public function closeCursor(){ //$this->_result = NULL; $this->prepareParams =array(); $this->readyTypes = array(); $this->readyValues = array(); $this->_pql = 'unknow'; $this->_mode = MYSQL_BOTH; if(!empty($this->_result)){ $this->_result->free(); } $this->_result = NULL; //$this->_connnection->close(); return $this->_statement->reset(); } public function columnCount(){ return $this->_statement->field_count; } public function debugDumpParams(){ echo $this->_pql; } public function errorCode(){ return $this->_statement->errno; } public function errorInfo(){ return array_values($this->_statement->error_list); } public function setPrepareParams($params){ $this->prepareParams = $params; } public function fetch($mode=NULL){ if($this->_result==NULL){ $this->_result = $this->_statement->get_result(); } if(empty($this->_result)){ throw new PDOException($this->_statement->error); } $_mode = $this->_mode; if(!empty($mode) and ($mode = $this->transformFetchMode($mode))!=false){ $_mode = $mode; } $result = $this->_result->fetch_array($_mode); return $result === NULL ? false : $result; } public function fetchColumn($column_number=0){ $column = $this->fetch(PDO::FETCH_NUM); return $column[$column_number]; } public function fetchAll($mode=NULL){ if($this->_result==NULL){ $this->_result = $this->_statement->get_result(); } if(empty($this->_result)){ throw new PDOException($this->_statement->error); } $_mode = $this->_mode; if(!empty($mode) and ($mode = $this->transformFetchMode($mode))!=false){ $_mode = $mode; } $result = $this->_result->fetch_all($_mode); return $result === NULL ? false : $result; } public function fetchObject(){ throw new PDOException('Not supported yet'); } private function transformFetchMode($mode){ switch ($mode){ case PDO::FETCH_ASSOC : return MYSQLI_ASSOC; case PDO::FETCH_BOTH : return MYSQLI_BOTH; case PDO::FETCH_NUM : return MYSQLI_NUM; default : return false; } } private function refValues($arr){ $refs = array(); foreach($arr as $key => $value){ if($key!=0){ $refs[$key] = &$arr[$key]; }else{ $refs[$key] = $value; } } return $refs; } public function __destruct(){ if(!empty($this->_result)) { $this->_result->free(); } if(!empty($this->_statement)){ $this->_statement->close(); } } }
都是PDO的方法,就不加注释了,在index.php添加了两行;
/** * 服务器目前不支持pdo_mysql连接方式,如果支持了,请删除此处代码,并删除components相关文件; * 本地环境是用PDO_Mysql模拟,测试 */ if(!in_array('mysql', PDO::getAvailableDrivers())){ $config = require($config); $config['components']['db']['pdoClass'] = 'PDO_Mysql'; }
中间还遇到一个不得不说的问题,Yii的CDbDataReader 实现了Iterator接口,当使用foreach进行遍历时,PDOStatement::fetch()在获取不到行的时候必须要返回boolean,返回NULL将是死循环;
最后也提一下伪静态的问题,对于框架路由的方式,nginx 配置pathinfo支持,不仅麻烦而且有一定的风险,其实大可不必,rewrite 就可以了
rewrite ^/html/(.*)$ /html/index.php?r=$1;
其他相关代码和配置就不便分享了;
PS: stackoverflow真是个好地方,解决了我好多问题比如这个refValues方法;