面向對象程序設計(Object Oriented Programming,OOP)是一種程序設計范式,同時也是一種程序開發方法。它將對象作為程序的基本單元,將程序和數據封裝其中,以提高軟件的重用性、靈活性和擴展性。
面向過程、面向對象以及函數式編程被人們稱為編程語言中的三大范式(實際上,面向過程和面向對象都同屬於命令式編程),是三種不同編碼和設計風格。其中面向對象的核心思想是對象、封裝、可重用性和可擴展性。
類是對象的抽象組織,對象是類的具體存在。
類是我們對一組對象的描述,類定義了一系列的屬性和方法,並提供了實際的操作細節,這些方法可以用來對屬性進行加工。
對象含有類屬性的具體值,這就是類的實例化。
類與對象的關系類似一種服務被服務、加工與被加工的關系。
類是屬性和方法的集合,那么在PHP中,對象是什么呢?比較普遍的說法就是“對象由屬性和方法組成”。
對象與數組
對象是什么,我們不好理解,也不容易回答,但是我們知道數組是什么。數組的概念比較簡單。數組是由鍵值對數組組成的,數組的鍵值對和對象的屬性/屬性值對十分相似。對象序列化后和數組序列化后的結果是驚人的相似。
而對象和數組的區別在於:對象還有個指針,指向了它所屬的類。
對象與類
類是定義了一系列屬性和操作的模板,而對象則把屬性進行具體化,然后交給類處理。
對象就是數據,對象本身不包括方法。但對象有一個"指針"指向一個類,這個類里可以有方法。
方法描述不同屬性所導致的不同表現。
類和對象是不可分割的,有對象就必定有一個類和其對應,否則這個對象也就成了沒有親人的孩子。(但有一個特殊情況存在,就是由標量進行強制類型轉換的object,沒有一個類和它對應。此時,PHP中一個被稱為“孤兒”的stdClass類就會收留這個對象)
魔術方法的應用
魔術方法是以兩個下划線“”開頭具有特殊作用的一些方法,可以看做PHP的“語法糖”。
語法糖指那些沒有給計算機語言添加新功能,而只是對人類來說更“甜蜜”的語法。語法糖往往給程序員提供了更實用的編碼方式或者一些技巧性的用法,有益於更好的編碼風格,使代碼更易讀。不過其並沒有給語言添加什么新東西。PHP里的引用、SPL等都屬於語法糖。
<?php
class person{
// 構造方法
// 每次創建對象時先調用此方法,適合在使用對象之前做一些初始化工作。如給屬性賦值、連接數據庫等
public function __construct()
{
}
// PHP提供的“重載”指動態地“創建”類屬性和方法。set和get方法都被歸到重載里。
// 當給對象屬性賦值或者取值時,即使這個屬性不存在,也不會報錯,一定程度上增強了程序的健壯性。
public function __set()
{
}
public function __get()
{
}
// 如何防止調用不存在的方法而出錯?一樣的道理,使用call魔術重載方法。
// call方法原型如下:
// mixed call (string $name, array $arguments)
// 當調用一個不可訪問的方法(如未定義,或者不可見)時,call()會被調用。
// 其中name參數是要調用的方法名稱。arguments參數是一個數組,包含着要傳遞給方法的參數。
public function call($name, $arguments)
{
}
// 當echo輸出對象時,會自動調用toString方法,否則會報錯
public function toString()
{
}
// 當調用一個不可訪問的靜態方法時,callStatic()會被調用。
public function callStatics()
{
}
// 析構方法
// 析構方法會在某個對象的所有引用被刪除或者當對象被顯式銷毀時執行
public function __destruct()
{
}
}
繼承與多態
面向對象的優勢在於類的復用。
把一個類的對象當作另一個類的屬性,並調用它的方法處理問題,這種復用方式叫“組合”。
類與類之間有一種父與子的關系,子類繼承父類的屬性和方法,稱為繼承。
從方法復用角度考慮,如果兩個類具有很多相同的代碼和方法,可以從這兩個類中抽象出一個父類,提供公共方法,然后兩個類作為子類,提供個選哪個方法。這時用繼承語意更好。
在繼承中,用parent指代父類,用self指代自身。使用“:”運算符(范圍解析操作符)調用父類的方法。“:”操作符還用來作為類常量和靜態方法的調用。如果聲明類成員或方法為static,就可以不實例化類而直接訪問,同時也就不能通過一個對象訪問其中的靜態成員,也不能使用“:”訪問一個非靜態方法。
而組合就沒有那么多限制。組合之間的類可以關系(體現為復用代碼)很小,甚至沒有關系。
在編程中,繼承與組合的取舍往往並不是這么直接明了,很難說出兩者是“像”的關系還是“需要”的關系,甚至把它拿到現實世界中建模,還是無法決定應該是繼承還是組合。那應該怎么辦呢?有什么標准嗎?有的。這個標准就是“低耦合”。
耦合是一個軟件結構內不同模塊之間互連程度的度量,也就是不同模塊之間的依賴關系。
低耦合指模塊與模塊之間,盡可能地使模塊間獨立存在;模塊與模塊之間的接口盡量少而簡單。現代的面向對象的思想不強調為真實世界建模,變得更加理性化一些,把目標放在解耦上。
解耦是要解除模塊與模塊之間的依賴。
繼承與組合二者語義上難於區分,在兩者均可使用的情況下,更傾向於使用組合。為什么呢?繼承存在什么問題呢?
-
繼承破壞封裝性。
比如,定義鳥類為父類,具有羽毛屬性和飛翔方法,其子類天鵝、鴨子、鴕鳥等繼承鳥這個類。顯然,鴨子和鴕鳥不需要飛翔這個方法,但作為這個子類,它們去可以無區別地使用飛翔這個方法,顯然破壞了類的封裝性。而組合,從語義上來說,要優於繼承。 -
繼承是緊耦合的
繼承使得子類和父類捆綁在一起。組合僅通過唯一接口和外部進行通信,耦合度低於繼承。 -
繼承擴展更復雜
隨着繼承層的增加和子類的增加,將涉及大量方法重寫。使用組合,可以根據類型約束,實現動態組合,減少代碼。 -
不恰當地使用繼承可能違反現實世界中的邏輯
比如,人作為父類,雇員、經理、學生作為子類,可能存在這樣的問題,經理一定是雇員,學生也可能是雇員,而使用繼承的話一個人就無法擁有多個角色。這種問題歸結起來就是“角色”和“權限”問題。在權限系統中很可能存在這樣的問題,經理沒有權限直接操作主管所負責的資源,技術部經理也沒權限直接命令市場部主管。這就要求角色和權限系統的設計要更靈活。不恰當地繼承可能導致邏輯混亂,而使用組合就可以較好地解決這個問題。
當然,組合並非沒有缺點。在創建組合對象時,組合需要——創建局部對象,這一定程度上增加了一些代碼,而繼承則不需要這一步,因為子類自動有了父類的方法。組合還有其他的一些缺點,不過總體來說,是優點大於缺點。
繼承最大的優點是擴展簡單,但是其缺點大於優點,所以在設計時,需要慎重考慮。那應該如何使用繼承呢?
精心設計專門用於被繼承的類,繼承樹的抽象層應該比較穩定,一般不要多於三層。
對於不是專門用於被繼承的類,禁止其被繼承,也就是使用final修飾符。使用final修飾符既可防止重方法被非法重寫,又給能編輯器尋找優化的機會。
優先考慮組合關系提高代碼的可重用性。
子類是一種特殊的類型,而不只是父類的一個角色。
子類擴展,而不是覆蓋或者使父類的功能失效。
底層代碼多用組合,頂層/業務代碼代碼多用繼承。底層用組合可以提高效率,避免對象臃腫。頂層代碼用繼承可以提高靈活性,讓業務使用更方便。
繼承並非一無是處,而組合也不是完美無缺。如果既要組合的靈活,又要繼承的代碼簡潔,能做到嗎?
這是可以做到的,譬如多重繼承,就具有這個特性。多重繼承里一個類可以同時繼承多個父類,組合兩個父類的功能。C++里就是使用的這種模型來增強繼承的靈活性的,但是多重繼承過於靈活,並且會帶來“菱形問題”,故為其使用帶來了不少困難,模型變得復雜起來,因此在大多數語言中,都放棄了多重繼承這一模型。
多重繼承太復雜,PHP5.4引入的新的語法結構Traits就是一種很好的解決方案,可以很方便實現對象的擴展,是除extend、implements外的另外一種擴展對象的方式。Traits既可以使單繼承模式的語言獲得多重繼承的靈活,又可以避免多重繼承帶來的種種問題。
各種語言中的多態
多態確切的含義是:同一類的對象收到相同消息時,會得到不同的結果。而這個消息是不可預測的。多態,顧名思義,就是多種狀態,也就是多種結果。
多態性是一種通過多種狀態或階段描述相同對象的編程方式。它的真正意義在於:實際開發中,只要關心一個接口或基類的編程,而不必關心一個對象所屬於的具體類。
PHP作為一門腳本語言,自身就是多態的。
通過判斷傳入的對象所屬的類不同來調用其同名方法,得出不同結果,這是多態嗎?如果站在C++
角度,這不是多態,這只是不同類對象的不同表現而已。C++里的多態指運行時對象的具體化,指同一類對象調用相同的方法而返回不同的結果。
區別是否是多態的關鍵在於看對象是否屬於同一類型。如果把它們看做同一種類型,調用相同的函數,返回了不同的結果,那么它就是多態;否則,不能稱其為多態。由此可見,弱類型的PHP里多態和傳統強類型語言里的多態在實現和概念上是有一些區別的,而且弱類型語言實現起多態來會更簡單,更靈活。
總結如下:
多態指同一類對象在運行時的具體化。
PHP語言是弱類型的,實現多態更簡單、更靈活。
類型轉換不是多態。
PHP中父類和子類看做“繼父”和“繼子”關系,它們存在繼承關系,但不存在血緣關系。因此子類無法向上轉型為父類,從而失去多態最典型的特征。
多態的本質就是 if ……else,只不過實現的層級不同。
面向接口編程
面向接口編程並不是一種新的編程范式。其次,這里指的是狹義的接口,即interface關鍵字。廣義的接口可以是任何一個對外提供服務的出口,比如提供數據傳輸的USB接口、淘寶網對其他網站開發的支付寶接口。
接口定義一套規范,描述一個“物”的功能,要求如果現實中的“物”想成為可用,就必須實現這些基本功能。接口這樣描述自己:
“對於實現我的所有類,看起來都應該像我現在這個樣子。”
采用一個特定接口的所有代碼都知道對於那個接口會調用什么方法。這便是接口的全部含義。接口常用來作為類與類之間的一個“協議”。接口是抽象類的變體,接口中所有方法都是抽象的,沒有一個有程序體。接口除了可以包含方法外,還能包含常量。
接口起了一個強制規范和契約的作用。
接口不僅規范接口的實現者,還規范接口的執行者,不允許調用接口中本不存在的方法。當然這並不是說一個類如果實現了接口,就只能實現接口中才用的方法,而是說,如果針對的是接口,而不是具體的類,則只能按接口的約定辦事。這樣的語法規定對接口的使用是有利的,讓程序更加健全。根據這個角度講,為了保證接口的定義,通常一個接口的實現類僅實現該接口所具有的方法,做到專一,當然這也不是一成不變的。
通常在大型項目中,會把代碼進行分層和分工。核心開發人員和技術經理編寫的流程和代碼,往往是以接口的形式給出,而基礎開發人員則針對這些接口,填充代碼。這樣,核心技術人員把更多精力投入到了技術攻關和業務邏輯中。前端針對接口編程,只管在Action層調用Service,不管實現細節;而后端則要負責Dao和Service層接口實現。這樣,就實現了代碼分工與合作。
由於PHP是弱類型,且強調靈活,所以並不推薦大規模使用接口,而是僅在部分“內核”代碼中使用接口,因為PHP中的接口已經失去很多接口應該具有的語義。從語義上考慮, 可以更多地使用抽象類。
反射
面向對象編程中對象被賦予了自省的能力,而這個自省的過程就是反射。
反射指在PHP運行狀態中,擴展分析PHP程序,導出或提取關於類、方法、屬性、參數等的詳細信息,包括注釋。這種動態獲取信息以及動態調用對象方法的功能稱為反射API。
反射API 的功能很強大,甚至能還原這個類的原型,包括方法的訪問權限。反射完整地描述了一個類或者對象的原型。反射不僅可以用於類和對象,還可以用於函數、擴展模塊、異常等。
反射可以用於文檔生成。因此可以用它對文件里的類進行掃描,逐個生成描述文檔。
反射可以做動態代理,來代替實際的類運行,並且在方法運行前后進行攔截,動態地改變類中的方法和屬性。
在平常開發中,用到反射的地方不多:一個是對對象進行調試,另一個是獲取類的信息。在MVC和插件開發中,使用反射很常見,但是反射的消耗也很大,在可以找到替代方案的情況下,不要濫用。
很多時候,善用反射能保持代碼的優雅和簡潔,但反射也會破壞類的封裝性,因為反射可以使本不應該暴露的方法或屬性被強制暴露了出來,這既是優點也是缺點。
異常和錯誤處理
在語言級別上,通常具有許多錯誤處理模式,但這些模式往往建立在約定俗成的基礎上,也就是說這些錯誤都是預知的。但是在大型程序中,如果沒出調用都去逐一檢查錯誤,會讓代碼變得冗長復雜,到處充斥着if……else,並且嚴重降低代碼的可讀性。而且人的因素也是不可信賴的,程序員可能並不會把這些問題當一回事,從而導致業務異常。在這種背景下,就逐漸形成了異常處理機制。
異常的思想最早可以追溯到20世紀60年代,其在C++
、Java
中發揚光大,PHP則部分借鑒了這兩種語言的異常處理機制。
PHP里的異常,是程序運行中不符合預期的情況及與正常流程不同的狀況。一種不正常的情況,就是按照正常邏輯不該出錯,但仍然出錯的情況,這屬於邏輯和業務流程的一種中斷,而不是語法錯誤。PHP里的錯誤則屬於自身問題,是一種非法語法或者環境問題導致的、讓編譯器無法通過檢查甚至無法運行的情況。
在各種語言中,異常(exception)和錯誤(error)的概念是不一樣的。在PHP里,遇到任何自身錯誤都會觸發一個錯誤,而不是拋出異常(對於一些情況,會同時拋出異常和錯誤)。PHP一旦遇到非正常代碼,通常都會觸發錯誤,而不是拋出異常。在這個意義上,如果想使用異常處理不可預料的問題,是辦不到的。比如,想在文件不存在且數據庫連接打不開時觸發異常,是不可行的。這在PHP里把它作為錯誤拋出,而不會作為異常自動捕獲。
那PHP里的異常應該怎么用?在什么時候拋出異常,什么時候捕獲呢?什么場景下能應用異常?在下面三個場景下會用到異常處理機制。
1.對程序的悲觀預測
如果一個程序員對自己的代碼有“悲觀情緒”,這里並不是指該程序員代碼質量不高,而是他認為自己的代碼無法一一處理各種可預見、不可預見的情況,那該程序員就會進行異常處理。假設一個場景,程序員悲觀地認為自己的這段代碼在高並發條件下可能產生死鎖,那么他就會悲觀地拋出異常,然后在死鎖時進行捕獲,對異常進行細致的處理。
2.程序的需要和對業務的關注
如果程序員希望業務代碼中不會充斥大堆的打印、調試等處理,通常他們會使用異常機制;或者業務上需要定義一些自己的異常,這個時候就需要自定義一個異常,對現實世界中各種各樣的業務進行補充。比如上班遲到,這種情況認為是一個異常,要收集起來,到月底集中處理,扣你工資;如果程序員希望有預見性地處理可能發生的、會影響正常業務的代碼,那么它需要異常。在這里,強調了異常是業務處理中必不可少的環節,不能對異常視而不見。異常機制認為,數據一致很重要,在數據一致性可能被破壞時,就需要異常機制進行事后補救。
舉個例子,比如有個上傳文件的業務需求,要把上傳的文件保存在一個目錄中,並在數據庫里插入這個文件的記錄,那么這兩步就是互相關聯、密不可分的一個集成的業務,缺一不可。文件保存失敗,而插入紀錄成功就會導致無法下載文件;而文件保存成功數據庫寫入失敗,則會導致沒有記錄的文件成為死文件,永遠得不到下載。
那么假設文件保存成功后沒有提示,但是保存失敗會自動拋出異常,訪問數據庫也一樣,插入成功沒有提示,失敗則自動拋出異常,就可以把這兩個有可能拋出異常的代碼段包在一個try語句里,然后用catch捕獲錯誤,在catch代碼段里刪除沒有被記錄到文件或者刪除沒有文件的記錄,以保證業務數據的一致性。因此,從業務這個角度講,異常偏重於保護業務數據一致性,並且強調對異常業務的處理。
如果代碼中只是象征性地try……catch,然后打印一個報錯,最后over。這樣的異常不如不用,因為沒有體現異常思想。
<?php
try {
// 可能出錯的代碼段
if(文件上傳不成功) throw (上傳異常);
if(插入數據庫不成功) throw (數據庫操作異常);
} catch (異常) {
必須的補救措施,如刪除文件、刪除數據庫插入記錄,這個處理很細致
}
// ……
也可以如下
<?php
上傳 {
if(文件上傳不成功) throw (上傳異常);
if(插入數據庫不成功) throw (數據庫操作異常);
}
// 其他代碼……
try {
上傳;其他;
} catch (上傳異常) {
必須的補救措施,如刪除文件,刪除數據庫插入記錄
} catch (其他異常) {
記錄log
}
上面兩種捕獲異常的方式中,前一種是在異常發生時立刻捕獲;后一種是分散拋異常集中捕獲。那到底應該是吧哪一種呢?
如果業務很重要,那么異常越早處理越好,以保證程序在意外情況下能保持業務處理的一致性。比如一個操作有多個前提步驟,突入按最后一個步驟異常了,那么其他前提操作都要消除掉才行,以保證數據的一致性。並且在這種核心業務下,有大量的代碼來做善后工作,進行數據補救,這是一種比較悲觀的、而又重要的異常。我們應把異常消滅在局部,避免異常的擴散。
如果異常不是那么重要,並且在單一入口、MVC風格的應用中,為了保持代碼流程的統一,則常常采用后一種異常處理方式。這種異常處理方式更多強調業務流程的走向,對善后工作並不是很關心。這是一種次要異常,其將異常集中處理從而使流程更專一。
異常處理機制可以把每一件事當作事務考慮,還可以把異常看成一種內建的恢復系統。如果程序某部分失敗,異常將恢復到某個已知穩定的點上,而這個點就是程序的上下文環境,而try塊里面的代碼就保存catch所要知道的程序上下文信息。因此,如果很看中異常,就應該分散進行try……catch處理。
3.語言級別的健壯性要求
在健壯性這點上,PHP是不足的。以Java為例,Java是一種面向企業級開發的語言,強調健壯性。Java中支持多線程,Java認為,多線程被中斷這種情況是徹徹底底的無法預料和避免的。所以Java規定,凡是用了多線程,就必須正視這種情況。你要么拋出,不管它;要么捕獲,進行處理。總之,你必須面對InterruptedException異常,不准回避。也就是異常發生后應對重要數據業務進行補救,當然你可以不做,但是你必須意識到,異常有可能發生。
這類異常是強制的。更多異常是非強制的,由程序員決定。Java對異常的分類和約束,保證了Java程序的健壯性。
異常就是無法控制的運行時錯誤,會導致出錯時中斷正常邏輯運行,該異常代碼后面的邏輯都不能繼續運行。那么try……catch處理的好處就是:可以把異常造成邏輯中斷破壞降到最小范圍內,並且經過補救處理后不影響業務邏輯的完整性;亂拋異常和只拋不捕獲,或捕獲而不補救,會導致數據混亂。這就是異常處理的一個重要作用,就是通過精確控制運行時的流程,在程序中斷時,有預見地用try縮小可能出錯的影響范圍,及時捕獲異常發生並做出相應補救,以使邏輯流程仍然能回到正常軌道上。
PHP中的錯誤級別
PHP錯誤就是會使腳本運行不正常的情況。
PHP錯誤有很多種,包括warning、notice、deprecated、fetal error等。這和一般意義的錯誤概念有些差別。所以,notice不叫通知,而叫通知級別的錯誤,warning也不叫警告,而叫警告級別的錯誤。
錯誤大致分為以下幾類。
deprecated是最低級別的錯誤,表示“不推薦,不建議”。
其次是notice。這種錯誤一般告訴你語法中存在不當的地方。雖然PHP是腳本語言,語法要求不嚴,但仍然建議對變量進行初始化。
warning是級別比較高的錯誤,在語法中出現很不恰當的情況時才會報此錯誤。
更高級別的錯誤是fetal error。這是致命錯誤,直接導致PHP流程終結,后面的代碼不再執行。這種問題非改不可。
最高級別的錯誤是語法解析錯誤fetal error。上面提到的錯誤都屬於PHP代碼運行期間錯誤,而語法解析錯誤屬於語法檢查階段錯誤,這將導致PHP代碼無法通過語法檢查。錯誤級別不止這幾個,最主要的都在前面提到了。PHP手冊中一共定義了16個級別錯誤,最常用的就這幾個。
error_reporting指定錯誤級別。
error_reporting=E_ALL | E_STRICT
display_errors=On
在代碼質量或者環境不可控時(比如數據庫連接失敗),使用error_reporting(0),這樣就能屏蔽錯誤了,正式部署時可以采取這樣的策略,防止錯誤消息泄露敏感信息。另一個技巧就是在函數前加@符號,抑制錯誤信息輸出,如@mysql_connect()。
PHP中錯誤處理機制
PHP里有一套錯誤處理機制,可以使用set_error_handler接管PHP錯誤處理,也可以使用trigger_error函數主動拋出一個錯誤。
set_error_handler()函數設置用戶自定義的錯誤處理函數。函數用於創建運行期間的用戶自己的錯誤處理方法。它需要先創建一個錯誤處理函數,然后設置錯誤級別,語法如下:
set_error_handler(error_function, error_types)
參數描述如下:
error_function:規定發生錯誤時運行的函數。必需。
error_types:規定在哪個錯誤報告級別會顯示用戶定義的錯誤。可選。默認為“E_ALL”。
如果使用該函數,會完全繞過標准PHP錯誤處理程序,如果有必要,用戶定義的錯誤處理程序必須終止(die())腳本。
自定義的異常處理函數
<?php
function customError($errno, $errstr, $errfile, $errline){
echo "<b>錯誤代碼:</b>[${error}]${errstr}\r\n";
echo "錯誤所在的代碼行:{$errline}文件{$errfile}\r\n";
echo "PHP版本",PHP_VERSION,"(",PHP_OS,")\r\n";
// die();
}
set_error_handler("customError", E_ARR | E_STRICT);
$a = array('o' => 2,3,4,6);
echo $a[o];
在函數里,可以對錯誤的詳情進行格式化輸出,也可以做任何要做的事情,比如判斷當前環境和權限不同給出不同的錯誤提示,可使用errer_log函數將錯誤記入log文件,還可以細化處理,針對error的不同進行對應的處理。
自定義的錯誤處理函數一定要有這四個輸入變量errno、errstr、errfile、errline。
errno是一組常量,代表錯誤的等級,同時也有一組整數和其對應,但一般使用其字符串表示,這樣語義更好一點。比如E_WARNING,其二進制掩碼為4,表示警告信息。
接下來,就是將這個函數作為回調參數傳遞給set_error_handler。這樣就能接管PHP原生的錯誤處理函數了。要注意的是,這種托管方式並不能托管所有種類的錯誤,如E_ERROR、E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、E_COMPILE_WARNING,以及E_STRICT中的部分。這些錯誤會以最原始的方式顯示,或者不顯示。
set_error_handler函數會接管PHP內置的錯誤處理,你可以在同一個頁面使用restore_error_handler(); 取消接管。
如果使用自定義的set_error_handler接管PHP的錯誤處理,先前代碼里的錯誤抑制@將失效,這種錯誤也會被顯示。
在PHP異常中,異常處理機制是有限的,無法自動拋出異常,必須收到進行,並且內置異常有限。PHP把許多異常看做錯誤,這樣就可以把這些“異常”像錯誤一樣用set_error_handler接管,進而主動拋出異常。代碼如下所示:
function customError($errno, $errstr, $errfile, $errline){
// 自定義錯誤處理時,手動拋出異常
throw new Exception($level.'|'.${errstr});
set_error_handler("customError", E_ALL | E_STRICT);
try {
$a = 5/0;
} catch (Exception $e){
echo "錯誤信息:",$e->getMessage();
}
}
這樣就能捕獲到異常和非致命的錯誤,這樣可以彌補PHP異常處理機制的部分不足。
這種“群則迂回”的處理方式存在的問題就是:必須依靠程序員自己來掌控對異常的處理,對於異常高發區、敏感區,如果程序員處理不好,就會導致前面所提到的業務數據不一致的問題。其優點在於,可以獲得程序運行時的上下文信息,以進行針對性的補救。
fetal error這樣的錯誤雖然捕獲不到,也無法在發生此錯誤后恢復流程處理,但是還是可以使用一些特殊方法對這種錯誤進行處理的。這需要用到一個函數——register_shutdown_function,此函數會在PHP程序終止或者die時觸發一個函數,給PHP來一個短暫的“回光返照”。這種方式只是做點收尾工作,但是PHP流程的終止是必然的。對於Parse error級別的錯誤,可以增加一個配置日志記錄,這樣一旦PHP發生了錯誤,就會被記入log文件,方便以后查詢。
log_errors=On
error_log=usr/log/php.log
和exception類似,錯誤處理也有對應拋出錯誤的函數,那就是trigger_error函數,如下所示:
<?php
$divisor = 0;
if($divisor == 0){
trigger_error("Cannot divde by zero",E_USER_ERROR);
}
echo "break";
在PHP中,錯誤和異常是兩個不同的概念,這種設計從根本上導致了PHP的異常和其他語言相異。以Java為例,Java中,異常是錯誤唯一的報告方式。說到底,兩者的區別就是對異常和錯誤的認識不同而產生的。PHP的異常絕大部分必須通過某種辦法手動拋出,才能被捕獲到,是一種半自動化的異常處理機制。
無論是錯誤還是異常,都可以使用handler接管系統已有的處理機制。