PHP V5 新的面向?qū)ο缶幊烫匦燥@著提升了這個(gè)流行語(yǔ)言中的功能層次。學(xué)習(xí)如何用 PHP V5 動(dòng)態(tài)特性創(chuàng)建可以滿足需求的對(duì)象。
PHP V5 中新的面向?qū)ο缶幊蹋∣OP)特性的引入顯著提升了這個(gè)編程語(yǔ)言的功能層次。現(xiàn)在不僅有了私有的、受保護(hù)的和公共的成員變量和函數(shù) —— 就像在 Java?、 C++ 或 C# 編程語(yǔ)言中一樣 —— 但是還可以創(chuàng)建在運(yùn)行時(shí)變化的對(duì)象,即動(dòng)態(tài)地創(chuàng)建新方法和成員變量。而使用 Java、C++ 或 C# 語(yǔ)言是做不到這件事的。這種功能使得超級(jí)快速的應(yīng)用程序開發(fā)系統(tǒng)(例如 Ruby on Rails)成為可能。
但是,在進(jìn)入這些之前,有一點(diǎn)要注意:本文介紹 PHP V5 中非常高級(jí)的 OOP 特性的使用,但是這類特性不是在每個(gè)應(yīng)用程序中都需要的。而且,如果不具備 OOP 的堅(jiān)實(shí)基礎(chǔ)以及 PHP 對(duì)象語(yǔ)法的初步知識(shí),這類特性將會(huì)很難理解。
對(duì)象是把雙刃劍。一方面,對(duì)象是封裝數(shù)據(jù)和邏輯并創(chuàng)建更容易維護(hù)的系統(tǒng)的重大方式。但另一方面,它們會(huì)變得很繁瑣,需要許多冗余的代碼,這時(shí)可能最希望做到的就是不要犯錯(cuò)。這類問題的一個(gè)示例來(lái)自數(shù)據(jù)庫(kù)訪問對(duì)象。一般來(lái)說,想用一個(gè)類代表每個(gè)數(shù)據(jù)庫(kù)表,并執(zhí)行以下功能:對(duì)象從數(shù)據(jù)庫(kù)讀出數(shù)據(jù)行;允許更新字段,然后用新數(shù)據(jù)更新數(shù)據(jù)庫(kù)或刪除行。還有一種方法可以創(chuàng)建新的空對(duì)象,設(shè)置對(duì)象的字段,并把數(shù)據(jù)插入數(shù)據(jù)庫(kù)。
如果在數(shù)據(jù)庫(kù)中有一個(gè)表,名為 Customers,那么就應(yīng)當(dāng)有一個(gè)對(duì)象,名為 Customer
,它應(yīng)當(dāng)擁有來(lái)自表的字段,并代表一個(gè)客戶。而且 Customer
對(duì)象應(yīng)當(dāng)允許插入、更新或刪除數(shù)據(jù)庫(kù)中對(duì)應(yīng)的記錄。現(xiàn)在,一切都很好,而且有也很多意義。但是,有許多代碼要編寫。如果在數(shù)據(jù)庫(kù)中有 20 個(gè)表,就需要 20 個(gè)類。
有三個(gè)解決方案可以采用。第一個(gè)解決方案就是,坐在鍵盤前,老老實(shí)實(shí)地錄入一段時(shí)間。對(duì)于小項(xiàng)目來(lái)說,這還可以,但是我很懶。第二個(gè)解決方案是用代碼生成器,讀取數(shù)據(jù)庫(kù)模式,并自動(dòng)編寫代碼。這是個(gè)好主意,而且是另一篇文章的主題。第三個(gè)解決方案,也是我在本文中介紹的,是編寫一個(gè)類,在運(yùn)行時(shí)動(dòng)態(tài)地把自己塑造成指定表的字段。這個(gè)類執(zhí)行起來(lái)比起特定于表的類可能有點(diǎn)慢 —— 但是把我從編寫大量代碼中解脫出來(lái)。這個(gè)解決方案在項(xiàng)目開始的時(shí)候特別有用,因?yàn)檫@時(shí)表和字段不斷地變化,所以跟上迅速的變化是至關(guān)重要的。
所以,如何才能編寫一個(gè)能夠彎曲 的類呢?
![]() ![]() |
![]()
|
對(duì)象有兩個(gè)方面:成員變量 和方法。在編譯語(yǔ)言(例如 Java)中,如果想調(diào)用不存在的方法或引用不存在的成員變量,會(huì)得到編譯時(shí)錯(cuò)誤。但是,在非編譯語(yǔ)言,例如 PHP 中,會(huì)發(fā)生什么?
在 PHP 中的方法調(diào)用是這樣工作的。首先,PHP 解釋器在類上查找方法。如果方法存在,PHP 就調(diào)用它。如果沒有,那么就調(diào)用類上的魔法方法 __call
(如果這個(gè)方法存在的話)。如果 __call
失敗,就調(diào)用父類方法,依此類推。
![]() |
|
__call
方法有兩個(gè)參數(shù):被請(qǐng)求的方法的名稱和方法參數(shù)。如果創(chuàng)建的 __call
方法接受這兩個(gè)參數(shù),執(zhí)行某項(xiàng)功能,然后返回 TRUE,那么調(diào)用這個(gè)對(duì)象的代碼就永遠(yuǎn)不會(huì)知道在有代碼的方法和 __call
機(jī)制處理的方法之間的區(qū)別。通過這種方式,可以創(chuàng)建這樣的對(duì)象,即動(dòng)態(tài)地模擬擁有無(wú)數(shù)方法的情況。
除了 __call
方法,其他魔法方法 —— 包括 __get
和 __set
—— 調(diào)用它們的時(shí)候,都是因?yàn)橐昧瞬淮嬖诘膶?shí)例變量。腦子里有了這個(gè)概念之后,就可以開始編寫能夠適應(yīng)任何表的動(dòng)態(tài)數(shù)據(jù)庫(kù)訪問類了。
![]() ![]() |
![]()
|
先從一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)模式開始。清單 1 所示的模式針對(duì)的是單一的數(shù)據(jù)表數(shù)據(jù)庫(kù),容納圖書列表。
清單 1. MySQL 數(shù)據(jù)庫(kù)模式
|
請(qǐng)把這個(gè)模式裝入到名為 bookdb 的數(shù)據(jù)庫(kù)。
接下來(lái),編寫一個(gè)常規(guī)的數(shù)據(jù)庫(kù)類,然后再把它修改成動(dòng)態(tài)的。清單 2 顯示了圖書表的簡(jiǎn)單的數(shù)據(jù)庫(kù)訪問類。
清單 2. 基本的數(shù)據(jù)庫(kù)訪問客戶機(jī)
|
為了保持代碼簡(jiǎn)單,我把類和測(cè)試代碼放在一個(gè)文件中。文件首先得到數(shù)據(jù)庫(kù)句柄,句柄保存在一個(gè)全局變量中。然后定義 Book
類,用私有成員變量代表每個(gè)字段。還包含了一套用來(lái)從數(shù)據(jù)庫(kù)裝入、插入、更新和刪除行的方法。
底部的測(cè)試代碼先刪除數(shù)據(jù)庫(kù)中的所有條目。然后,代碼插入一本書,輸出新記錄的 ID。然后,代碼把這本書裝入另一個(gè)對(duì)象并輸出書名。
清單 3 顯示了在命令行上用 PHP 解釋器運(yùn)行代碼的效果。
清單 3. 在命令行運(yùn)行代碼
|
不需要看太多,就已經(jīng)得到重點(diǎn)了。Book
對(duì)象代表圖書數(shù)據(jù)表中的行。通過使用上面的字段和方法,可以創(chuàng)建新行、更新行和刪除行。
![]() ![]() |
![]()
|
下一步是讓類變得稍微動(dòng)態(tài)一些:動(dòng)態(tài)地為每個(gè)字段創(chuàng)建 get_
和 set_
方法。清單 4 顯示了更新后的代碼。
清單 4. 動(dòng)態(tài) get_ 和 set_ 方法
|
要做這個(gè)變化,需要做兩件事。首先,必須把字段從單個(gè)實(shí)例變量修改成字段和值組合構(gòu)成的散列表。然后必須添加一個(gè) __call
方法,它只查看方法名稱,看方法是 set_
還是 get_
方法,然后在散列表中設(shè)置適當(dāng)?shù)淖侄巍?/font>
注意,load
方法通過調(diào)用 set_title
、set_author
和 set_publisher
方法 —— 實(shí)際上都不存在 —— 來(lái)實(shí)際使用 __call
方法。
![]() ![]() |
![]()
|
刪除 get_
和 set_
方法只是一個(gè)起點(diǎn)。要?jiǎng)?chuàng)建完全動(dòng)態(tài)的數(shù)據(jù)庫(kù)對(duì)象,必須向類提供表和字段的名稱,還不能有硬編碼的引用。清單 5 顯示了這個(gè)變化。
清單 5. 完全動(dòng)態(tài)的數(shù)據(jù)庫(kù)對(duì)象類
|
在這里,把類的名稱從 Book
改成 DBObject
。然后,把構(gòu)造函數(shù)修改成接受表的名稱和表中字段的名稱。之后,大多數(shù)變化發(fā)生在類的方法中,過去使用一些硬編碼結(jié)構(gòu)化查詢語(yǔ)言(SQL),現(xiàn)在則必須用表和字段的名稱動(dòng)態(tài)地創(chuàng)建 SQL 字符串。
代碼的惟一假設(shè)就是只有一個(gè)主鍵字段,而且這個(gè)字段的名稱是表名加上 _id
。所以,在 book
表這個(gè)示例中,有一個(gè)主鍵字段叫做 book_id
。主鍵的命名標(biāo)準(zhǔn)可能不同;如果這樣,需要修改代碼以符合標(biāo)準(zhǔn)。
這個(gè)類比最初的 Book
類復(fù)雜得多。但是,從類的客戶的角度來(lái)看,這個(gè)類用起來(lái)仍很簡(jiǎn)單。也就是說,我認(rèn)為這個(gè)類能更簡(jiǎn)單。具體來(lái)說,我不愿意每次創(chuàng)建圖書的時(shí)候都要指定表和字段的名稱。如果我四處拷貝和粘貼這個(gè)代碼,然后修改了 book 表的字段結(jié)構(gòu),那么我可能就麻煩了。在清單 6 中,通過創(chuàng)建一個(gè)繼承自 DBObject
的簡(jiǎn)單 Book
類,我解決了這個(gè)問題。
清單 6. 新的 Book 類
|
現(xiàn)在,Book
類真的是簡(jiǎn)單了。而且 Book
類的客戶也不再需要知道表或字段的名稱了。
![]() ![]() |
![]()
|
對(duì)這個(gè)動(dòng)態(tài)類我想做的最后一個(gè)改進(jìn),是用成員變量訪問字段,而不是用笨重的 get_
和 set_
操作符。清單 7 顯示了如何用 __get
和 __set
魔法方法代替 __call
。
清單 7. 使用 __get 和 __set 方法
|
底部的測(cè)試代碼只演示了這個(gè)語(yǔ)法干凈了多少。要得到圖書的書名,只需得到 title
成員變量。這個(gè)變量會(huì)調(diào)用對(duì)象的 __get
方法,在散列表中查找 title
條目并返回。
現(xiàn)在就得到了單個(gè)動(dòng)態(tài)的數(shù)據(jù)庫(kù)訪問類,它能夠讓自己適應(yīng)到數(shù)據(jù)庫(kù)中的任何表。
![]() ![]() |
![]()
|
編寫動(dòng)態(tài)類不僅限于數(shù)據(jù)庫(kù)訪問。請(qǐng)看清單 8 中的 Customer
對(duì)象這個(gè)例子。
清單 8. 簡(jiǎn)單的 Customer 對(duì)象
|
這個(gè)對(duì)象足夠簡(jiǎn)單。但是如果我想在每次檢索或設(shè)置客戶名稱時(shí)都記錄日志,會(huì)發(fā)生什么呢?我可以把這個(gè)對(duì)象包裝在一個(gè)動(dòng)態(tài)日志對(duì)象內(nèi),這個(gè)對(duì)象看起來(lái)像 Customer
對(duì)象,但是會(huì)把 get
或 set
操作的通知發(fā)送給日志。清單 9 顯示了這類包裝器對(duì)象。
清單 9. 動(dòng)態(tài)包裝器對(duì)象
|
調(diào)用日志版本的 Customer
的代碼看起來(lái)與前面相同,但是這時(shí),對(duì) Customer
對(duì)象的任何訪問都被記入日志。清單 10 顯示了運(yùn)行這個(gè)日志版代碼時(shí)輸出的日志。
清單 10. 運(yùn)行日志版對(duì)象
|
在這里,日志輸出表明用參數(shù) Jack
調(diào)用了set_name
方法。然后,調(diào)用 get_name
方法。最后,測(cè)試代碼輸出 get_name
調(diào)用的結(jié)果。
![]() ![]() |
![]()
|
如果這個(gè)動(dòng)態(tài)對(duì)象素材對(duì)您來(lái)說理解起來(lái)有點(diǎn)難,我不會(huì)責(zé)備您。因?yàn)槲易约阂不瞬簧贂r(shí)間研究它并使用代碼才理解它并看出它的好處。
動(dòng)態(tài)對(duì)象有許多功能,但是也有相當(dāng)?shù)娘L(fēng)險(xiǎn)。首先,在剛開始編寫魔法方法時(shí),類的復(fù)雜性顯著增加。這些類更難理解、調(diào)試和維護(hù)。另外,因?yàn)榧砷_發(fā)環(huán)境(IDE)變得越來(lái)越智能,所以在處理動(dòng)態(tài)類時(shí)它們也會(huì)遇到這類問題,因?yàn)楫?dāng)它們?cè)陬惿喜檎曳椒〞r(shí)會(huì)找不到方法。
現(xiàn)在,并不是說應(yīng)當(dāng)避免編寫這類代碼。相反。我非常喜歡 PHP 的設(shè)計(jì)者這么有想法,把這些魔法方法包含在語(yǔ)言中,這樣我們才能編寫這類代碼。但是重要的是,既要理解優(yōu)點(diǎn),也要理解不足。
當(dāng)然,對(duì)于應(yīng)用程序(例如數(shù)據(jù)庫(kù)訪問)來(lái)說,在這里介紹的技術(shù) —— 與廣泛流行的 Ruby on Rails 系統(tǒng)上使用的技術(shù)類似 —— 能夠極大地減少用 PHP 實(shí)現(xiàn)數(shù)據(jù)庫(kù)應(yīng)用程序所需要的時(shí)間。節(jié)約時(shí)間總不是壞事。
![]() ![]() |
![]()
|
學(xué)習(xí)
-
您可以參閱本文在 developerWorks 全球站點(diǎn)上的
英文原文
。
-
PHP.net
是 PHP 的所有事情的起點(diǎn)。
-
Object Oriented PHP
是學(xué)習(xí) PHP 的面向?qū)ο缶幊烫匦缘暮玫胤健?br />
-
請(qǐng)閱讀
Magic Methods
文檔。
-
Matt Zandstra 編寫的
PHP 5 Objects, Patterns, and Practice
(Apress 2004)是一本關(guān)于 PHP V5 對(duì)象的好書。
-
關(guān)于對(duì)象和模式的經(jīng)典圖書是
Design Patterns: Elements of Reusable Object-Oriented Software
,作者 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides (Addison-Wesley Professional 1995)。這是任何學(xué)習(xí)對(duì)象的程序員必讀的圖書。
-
請(qǐng)獲得關(guān)于
Ruby
語(yǔ)言的更多信息。
-
Ruby on Rails
包實(shí)際演示了許多這些概念。
-
請(qǐng)?jiān)L問 developerWorks 的
PHP project resources
學(xué)習(xí)關(guān)于 PHP 的更多內(nèi)容。
-
要獲得學(xué)習(xí) PHP 編程的一系列 developerWorks 教程,請(qǐng)參閱 “
學(xué)習(xí) PHP,第 1 部分
”、
第 2 部分
和
第 3 部分
。
-
請(qǐng)?jiān)L問
developerWorks 開放源碼專區(qū)
,獲得豐富的 how-to 信息、工具和項(xiàng)目更新,幫助您用開放源碼技術(shù)進(jìn)行開發(fā),并 IBM 產(chǎn)品結(jié)合使用。