PHPLIB是PHP的一些擴展庫,使用它我們可以很方便地對數據庫進行各種操作,不過,如果你要使用多個數據庫的話,它就顯得力不從心了,本文介紹了通過擴展PHPLIB,讓你魚和熊掌兼得,在使用PHPLIB的同時可以使用多個資料庫,從中你也可以了解到物件導向程式設計和如何擴充函式庫的知識,值得一讀。
資料庫管理
你可以在一個大型的資料庫中放入任何表格。不過時間長了,將會令資料庫變得越來越大,伺服器可能會跟不上IO的工作,或是沒有足夠的記憶體應付所有的存取?要分開現有的數據又非常難。明智的辦法是開始時就使用分開的資料庫,並且進行有效的資料庫管理。 如果你有一個賣書的網站,你可能有作者的列表,書價的列表,還有當前的庫存和訂單的列表。當你的業務不斷成長時,訂單將會不斷成長,而且處理每個訂單都需要進行大量的磁碟存取。很可能你將在某一天將所有的訂單都放到一個會計系統中。
現在就將訂單放到一個獨立的資料庫吧。由於庫存也是透過訂單更新的,因此庫存量也放到同樣的資料庫中。
作者的列表和書的列表都是一些靜態的信息,要經常讀取,但很少更新。實際上,更新一個作者的記錄可能只需要每5年一次,只在作者寫了一本新書(或去世)時進行。放置這些資料的伺服器的配置可與放訂單資料庫的伺服器完全不同。
包含PHPLIB
PHPLIB透過一個稱為DB_Sql的類別存取SQL資料庫。根據你需要使用的資料庫類型,將不同的inc檔包含在你的程式碼中。在這個範例中,我使用MySQL的版本。
為了在你的程式碼中使用DB_Sql,要將PHPLIB檔案安裝在它們自己的目錄中。然後,找到你的cgi-bin目錄,並且在cgi-bin的目錄旁建立phplib目錄。下一步,拷貝所有的PHPLIB .inc檔到phplib目錄。最後,修改php.inc文件,只要將「include_path=」的行改為該phplib目錄就可以了。
include_path是PHP使用include()或require()時尋找的目錄,在我的NT workstation中,include的路徑是:
include_path = ".;i:/project52/includes;i:/project52/phplib";
在Linux的系統上
include_path = ".;/home/httpd/includes;/home/httpd/phplib";
在每個PHP頁面的頂部加入
<? php
require(common.php);
? >
common.php3放在includes目錄中,包含了每個頁面要用到的所有資料和函數。在這個例子中的common.php是:
<? php
require(db_mysql.inc);
require(ct_sql.inc);
require(session.inc);
require(auth.inc);
require(perm.inc);
require(user.inc);
require(page.inc);
? >
如果你想知道每個inc檔的用處,可閱讀http://phplib.netuse.de上的PHPLIB文件。 Db_mysql.inc包含了所有DB_SQL類別的定義。如果你想使用PostGreSQL取代MySQL,只要用db_pgsql.inc取代db_mysql.inc就可以了。還有10個其它的.inc文件,可以使用MS SQL、Oracle、Sybase或其它的資料庫。
要注意的是,在這個例子中,require()和include()是完全一樣的。不過,如果放在程式碼中,或在if語句中使用時,Require()和include的使用是完全不同的,並且有不同的運行結果。
擴充PHPLIB
PHPLIB透過一個DB_Sql類別產生的物件來存取資料庫。 Db_mysql.inc包含了為MySQL修改過的DB_Sql類別。我們將透過在common.php中加入程式碼來擴充DB_sql,這些程式碼將加在包含db_mysql.inc的行後。
DB_Sql包含了許多用作查詢的函數,我們要作修改的是:
<? php
/* public: 連線管理*/
function connect($Database = "", $Host = "", $User = "", $Password = "") {
/* 處理預設連線*/
if ("" == $Database)
$Database = $this->Database;
if ("" == $Host)
$Host = $this->Host;
if ("" == $User)
$User = $this->User;
if ("" == $Password)
$Password = $this->Password;
/* 建立連接,選擇資料庫*/
if ( 0 == $this->Link_ID ) {
$this->Link_ID=mysql_pconnect($Host, $User, $Password);
if (!$this->Link_ID) {
$this->halt("pconnect($Host, $User, $Password) failed.");
return 0;
}
if ( !@mysql_select_db($Database,$this->Link_ID )) {
$this->halt("cannot use database ".$this->Database);
return 0;
}
}
return $this->Link_ID;
}
? >
在你的db_mysql.inc(或其它資料庫的相關.inc檔)中找到connect()函數,然後將它拷貝到common.php,放到包含db_mysql.inc程式碼的後面,在後面,還要將它封裝為一個類別的定義。
我發現這些程式碼有些難讀,因此,首先令拷貝來的程式碼的可讀性更好:
<? php
/* public: 連線管理*/
function connect($Database = "", $Host = "", $User = "", $Password = "") {
/* 處理預設連線*/
if ("" == $Database) {
$Database = $this->Database;
}
if ("" == $Host) {
$Host = $this->Host;
}
if ("" == $User) {
$User = $this->User;
}
if ("" == $Password) {
$Password = $this->Password;
}
/* 建立連接,選擇資料庫*/
if ( 0 == $this->Link_ID ) {
$this->Link_ID=mysql_pconnect($Host, $User, $Password);
if (!$this->Link_ID) {
$this->halt("pconnect($Host, $User, $Password) failed.");
return 0;
}
if ( !@mysql_select_db($Database,$this->Link_ID )) {
$this->halt("cannot use database ".$this->Database);
return 0;
}
}
return $this->Link_ID;
}
? >
我調整了一下括號的位置,在單行的前後也加入了一個大括號。在PHP的if語句中,如果只有一句程式碼的話你可以不用括號,但是,如果你增加多一行程式碼,就會馬上出錯。因此我建議你加入一個括號,以免後來加入程式碼時出錯。
在改變connect的程式碼之前,先了解一下connect()是如何運作的,它檢查目前是否存在一個連接,如果不存在連接的話,就建立一個連接。在每次的資料庫查詢之前,先執行這個connect()函數。可惜的是,它只在第一次連線的時候選擇資料庫,如果你的PHP頁面使用超過一個資料庫,connect()並不會選擇另外的資料庫。
要改變程式碼的話,有幾種不同的方法。我們要選擇一種對PHPLIB的影響最小,而且可讓我們在需要分析問題的時候,能夠顯示資料庫連線狀態的方法。我們需要在PHPLIB外保存連接id和資料庫的名字。只要在common.php加入:
<? php
$db_connection = 0; // 資料庫連線的id
$db_database = ""; // 目前資料庫的狀態? >
下一步,我們要對PHPLIB作修改,以便在這些變數中儲存連接id和資料庫的名字。在其它的程式碼中,你可以設定和使用同樣的變數名。在分析問題時,如果你需要知道現在使用哪個資料庫,只要在頁面中插入以下的程式碼:
<? php
Print(" db_database: " . $db_database . "");
? >
我們要怎麼讓connect()使用這些新變數呢?我們可以在頂部加入一行:
<? php
{
globals $db_connect, $db_database;
/* Handle defaults */
? >
透過這些程式碼,新變數就可被connect()存取到
在定義了$db_database後,加入:
<? php
function db_connect($db_connect_host="", $db_connect_user="",$db_connect_pass="") {
globals $db_connect;
if(!empty($db_connect_host)) {
$db_connect = mysql_pconnect($db_connect_host,
$db_connect_user, $db_connect_pass);
}
return($db_connect);
}
function db_database($db_database_new="") {
globals $db_database;
if(!empty($db_database_new)) {
$db_database = @mysql_select_db($db_database_new, db_connect());
}
return($db_database);
}
? >
只要定義這些公共的函數一次,你就可以在不同的地方使用這些公共的變量,而不需要加入global申明。以下就是使用上面db函數的公用函數:
<? php
function connect($Database = "", $Host = "", $User = "", $Password = "") {
/* 處理預設連線*/
if ("" == $Database) {
$Database = $this->Database;
}
if ("" == $Host) {
$Host = $this->Host;
}
if ("" == $User) {
$User = $this->User;
}
if ("" == $Password) {
$Password = $this->Password;
}
/* 建立連接,選擇資料庫*/
if ( 0 == db_connect()) {
$this->Link_ID = db_connect($Host, $User, $Password);
if (!$this->Link_ID) {
$this->halt("pconnect($Host, $User, $Password) failed.");
return 0;
}
}
if (0 != db_connect()) {
if($Database != db_database()) {
$this->Database = db_database($Database))
if(empty($this->Database)) {
$this->halt("cannot use database " . $this->Database);
return 0;
}
}
}
return $this->Link_ID;
}
? >
留意以下改變:
對資料庫的測試從連接的測試中分離出來,這樣即使connect()有一個當前連接時,還可以檢查是否要換成另外的資料庫。這意味著與以前相比,db_connect()和0作比較的次數多了一倍,不過這個額外的處理是必要的。
我們將資料庫連接和資料庫選擇放在PHPLIB外,這樣你就可以在PHP程式碼的任何地方使用相同的資料庫選擇函數。
不過,現在的處理有一個限制,這裡我們是假定對於所有的資料庫,都使用同樣的主機、使用者和密碼。如果你的資料庫對於不同的使用者有不同的權限,你必須建立一個特別的連線來存取它。怎樣做?只要定義以下變數就可以了:
<? php
$db_host = "";
$db_user = "";
$db_pass = "";
? >
透過擴充db_database()函數,將目前的使用者和主機和某個使用者和主機作對比就行。你還可以加入:
<? php
$db_type = "";
? >
這個變數用來儲存資料庫的類型,mysql或Oracle等。這樣你就可以存取多個資料庫。
不過要改變程式碼來處理多個不同類型的資料庫是相當複雜的。你必須也要改變查詢函數,以及連接和選擇函數。你或許可透過PHP的ODBC來連接,然後使用PHPLIB的ODBC選項來處理。 ODBC透過一個通用的方式來處理多種資料庫,因此將會慢一點。 ODBC雖然可讓你使用同樣的程式碼來處理多個不同類型的資料庫。但是在需要用到不同處理格式的日期時,將會有問題,資料庫間也會有一些奇怪的差異。 ODBC只是簡化了連接,但並沒有修改資料庫解釋資料和SQL的方式。
現在來學習如何重新定義一個物件類別。 connect()函數被封裝到一個類別的定義中:
<? php
class DB_Sql {
}
? >
我們將該函數拷貝到common.php時,我們必須重新定義DB_Sql類,我們可以這樣封裝connect():
<? php
class db_DB_Sql extends DB_Sql {
}
? >
要詳細了解"extends"的工作,我們可以看看PHP文件中關於物件和類別的部分。簡單說來:擴展部分的任何定義替換和覆蓋了以前的所有定義。
現在可以使用db_DB_Sql。在你配置PHPLIB時,你要做以下聲明:
<? php
$x = new DB_Sql;
? > Change it to: <? php
$x = new db_DB_Sql;
? >
這樣你就可以使用修改的類,而不是以前的類。
在連接資料庫出錯的時候,你可以在外部的函數中輸出目前的連線狀態。如果SQL語句出錯,你也可以將DB_Sql中的query()函式拷貝到common.PHP的db_DB_Sql中,然後插入一個輸出語句,看看目前的SQL語句是什麼。
你也可以將錯誤或診斷的資訊寫到一個磁碟檔案中。透過定義
$db_log_file = "t:/diag.txt";
或一個類似的文字檔。如果使用Windows,你要確保該目錄存在,否則你會得到一個錯誤的訊息。
然後定義一個函數:
<? php
function db_log($db_log_message) {
globals $db_log_file;
$db_log_f = fopen($db_log_file, "a");
fwrite($db_log_f, date("Y md H:i:s")." ".$db_log_message."rn");
fclose($db_log_f);
}
? >
在你需要記錄資訊的地方,加入以下程式碼:
<? php
db_log("current database: " . db_database());
? >
其實你可以使用內建的或是系統的日誌檔。不過這樣你就要在一大堆的文件中找一小段資訊。因此這個獨立的記錄文件可幫助你進行測試。我建議在記錄前後寫下以下的程式碼:
<? php
db_log("current database: " . db_database());
db_database("bookcatalogue");
db_log("current database: " . db_database());
? >
在資料存取時,要記得使用正確的資料庫,而不是PHPLIB中定義的資料庫。你可以為該資料庫建立一個封裝的函數,或是改變你使用的函數。如果你使用mysql_query(),你可以先用db_database(),你可以用
<? php
$result = mysql_db_query(db_database("bookcatalogue"), "select * from?",
db_connect());
? > which suggests the function: <? php
function db_query($db_query_database, $db_query_sql) {
return(mysql_db_query(db_database($db_query_database), $db_query_sql,
db_connect());
}
? >
來代替
<? php
db_database("bookcatalogue");
$result = mysql_query("select * from?", db_connect());
? >
現在你可以做到
.使用PHPLIB(或類似的軟體)來存取多個資料庫
.擴展類別/對象
.插入診斷檢查
.建立日誌文件