http://www.rritw.com/a/shujuku/Mysql/20140329/442735.html
这真是一件很郁闷的事情,项目的一个子项目(cms)中需要实现伪静态并且为了快速完成,选择了Yii,
各方面都已准备好,路由规则,nginx rewrite,mysql slave ...但就是没有注意到,服务器没有pdo_mysql驱动,当时就震惊了! 项目已经上线,再编译已是不怎么实际,只好思考解决档案,也就是在这个时候才发现Yii只提供了PDO连接方式;时间很急,当时想到的解决办法:
- 从其他服务器复制过来php_mysql.so;
- 重写sql处理逻辑,改为mysqli实现(抛弃Yii的DAO);
- 使用mysqli模拟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方法;