假設我們今天利用PHP+MSSQL架設了一個網站,結果有一天我們想把網站移到虛擬主機上面,但是虛擬主機只支援MySQL,資料庫的轉換不免要花不少功夫。轉換完成後再看看我們眾多的PHP程式碼,逐一取代還真是大工程阿,更危險的是萬一漏了幾支沒改到,又剛好在不常用的功能頁面中,我們也沒發現。等到使用者遇到問題時,若是反應了我們還能及時改正;若是他們選擇放棄使用了,那我們就不知道要流失多少的使用者了!
PHP可以支援很多常用的資料庫,妥善利用PHP物件導向的能力,可以讓我們的網站能很容易的在各種不同的資料庫間轉換,廢話不多說,快來看看怎麼做吧!
首先我們建立一個通用的資料庫抽象類別mydb:
abstract class mydb
{
var $rs;
var $link;
function mydb(){}
abstract function connect($host,$db,$id,$password);
abstract function close($rs);
abstract function query_exec($sql);
abstract function query_exec_new($sql);
abstract function num_rows_exec($rs);
abstract function fetch_row_exec($rs);
function query() //傳入參數:$query,[new]
{
$numargs = func_num_args();
if ($numargs == 2 && func_get_arg(1)=="new")
{
return $this->query_exec_new(func_get_arg(0));
}
if ($numargs == 1)
{
return $this->query_exec(func_get_arg(0));
}
}
function num_rows() //傳入參數:[$rs]
{
$numargs = func_num_args();
if ($numargs == 0)
{
return $this->num_rows_exec($this->rs);
}
if ($numargs == 1)
{
return $this->num_rows_exec(func_get_arg(0));
}
}
function fetch_row()
{
$numargs = func_num_args();
if ($numargs == 0)
{
$rows = $this->fetch_row_exec($this->rs);
}
if ($numargs == 1)
{
$rows = $this->fetch_row_exec(func_get_arg(0));
}
for($i=0;$i < sizeof($rows);$i++)
$rows[$i] = str_replace("
\\\\","\\",$rows[$i]);
return $rows;
}
}
這樣做有幾個好處:
1.所有實際操作的資料庫類別繼承於mydb,這樣共通的程式碼寫在mydb類別的方法中,專屬的程式碼就寫在各個資料庫類別的方法中。如此一來萬一共通的程式部分有修正時就不用到各類別中逐一修正,可以避免遺漏了某個資料庫類別。例如下面這個方法中就包含了共同的程式碼部分:
function fetch_row()
{
$numargs = func_num_args();
if ($numargs == 0)
{
$rows = $this->fetch_row_exec($this->rs);
}
if ($numargs == 1)
{
$rows = $this->fetch_row_exec(func_get_arg(0));
}
for($i=0;$i < sizeof($rows);$i++)
$rows[$i] = str_replace("
\\\\","\\",$rows[$i]);
return $rows;
}
程式中我們都呼叫這個fetch_row()方法,然後再依照參數,這個方法會再呼叫子類別中的fetch_row_exec()方法,fetch_row_exec()方法中放的就是各資料庫類別自己專屬的程式碼。
PS.這段程式是有點小問題的,大家自己想一想該如何修正會比較完整哦~
2.利用抽象方法來避免繼承的類別中忘了實做必要的方法:
abstract function connect($host,$db,$id,$password);
abstract function close($rs);
abstract function query_exec($sql);
abstract function query_exec_new($sql);
abstract function num_rows_exec($rs);
abstract function fetch_row_exec($rs);
未來加入新的資料庫類別時,這些方法都是必須實做的,若是忘了實做其中一個方法,會得到如下的錯誤訊息:
Fatal error: Class myaccessdb contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (mydb::[未實做的方法名稱])
接下來看看Mysql的實做類別,我們把它取名叫做mymysqldb:
class mymysqldb extends mydb
{
function mymysqldb()
{}
function connect($host,$db,$id,$password)
{
$this->link=mysql_pconnect($host,$id,$password);
mysql_select_db($db,$this->link);
return $this->link;
}
function close($rs)
{}
function query_exec($sql)
{
$this->rs = mysql_query($sql,$this->link);
return $this->rs;
}
function query_exec_new($sql)
{
return mysql_query($sql,$this->link);;
}
function num_rows_exec($rs)
{
return mysql_num_rows($rs);
}
function fetch_row_exec($rs)
{
return mysql_fetch_row($rs);
}
}
再來看看MSSQL的類別實做,我們把它取名叫做mymssqldb:
class mymssqldb extends mydb
{
function mymssqldb()
{}
function connect($host,$db,$id,$password)
{
$this->link=mssql_pconnect($host,$id,$password);
mssql_select_db($db,$this->link);
return $this->link;
}
function close($rs)
{}
function query_exec($sql)
{
$this->rs = mssql_query($sql,$this->link);
return $this->rs;
}
function query_exec_new($sql)
{
return mssql_query($sql,$this->link);;
}
function num_rows_exec($rs)
{
return mssql_num_rows($rs);
}
function fetch_row_exec($rs)
{
return mssql_fetch_row($rs);
}
}
怎麼程式碼這麼像?沒錯,這兩個資料庫的PHP函式就是這麼像!那這樣還需要物件導向嗎?批次字串取代就好了ㄚ!再來看看Access資料庫類別的實作方法吧,你一定會覺得還是用物件導向來做比較好了,我們把它取名叫做myaccessdb:
class myaccessdb extends mydb
{
function myaccessdb()
{}
function connect($host,$db,$id,$password)
{
$this->link = new COM("ADODB.Connection") or die ("ADO連接失敗!");
$dsn="DRIVER={Microsoft Access Driver (*.mdb)};DBQ=".realpath($db).";Uid=$id;Pwd=$password";
$this->link->open($dsn);
$this->rs = new COM("ADODB.RecordSet");
return $this->link;
}
function close($rs)
{
$rs->close();
}
function query_exec($sql)
{
if($this->rs->state != 0)
$this->close($this->rs);
$this->rs->Open($sql,$this->link,1,3);
return $this->rs;
}
function query_exec_new($sql)
{
$rs = new COM("ADODB.RecordSet");
$rs->Open($sql,$this->link,1,3);
return $rs;
}
function num_rows_exec($rs)
{
return $rs->recordcount;
}
function fetch_row_exec($rs)
{
if (!$rs->EOF)
{
$rows = array();
for($i=0 ; $i < $rs->Fields->count ; $i++)
array_push($rows,$rs->fields[$i]->value);
$rs->MoveNext();
return $rows;
}
else
return NULL;
}
}
最後我們來看看使用方式囉!
放在config.ini.php(每一支PHP程式都include的設定檔)中的宣告是有些不同,分別列出如下:
Mysql:
$mydb = new mymysqldb();
$conn = $mydb->connect("127.0.0.1","testdb","testid","testpasswd");
MSSQL:
$mydb = new mymssqldb();
$conn = $mydb->connect("127.0.0.1","testdb","testid","testpasswd");
Access:
$mydb = new myaccessdb();
$conn = $mydb->connect("","testdb","","testpasswd");
程式中的使用就完全相同了,我們來看看:
$mydb->query("select field1,field2,field3,field4 from table where field0 = '0'");
if($mydb->num_rows() > 0)
{
list($field1,$field2,$field3,$field4)=$mydb->fetch_row();
}
所以換資料庫時不用動程式,只要改設定檔囉!
這邊我用的是Query的方式,所以必須注意的是使用的SQL必須是通用的標準SQL,例如Mysql中的limit就不能使用了!當然還有更好的改進方式,等我有空完成了會再分享給大家!