在PHP 4中,宣告變數通常使用var,而在PHP 5中,可使用物件導向程式設計(OOP)的特性來自訂資料的可見性--即可存取性,可見性在此與變數作用域非常類似,但提供了更好的控制機制,有以下三種類型的可見性修飾符:
Public(預設)--變數可在全域範圍內存取或修改。
Protected--變數只能在類別本身及直接衍生(使用extends語句)類別內存取或修改。
Private--變數只能在類別內部存取或修改。
與介面實作類似,在程式中違反這些規則將會導致嚴重的錯誤;且與介面類似的是,它們的存在純粹是為了方便程式設計師。但這並不意味著可以忽略它們,指定某個類別成員變數的可見性,可保護物件內的資料免受外界影響。
假設有一個MySqlDB類,一個$link變數在其中宣告為private,這意味著這個變數只能從物件內部使用$this變數訪問,這防止了類外其他物件或函數的意外覆蓋,在此,我們將使用可見性特性幫助我們建立一個query物件。
你可以把query當作一個單獨的實體,它可以執行,並且回傳結果。有些資料庫系統也具有預存程序,預存程序與函數很相似,它們儲存查詢語句,並在呼叫時接受對應的參數,但MySQL在5.1版本之前並沒有提供類似功能,某些其他類型的資料庫管理系統也沒有。
在本文中,將把上述兩個特性結合進範例的query物件中,範例將模擬一個基本的儲存過程,並在內部保存結果指標。目前,重點是從物件執行query,在此可以呼叫MySqlDB物件的query()函數。
可在query物件中定義如下的public函數:
__construct()--建構子接受一個包含了實作DB介面物件實例參考的參數。
prepare()--函數prepare()初始化query的預存程序。它可能包含一個或多個有限的佔位符,而其將會作為參數傳遞給execute()函數。佔位符定義為與參數個數有關的一個冒號緊接在一個整數及與參數類型有關的一個字母。
包含佔位符的一個簡單的query看起來像以下:
SELECT col1,col2 FROM table_name WHERE col1=:1I
execute()--函數execute()將執行 query。如果它被prepare()函數過早初始化為一個預存過程,任何傳遞進來的參數都會被當作預存程序的執行參數,否則,第一個參數只會被當作查詢文字。函數execute()將傳回執行查詢後的結果。
compile()--函數compile()與函數execute()類似,實際上,query並沒有執行,而是取代查詢字串中所有佔位符,接受預存程序的參數,並傳回query的編譯版本。
受保護的成員
如同上面所提到的,可見性的概念可用於隱藏物件的內部工作,保護內部工作所需的資料完整性。前面已經解釋,query傳回的結果指標將會儲存為protected屬性,在此使用保護成員是因為從query物件派生出來的特定資料庫query物件可能會重載某些核心功能。
深掘程式碼
理論說夠了,現在開始寫程式碼,首先,建立一個範例1所示的範本:
例1:資料庫query類別的一個範本
class DBQuery
{
/**
*保存一個實作了DB介面物件的參考。
*/
protected $db;
/**
*如果是一個預存過程,設為true。
*/
protected $stored_procedure = false;
/**
*保存一個刪除了所有字串的query。
*/
private $query;
/**
*用於在SQL中匹配引號。
*/
private static $QUOTE_MATCH = "/(".*(?db = $db;
}
public function prepare($query)
{
$this->stored_procedure = true;
}
public function compile($args)
{}
public function execute($query)
{}
}
函數prepare
為使用例1中的模板,你要做的第一件事是建立好prepare()函數,為確保無帶引號的字符被偶然解析為佔位符,函數應該移除query內所有字符串,並把它們暫時存放在一個數組內。而字串本身也會被佔位符取代,其通常被辨識為不應該在SQL語句中出現的字串序列。在query的編譯期間,過程佔位符會先被替換,接著把字串放回query中,這是透過preg_replace()函數,和另一個用作preg_replace()函數的helper回呼函數完成的。
例2:prepare()函數
/**
* 把query準備為一個預存程序。
* @param string $query Prepared query text
* @return void
*/
public function prepare($query)
{
$this->stored_procedure = true;
$this->quote_store = array(); //清除引號$this->query = preg_replace(self::$QUOTE_MATCH, '$this->sql_quote_replace("1"?"1":'2')', $ query);
}
private function sql_quote_replace($match)
{
$number = count($this->query_strings);
$this->query_strings[] = $match;
return "$||$$number";
}
在此留意對靜態QUOTE_MATCH屬性private的使用,還有quote_store屬性和sql_quote_replace()函數。相較於protected,在此定義為private更能確保任何重載query類別prepare()方法的子類別使用其自身的機制來剔除引號。
函數compile
下一步是建構compile()與execute()函數。
函數compile()如例3所示,功能如下:
·接受的參數數目可變(即可變參數),其將符合query中的佔位符。
·檢查佔位符是否為正確的資料類型,並把它替換為參數中的值。
·把query當作字串傳回,但不執行它。
·如果query物件沒有使用prepare()函數初始化為一個預存過程,將會拋出一個例外。
例3:compile()函數
/**
* 返回編譯的query,但不執行它。
* @param mixed $args,... Query Parameters
* @return string Compiled Query
*/
public function compile($params)
{
if (! $this->stored_procedure) {
throw new Exception("預存程序未被初始化!");
}
/* 替代參數*/
$params = func_get_args(); // 取得函數參數$query = preg_replace("/(?query);
return $this->add_strings($query); //把字串放回query中
}
/**
* 重新插入被prepare()函數移除的字串。
*/
private function add_strings($string)
{
$numbers = array_keys($this->query_strings);
$count = count($numbers);
$searches = array();
for($x = 0; $x < $count; $x++) {
$searches[$x] = "$||${$numbers[$x]}";
}
return str_replace($searches, $this->query_strings, $string);
}
/**
* 每次執行,儲存過程中都有一個佔位符被取代。
*/
protected function compile_callback($params, $index, $type)
{
--$index;
/* 拋出一個例外*/
if (! isset($params[$index])) {
throw new Exception("預存程序未收到所需的參數數目!");
}
/* 可以在此新增別的類型,如日期和時間。 */
switch ($type) {
case 'S':
return '"' . $this->db->escape_string($params[$index]) . '"';
break;
case 'I':
return (int) $params[$index];
break;
case 'N':
return (float) $params[$index];
default:
throw new Exception("在預存過程中指定的資料型別'$type' 無法辨識。");
}
}
函數compile()中使用了兩個額外的函數,其中compile_callback()函數是作為在preg_replace()函數呼叫中的回調函數,每一次在query中查找到佔位符,並把它替換為傳給compile函數的值時,都會執行它。
函數execute
最後,還需要建構函數execute(),函數execute()編譯query並且使用DB物件執行它,而DB物件在此是用於初始化DBQuery物件的。請注意在例4中,是如何運用函數call_user_func_array()來得到編譯後的query的,而這樣做的原因是,函數execute()要直到執行時,才能確定傳遞給它的參數數目。
例4:execute()函數
/**
*
* 執行目前query,並把佔位符替換為所提供的參數。
*
* @param mixed $queryParams,... Query parameter
* @return resource A reference to the resource representing the executed query.
*/
public function execute($queryParams = '')
{
//例如:SELECT * FROM table WHERE name=:1S AND type=:2I AND level=:3N
$args = func_get_args();
if ($this->stored_procedure) {
/* 呼叫函數compile以獲得query */
$query = call_user_func_array(array($this, 'compile'), $args);
} else {
/* 如果預存程序未初始化,就把它當作標準query執行。 */
$query = $queryParams;
}
$this->result = $this->db->query($query);
return $this->result;
}
全部整合起來
為演示怎樣使用query對象,下面構造了一個小例子,其將把DBQuery對像作為存儲過程使用,並檢查是否輸入了正確的用戶名與密碼,請看例5:
例5:
require ' mysql_db.php5';
require_once 'query2.php5';
$db = new MySqlDb;
$db->connect('host', 'username', 'pass');
$db->query('use content_management_system');
$query = new DBQuery($db);
$query->prepare('SELECT fname,sname FROM users WHERE username=:1S AND pword=:2S AND expire_time<:3I ');
if ($result = $query->execute("visualad", "apron", time())) {
if ($db->num_rows($result) == 1) {
echo('憑證正確。');
} else {
echo('憑證不正確,會話已過期。');
}
} else {
echo('執行query時發生錯誤:' . $db->error());
}
在本文中,你已經看到如何在宣告類別變數時,利用存取修飾符private、protected和public,保護資料和限制資料物件的可見性,同時,在PHP 5中,這些概念也可用於其他的資料類,保護其重要的內部資料。