当Yii遇上不支持pdo_mysql的服务器

原创
2014/03/28 19:14
阅读数 2.2K

    (2014-10-9,在使用过程中仍发现不少问题,已迁移至https://github.com/xiilei/php-functions/tree/master/yii,不定期维护,下列代码不再更新)    

    这真是一件很郁闷的事情,项目的一个子项目(cms)中需要实现伪静态并且为了快速完成,选择了Yii,

    各方面都已准备好,路由规则,nginx rewrite,mysql slave ...但就是没有注意到,服务器没有pdo_mysql驱动,当时就震惊了! 项目已经上线,再编译已是不怎么实际,只好思考解决方案,也就是在这个时候才发现Yii只提供了PDO连接方式;时间很急,当时想到的解决办法:

  1. 从其他服务器复制过来php_mysql.so;
  2. 重写sql处理逻辑,改为mysqli实现(抛弃Yii的DAO);
  3. 使用mysqli模拟pdo;

第一种方式最简单,但没有这么做,很难保证成功和稳定性;

第二种是同事推荐的,代码量是很大,而且这样做了之后就很难回头,还要重新测试;

第三种倒是一个不错的解决方案,并且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_INT
    );   
   

    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 getPdoType($type){
        static $map=array(
                'boolean'=>PDO::PARAM_BOOL,
                'integer'=>PDO::PARAM_INT,
                'string'=>PDO::PARAM_STR,
                'NULL'=>PDO::PARAM_NULL,
        );
        return isset($map[$type]) ? $map[$type] : PDO::PARAM_STR;
    }

    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;
        }
    }
    //这里bindValue已经失去了本应该有的特性
    public function bindValue($parameter,$value,$type){
        return $this->bindParam($parameter, $value, $type);
    }
    
    public function setStateSql($sql){
        $this->_pql = $sql;
    }

    //2014-9-27添加$params
    public function execute($params=array()){
        if(!empty($params)){
            foreach($params as $_k=>$_v){
                $this->bindParam($_k, $_v, $this->getPdoType(gettype($_v)));
            }
        }
        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->affected_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方法;

展开阅读全文
打赏
0
8 收藏
分享
加载中
xilei博主

引用来自“Z.kc”的评论

为什么不装一个→_→

pdo_mysql已经加入到php core了,扩展安装的方式官方早不推荐了,而且这是上线的项目,风险比较高;

2014/03/29 11:14
回复
举报
为什么不装一个→_→
2014/03/29 10:13
回复
举报
更多评论
打赏
2 评论
8 收藏
0
分享
返回顶部
顶部