使用PHPLIB訪問(wèn)多個(gè)數(shù)據(jù)庫(kù)
PHPLIB是PHP的一些擴(kuò)展庫(kù),使用它我們可以很方便地對(duì)數(shù)據(jù)庫(kù)進(jìn)行各種操作,不過(guò),如果你要使用多個(gè)數(shù)據(jù)庫(kù)的話(huà),它就顯得力不從心了,本文介紹了通過(guò)擴(kuò)展PHPLIB,讓你魚(yú)和熊掌兼得,在使用PHPLIB的同時(shí)可以使用多個(gè)數(shù)據(jù)庫(kù),而且從中你也可以了解到面向?qū)ο缶幊毯腿绾螖U(kuò)展庫(kù)的知識(shí),值得一讀。
數(shù)據(jù)庫(kù)管理
你可以在一個(gè)大型的數(shù)據(jù)庫(kù)中放入任何表。不過(guò)時(shí)間長(zhǎng)了,將會(huì)令數(shù)據(jù)庫(kù)變得越來(lái)越大,服務(wù)器可能會(huì)跟不上IO的工作,或者沒(méi)有足夠的內(nèi)存應(yīng)付所有的訪問(wèn)?要分開(kāi)現(xiàn)有的數(shù)據(jù)又非常難。明智的辦法是開(kāi)始時(shí)就使用分開(kāi)的數(shù)據(jù)庫(kù),并且進(jìn)行有效的數(shù)據(jù)庫(kù)管理。 如果你有一個(gè)賣(mài)書(shū)的網(wǎng)站,你可能有作者的列表,書(shū)價(jià)的列表,還有當(dāng)前的庫(kù)存和訂單的列表。當(dāng)你的業(yè)務(wù)不斷增長(zhǎng)時(shí),訂單將會(huì)不斷地增長(zhǎng),而且處理每個(gè)訂單都需要進(jìn)行很多的磁盤(pán)訪問(wèn)。很可能你將在某一天將所有的訂單都放到一個(gè)會(huì)計(jì)系統(tǒng)中。
現(xiàn)在就將訂單放到一個(gè)獨(dú)立的數(shù)據(jù)庫(kù)吧。由于庫(kù)存也是通過(guò)訂單更新的,因此庫(kù)存量也放到同樣的數(shù)據(jù)庫(kù)中。
作者的列表和書(shū)的列表都是一些靜態(tài)的信息,要經(jīng)常讀取,但很少更新。實(shí)際上,更新一個(gè)作者的記錄可能只需要每5年一次,只在作者寫(xiě)了一本新書(shū)(或者去世)時(shí)進(jìn)行。放這些數(shù)據(jù)的服務(wù)器的配置可與放訂單數(shù)據(jù)庫(kù)的服務(wù)器完全不同。
包含PHPLIB
PHPLIB通過(guò)一個(gè)稱(chēng)為DB_Sql的類(lèi)訪問(wèn)SQL數(shù)據(jù)庫(kù)。根據(jù)你需要使用的數(shù)據(jù)庫(kù)類(lèi)型,將不同的inc文件包含在你的代碼中。在這個(gè)例子中,我使用MySQL的版本。
為了在你的代碼中使用DB_Sql,要將PHPLIB文件安裝在它們自己的目錄中。然后,找到你的cgi-bin目錄,并且在cgi-bin的目錄旁創(chuàng)建phplib目錄。下一步,拷貝所有的PHPLIB .inc文件到phplib目錄。最后,修改php.inc文件,只要將“include_path=”的行改為該phplib目錄就可以了。
include_path是PHP使用include()或者require()時(shí)查找的目錄,在我的NT workstation中,include的路徑是:
include_path = '.;i:/project52/includes;i:/project52/phplib';
在Linux的系統(tǒng)上
include_path = '.;/home/httpd/includes;/home/httpd/phplib';
在每個(gè)PHP頁(yè)面的頂部加入<?phprequire(common.php);?>common.php3放在includes目錄中,包含了每個(gè)頁(yè)面要用到的所有數(shù)據(jù)和函數(shù)。在這個(gè)例子中的common.php是:<?phprequire(db_mysql.inc);require(ct_sql.inc);require(session.inc);require(auth.inc);require(perm.inc);require(user.inc);require(page.inc);?>
如果你想知道每個(gè)inc文件的用處,可閱讀http://phplib.netuse.de上的PHPLIB文檔。Db_mysql.inc包含了所有DB_SQL類(lèi)的定義。如果你想使用PostGreSQL代替MySQL,只要用db_pgsql.inc代替db_mysql.inc就可以了。還有10個(gè)其它的.inc文件,可以使用MS SQL、Oracle、Sybase或者其它的數(shù)據(jù)庫(kù)。
要注意的是,在這個(gè)例子中,require()和include()是完全一樣的。不過(guò),如果放在代碼中,或者在if語(yǔ)句中使用時(shí),Require()和include的使用是完全不同的,并且有不同的運(yùn)行結(jié)果。
擴(kuò)展PHPLIB
PHPLIB通過(guò)一個(gè)DB_Sql類(lèi)產(chǎn)生的對(duì)象來(lái)訪問(wèn)數(shù)據(jù)庫(kù)。Db_mysql.inc包含了為MySQL修改過(guò)的DB_Sql類(lèi)。我們將通過(guò)在common.php中加入代碼來(lái)擴(kuò)展DB_sql,這些代碼將加在包含db_mysql.inc的行后。
DB_Sql包含了很多用作查詢(xún)的函數(shù),我們要作修改的是:
<?php/* public: 連接管理 */function connect($Database = '', $Host = '', $User = '', $Password = '') {/* 處理默認(rèn)連接 */if ('' == $Database)$Database = $this->Database;if ('' == $Host)$Host = $this->Host;if ('' == $User)$User = $this->User;if ('' == $Password)$Password = $this->Password;
/* 建立連接,選擇數(shù)據(jù)庫(kù) */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(或者其它數(shù)據(jù)庫(kù)的相關(guān).inc文件)中找到connect()函數(shù),然后將它拷貝到common.php,放到包含db_mysql.inc代碼的后面,在后面,還要將它封裝為一個(gè)類(lèi)的定義。
我發(fā)現(xiàn)這些代碼有些難讀,因此,首先令拷貝來(lái)的代碼的可讀性更好:
<?php/* public: 連接管理*/function connect($Database = '', $Host = '', $User = '', $Password = '') {/* 處理默認(rèn)連接 */if ('' == $Database) {$Database = $this->Database;}if ('' == $Host) {$Host = $this->Host;}if ('' == $User) {$User = $this->User;}if ('' == $Password) {$Password = $this->Password;}/* 建立連接,選擇數(shù)據(jù)庫(kù) */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;}?>
我調(diào)整了一下括號(hào)的位置,并且在單行的前后也加入了一個(gè)大括號(hào)。在PHP的if語(yǔ)句中,如果只有一句代碼的話(huà)你可以不用括號(hào),但是,如果你增加多一行代碼,就會(huì)馬上出錯(cuò)。因此我建議你加入一個(gè)括號(hào),以免后來(lái)加入代碼時(shí)出錯(cuò)。
在改變connect的代碼之前,先要了解一下connect()是如何工作的,它檢查當(dāng)前是否存在一個(gè)連接,如果不存在連接的話(huà),就創(chuàng)建一個(gè)連接。在每次的數(shù)據(jù)庫(kù)查詢(xún)之前,首先運(yùn)行這個(gè)connect()函數(shù)。可惜的是,它只在首次連接的時(shí)候選擇數(shù)據(jù)庫(kù),如果你的PHP頁(yè)面使用超過(guò)一個(gè)數(shù)據(jù)庫(kù),connect()并不會(huì)選擇另外的數(shù)據(jù)庫(kù)。
要改變代碼的話(huà),有幾種不同的方法。我們要選擇一種對(duì)PHPLIB的影響最小,而且可讓我們?cè)谛枰治鰡?wèn)題的時(shí)候,能夠顯示數(shù)據(jù)庫(kù)連接狀態(tài)的方法。我們需要在PHPLIB外保存連接id和數(shù)據(jù)庫(kù)的名字。只要在common.php加入:
<?php$db_connection = 0; // 數(shù)據(jù)庫(kù)連接的id$db_database = ''; // 當(dāng)前數(shù)據(jù)庫(kù)的狀態(tài)?>
下一步,我們要對(duì)PHPLIB作修改,以便在這些變量中存儲(chǔ)連接id和數(shù)據(jù)庫(kù)的名字。在其它的代碼中,你可以設(shè)置和使用同樣的變量名。在分析問(wèn)題時(shí),如果你需要知道現(xiàn)在使用哪個(gè)數(shù)據(jù)庫(kù),只要在頁(yè)面中插入以下的代碼:
<?phpPrint(' db_database: ' . $db_database . '');?>
我們?cè)鯓硬拍茏宑onnect()使用這些新變量呢?我們可以在頂部加入一行:
<?php{globals $db_connect, $db_database;/* Handle defaults */?>
通過(guò)這些代碼,新變量就可被connect()訪問(wèn)到
在定義了$db_database后,加入:<?phpfunction 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);}?>
只要定義這些公共的函數(shù)一次,你就可以在不同的地方使用這些公共的變量,而不需要加入global申明。以下就是使用上面db函數(shù)的公共函數(shù):
<?phpfunction connect($Database = '', $Host = '', $User = '', $Password = '') {/* 處理默認(rèn)連接 */if ('' == $Database) {$Database = $this->Database;}if ('' == $Host) {$Host = $this->Host;}if ('' == $User) {$User = $this->User;}if ('' == $Password) {$Password = $this->Password;}/* 建立連接,選擇數(shù)據(jù)庫(kù) */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;}?>
留意以下改變:
對(duì)數(shù)據(jù)庫(kù)的測(cè)試從連接的測(cè)試中分離出來(lái),這樣即使connect()有一個(gè)當(dāng)前連接時(shí),還可以檢查是否要換成另外的數(shù)據(jù)庫(kù)。這意味著與以前相比,db_connect()和0作比較的次數(shù)多了一倍,不過(guò)這個(gè)額外的處理是必要的。我們將數(shù)據(jù)庫(kù)連接和數(shù)據(jù)庫(kù)選擇放在PHPLIB外,這樣你就可以在PHP代碼的任何地方使用同樣的數(shù)據(jù)庫(kù)選擇函數(shù)。不過(guò),現(xiàn)在的處理有一個(gè)限制,這里我們是假定對(duì)于所有的數(shù)據(jù)庫(kù),都使用同樣的主機(jī)、用戶(hù)和密碼。如果你的數(shù)據(jù)庫(kù)對(duì)于不同的用戶(hù)有不同的權(quán)限,你必須建立一個(gè)特別的連接來(lái)訪問(wèn)它。怎樣做?只要定義以下變量就可以了:
<?php$db_host = '';$db_user = '';$db_pass = '';?>
通過(guò)擴(kuò)展db_database()函數(shù),將當(dāng)前的用戶(hù)和主機(jī)和某個(gè)用戶(hù)和主機(jī)作對(duì)比就行。你還可以加入:
<?php$db_type = '';?>
這個(gè)變量用來(lái)存儲(chǔ)數(shù)據(jù)庫(kù)的類(lèi)型,mysql或者Oracle等。這樣你就可以訪問(wèn)多個(gè)數(shù)據(jù)庫(kù)。
不過(guò)要改變代碼來(lái)處理多個(gè)不同類(lèi)型的數(shù)據(jù)庫(kù)是頗復(fù)雜的。你必須還要改變查詢(xún)函數(shù),以及連接和選擇函數(shù)。你或許可通過(guò)PHP的ODBC來(lái)連接,然后使用PHPLIB的ODBC選項(xiàng)來(lái)處理。ODBC通過(guò)一個(gè)通用的方式來(lái)處理多種數(shù)據(jù)庫(kù),因此將會(huì)慢一點(diǎn)。ODBC雖然可讓你使用同樣的代碼來(lái)處理多個(gè)不同類(lèi)型的數(shù)據(jù)庫(kù)。但是在需要用到不同處理格式的日期時(shí),將會(huì)有問(wèn)題,而且在數(shù)據(jù)庫(kù)間也會(huì)存在一些奇怪的差異。ODBC只是簡(jiǎn)化了連接,但是并沒(méi)有修改數(shù)據(jù)庫(kù)解釋數(shù)據(jù)和SQL的方式。
現(xiàn)在來(lái)學(xué)習(xí)一下如何重新定義一個(gè)對(duì)象類(lèi)。connect()函數(shù)被封裝到一個(gè)類(lèi)的定義中:<?phpclass DB_Sql {}?>
我們將該函數(shù)拷貝到common.php時(shí),我們必須重新定義DB_Sql類(lèi),我們可以這樣封裝connect():<?phpclass db_DB_Sql extends DB_Sql {}?>
要詳細(xì)了解'extends'的工作,我們可以看看PHP文檔中關(guān)于對(duì)象和類(lèi)的部分。簡(jiǎn)單說(shuō)來(lái):擴(kuò)展部分的任何定義替換和覆蓋了以前的所有定義。
現(xiàn)在可以使用db_DB_Sql。在你配置PHPLIB時(shí),你要做以下聲明:
<?php$x = new DB_Sql;?> Change it to: <?php$x = new db_DB_Sql;?>
這樣你就可以使用修改的類(lèi),而不是以前的類(lèi)。
在連接數(shù)據(jù)庫(kù)出錯(cuò)的時(shí)候,你可以在外部的函數(shù)中輸出目前的連接狀態(tài)。如果SQL語(yǔ)句出錯(cuò),你也可以將DB_Sql中的query()函數(shù)拷貝到common.PHP的db_DB_Sql中,然后插入一個(gè)輸出語(yǔ)句,看看當(dāng)前的SQL語(yǔ)句是什么。
你也可以將錯(cuò)誤或者診斷的信息寫(xiě)到一個(gè)磁盤(pán)文件中。通過(guò)定義
$db_log_file = 't:/diag.txt';
或者一個(gè)類(lèi)似的文本文件。如果使用Windows,你要確保該目錄存在,否則你會(huì)得到一個(gè)錯(cuò)誤的信息。
然后定義一個(gè)函數(shù):
<?phpfunction db_log($db_log_message) {globals $db_log_file;$db_log_f = fopen($db_log_file, 'a');fwrite($db_log_f, date('Y m d H:i:s').' '.$db_log_message.'rn');fclose($db_log_f);}?>
在你需要記錄信息的地方,加入以下代碼:
<?phpdb_log('current database: ' . db_database());?>
其實(shí)你可以使用內(nèi)置的或者系統(tǒng)的日志文件。不過(guò)這樣你就要在一大堆的文件中查找一小段信息。因此這個(gè)獨(dú)立的記錄文件可幫助你進(jìn)行測(cè)試。我建議在記錄前后寫(xiě)以下的代碼:
<?phpdb_log('current database: ' . db_database());db_database('bookcatalogue');db_log('current database: ' . db_database());?>
在數(shù)據(jù)訪問(wèn)時(shí),要記得使用正確的數(shù)據(jù)庫(kù),而不是PHPLIB中定義的數(shù)據(jù)庫(kù)。你可以為該數(shù)據(jù)庫(kù)創(chuàng)建一個(gè)封裝的函數(shù),或者改變你使用的函數(shù)。如果你使用mysql_query(),你可以先用db_database(),你可以用
<?php$result = mysql_db_query(db_database('bookcatalogue'), 'select * from?',db_connect());?> which suggests the function: <?phpfunction db_query($db_query_database, $db_query_sql) {return(mysql_db_query(db_database($db_query_database), $db_query_sql,db_connect());}?>
來(lái)代替
<?phpdb_database('bookcatalogue');$result = mysql_query('select * from?', db_connect());?>
現(xiàn)在你可以做到
.使用PHPLIB(或者類(lèi)似的軟件)來(lái)訪問(wèn)多個(gè)數(shù)據(jù)庫(kù).擴(kuò)展類(lèi)/對(duì)象.插入診斷檢查.建立日志文件
