我們都知道如何從Mysql獲取我們需要的行(記錄),讀取數據,然後訪問一些改動。很明顯也很直接,過程背後也沒有什麼拐彎抹角的。然而對於我們使用面對物件的程式設計(OOP)來管理我們資料庫中的資料時,這個過程就需要大幅改進一下了。這篇文章將對如何設計一個面對物件的方式來管理資料庫的記錄做一個簡單的描述。你的數據當中的所有內部邏輯關係將被封裝到一個非常條理的記錄對象,這個對象能夠提供專門(專一)的確認代碼系統,轉換以及數據處理。隨著Zend Engine2 和PHP5的發布,PHP開發者將擁有更強大的面對物件的工具來輔助工作,這將使這個過程(面對物件地管理資料庫)更有吸引力。
以下列出了一些使用物件來描敘你的資料庫的有利方面:
存取方法(Accessor methods)將會使你對屬性的讀取和寫入過程做到完全的控制
每一級的每個記錄和屬性(的操作)都有確認過程
從關係表中智能的獲取對象
重複使用的邏輯方法意味著所有的資料互動都要通過相同的基礎程式碼(codebase),這將使維護變得更加簡單
程式碼簡單,因為不同的記錄的內部邏輯都已經包含在各自所處的類別(class)當中,而不是繁瑣的函式庫(lib)檔案
在手工編寫程式碼和SQL查詢語句時,出錯的機會將更少
存取方法(Accessor methods)
存取方式是透過類別給實例(instance)的變數賦值。一個例子,我有一個叫User的類,並且有一個實例$username,我會寫這樣的存取方法(函數),User->username()和User->setUsername()用來傳回和給實例賦值。
class User {
var $username;
function username() {
return $this->username; 🎜>}
function setUsername($newUsername) {
$this->username = $newUsername;
}
}
?>
這裡有很好的理由讓我們寫這樣的「特別的程式碼」。它將使開發者更靈活的改變類別的繁瑣的工作,因為這個過程將不需要其他的使用類別的php程式碼。讓我們來看看下面這個更加完善的可信賴的User類別。
變數$username將不復存在,所有的東西都被整合的放在數組$_data當中
如果username是空的話,username()函數將提供一個缺省(默認)的值給它
setUsername()過程將在接受值之前確認username是否合乎標準格式(如字長等)
class User {
var $_data = array(); // associative array containing all the attributes for the User
function username() {
return !empty($this->_data['username']) ? $this-> _data['username'] : '(no name!)';
}
function setUsername($newUsername) {
if ($this->validateUsername($newUsername)) {
$this->_data['username'] = $newUsername;
}
}
function validateUsername(&$someName) {
if (strlen($someName) > 12) {
throw new Exception('Your username is too long'); // PHP5 only
}
return true;
}
}
?>
}
?>
?>
顯而易見,這對我們控制存取物件的資料有很大幫助。如果一個程式設計師已經直接存取username的訊息,以上程式碼的變更將會破壞他的程式碼。然而我們可以使用(類別的)訪問方法,就像上面程式碼中註釋的那樣,添加一個驗證的功能而不需要改變任何其他的東西。注意username的驗證(範例當中是不能超過12位元組)程式碼是獨立在setUsername()方法之外的。從驗證到儲存到資料庫的過程輕而易舉。而且,這是一個非常好的單憑經驗的方法,一個方法或一個類別需要做的越少,它的重複使用的機會將會越大。這在你開始寫一個子類時更加明顯,假如你需要一個子類,並且又要跳過(忽略)父類方法(行為)中的一些特殊的細節,如果(針對這個細節的)方法很小而又精細,(修改它)只是一瞬間的過程,而如果這個方法非常臃腫,針對多種目的,你可能將在複製子類中大量代碼中鬱悶而終。
比方說,假如Admin是User類別的子類別。我們對adamin的用戶可能會有不同的,相對苛刻一些的密碼驗證方法。最好是跨過父類別的驗證方法和整個setUsername()方法(在子類別中重寫)。
更多關於存取器(Accessor)
以下是一些其他的例子來說明如何讓存取器用的更有效果。很多時候我們可能要計算結果,而不是簡單的回傳數組中的靜態資料。存取方法還能做的一個有用的事情就是更新(updating)快取中的值。當所有的變動(對資料的所有操作)都要通過setX()方法的時候,這正是我們根據X來重置快取中的值的時刻。 於是我們的這個類別層次變得更加明了:內部變數$_data的處理被替換成受保護的私有方法(private methods)_getData()和_setData() 這類方法被轉移到被稱作記錄(Record)的抽象的超級類別(super class),當然它是User類別下的子類別這個記錄類別(Record class)掌握所有存取陣列$ _data的細節,在內容被修改之前呼叫驗證的方法,以及將變更的通知發給記錄(Records),就像發給中心物件儲存(ObjectStore)實例。
class User extends Record {
// --- 省略程式碼--- //
/**
* 不顯示使用者的實際密碼,僅顯示一些與密碼值相同 strlen 的星號。
*/
函數password() {
$passLength = strlen($this->_getData('password'));
return str_repeat('*', $passLength);
}
/* *
* 設定使用者密碼不受影響。
*/
function setPassword($newPassword) {
$this->_setData('password', $newPassword);
}
/**
* fullName 是從firstName 和lastName
* 派生的屬性,不需要儲存為變數。
* 因此它是唯讀的,並且沒有「setFullname()」存取器方法。
* /
function fullName() {
return $this->firstName() . 」 「。 $this->lastName();
}
/**
* 支出限額回傳用戶支出限額的金錢價值。
* 該值作為 INT 儲存在資料庫中,無需
* 更昂貴的 DECIMAL 或 DOUBLE 列類型。
*/
function SpendingLimit() {
return $this->_getData('spendingLimit') / 100 ;
}
/**
* set 存取器將貨幣值乘以 100,因此它可以再次作為 INT 值儲存在資料庫中
*。
*/
function setSpendingLimit($newSpendLimit) {
$this->_setData('spendingLimit', $newSpendLimit * 100); >}
/**
* validateSpendingLimit 不在此類中調用,而是由 Record 超類中的 _setData() 方法自動調用
* ,而 Record 超類別又由 setSpendingLimit() 方法調用。
*/
function validateSpendingLimit(&$someLimit) {
if (is_numeric($someLimit) AND $someLimit >= 0) {
回真🎜 >回真;
} else {
throw new Exception("支出限額必須為非負整數"); //僅PHP5
}
}
}
/**
* Record 是所有資料庫物件的超類別。
*/
抽象類別Record {
var $_data = array();
var $_modifiedKeys = array(); // 追蹤自建立/取得記錄以來哪些欄位已變更
/**
* 傳回 $_data 關聯數組中的一個元素。
*/
function _getData($attributeName) {
return $this->_data[ $屬性名稱];
}
/**
* 如果提供的值通過驗證,則此
* 設定 $_data 關聯數組中的值。
*/
function _setData($attributeName, $value) {
if ($this->validateAttribute($attributeName, $value)) {
if ($value != $this->_data[ $attributeName]) {
$this->_data[$attributeName] = $value;
$this->_modifiedKeys[] = $attributeName;
$this->didChange();
} else {
// 新值與目前值相同
// 不需要更改
}
}
}
/**
* 對於名為「foo」的屬性,它將尋找名為「validateFoo()」的方法
* 並呼叫它(如果存在)。否則傳回 true(表示驗證通過)。
*/
function validateAttribute($attributeName, &$value) {
$methodName = 'validate' . $屬性名稱;
if (method_exists($this, $methodName)) {
return $this->$methodName( $value);
} else {
回傳true;
}
}
function didChange() {
//通知objectStore這條記錄發生了變化
}
}
?>
現在我們擁有了一個抽象的超級類別(Record),我們可以將User類別裡面大量的程式碼轉移出來,而讓這個User的子類別來關注User的特殊項目如存取和驗證方法。您可能已經注意到在我們的這個記錄類別(隱藏類別)中沒有任何 SQL 程式碼。這不是疏忽或遺漏!物件儲存類別(ObjectStore 類別)(在第二部分)將負責所有和資料庫的交互,以及我們的超級類別記錄的實例化。這樣使我們的記錄類別更加精簡有效率,而這對於評估我們處理大量物件的效率的時候是個重要因素。