您的位置:首页 > 编程语言 > PHP开发

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

2014-04-05 20:16 169 查看
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方法;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: