當Yii趕上不支持pdo_mysql的服務器

    (2014-10-9,在使用過程當中仍發現很多問題,已遷移至https://github.com/xiilei/php-functions/tree/master/yii,不按期維護,下列代碼再也不更新)     php

    這真是一件很鬱悶的事情,項目的一個子項目(cms)中須要實現僞靜態而且爲了快速完成,選擇了Yii, html

    各方面都已準備好,路由規則,nginx rewrite,mysql slave ...但就是沒有注意到,服務器沒有pdo_mysql驅動,當時就震驚了! 項目已經上線,再編譯已經是不怎麼實際,只好思考解決方案,也就是在這個時候才發現Yii只提供了PDO鏈接方式;時間很急,當時想到的解決辦法: mysql

  1. 從其餘服務器複製過來php_mysql.so;
  2. 重寫sql處理邏輯,改成mysqli實現(拋棄Yii的DAO);
  3. 使用mysqli模擬pdo;

第一種方式最簡單,但沒有這麼作,很難保證成功和穩定性; nginx

第二種是同事推薦的,代碼量是很大,並且這樣作了以後就很難回頭,還要從新測試; git

第三種卻是一個不錯的解決方案,而且Yii支持指定pdoClass,對框架和原有代碼都沒有侵入性,短期內也能夠實現,因此就這個了; github

因爲對數據庫只讀,因此沒有考慮太多,也不打算全部的方法都實現,就閱讀了Yii的 DAO處理部分,簡單寫了這兩個class sql

<?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添加了兩行; shell


/**
 * 服務器目前不支持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方法;

相關文章
相關標籤/搜索