php 事件驅動編程:(http://hi.baidu.com/yiqing95)
*
事件驅動在桌面型應用中是很是廣泛的,好比你點擊鼠標,點擊某個按鈕應用程序就得對你的動做作出相應的反應,從程序員的角度看,有兩個角色須要識別:一個是用戶 你個就是你構建的系統,此外無它! 如今用中間者的身份看兩者的行爲模式:用戶老是在向系統發送某種信息,這種信息促使系統進行響應,全部的請求從更抽象的角度觀察,無外乎兩種:命令與請求;請嚴格區分這二者,命令是促使系統作某種變動(好比刪除一條記錄),而請求是須要系統返回某些信息(好比查詢);固然命令也通常會反饋一些信息告訴系統執行狀況(是成功了仍是失敗了,仍是發生了不可預知的異常。);從這裏能夠獲得一個適應全部狀況的編程接口
execute(IN paramInformation):returnedInformation; //參數和返回值有時可選
事件模型是對 「變動——響應」機制的實現,一種機制老是可用多個方法來實現,這取決於編程者如何想問題的。php內部並無所謂的事件,事件分發器的概念,但並不表明咱們不能用事件模型來處理php世界裏的問題!!
*
**
理解事件:
什麼是事件,從你易接受的角度看就是鍵盤,鼠標等人物動做引發的變動,這個變動就叫事件:鼠標單擊事件,鼠標拖動事件,鍵盤點擊事件...... 。 從咱們人類對世界的認識角度來看:全部對你,或對你們有意義的變動就是事件,好比918事件,1212事件911事件,天然界無時不刻發生着某種變化,而只有這些變化引發你足夠重視時他纔是事件,好比你能夠說1980年發生了一個重大事件---那一天我誕生了(這對你父母來講固然是重大事件,至少比911重大些);因此最後闡明一點:事件就是變化,任何變化的結果都能被定義爲事件,因此什麼時事件有你來決定。
從計算機角度看,全部的01序列的位變化均可定義爲事件,仍取決於你,但計算機最底層的實現無外乎讀與寫,因此讀和寫性質的操做結果(或者這個發生)就能夠被稱爲事件。還有一個比較特殊的變化是時間,從四維空間來看雖然三維空間上的某些東西沒有變,但時間維老是在向前推動,全部時間也能夠引發事件。
任何變動或條件的知足都能被定義爲事件,若是不產生信息交換,一個封閉的系統對外界不會產生有意義的事件,你若是隻設計了一個沒有鍵盤和鼠標的電腦,估計電腦充其量也就是一個看着複雜的電視或DVD而已。爲此程序都向外部暴露了某些接口,經過這些接口咱們就可以觸發某些變動。這些接口能夠是GUI上的可視化接口,也能夠是命令行方式的接口,固然從仿形遞歸角度看,更小一些的組件(子系統,類..)會向外界暴露一些API。
外界暴露的接口是通向系統內部的引腳,也是觸發系統內部事件的導火索,在web開發中這些接口,如你所見好比超連接,按鈕,輸入框,下拉菜單等,這些東西都能引起系統對其響應。從上面的事件理解來看,事件是能夠被忽略的(好比911對別人來講是事件對我就不是,又不會影響我什麼),並且要緊的一點是事件能夠傳播!!變化是能夠致使變化的,因此一個事件就有可能致使另外一個事件。這些都是事件的特徵。值得一提的事想一想若是造成環行事件傳播路徑會怎樣??
**
***
咱們是系統的設計者,從系統角度來看咱們須要響應用戶對系統的請求與命令。也就是須要響應一些事件,而後作某些處理,把最終的執行狀況或請求的信息返回給客戶。在系統的邊界處咱們要把這些事件跟系統內部預約義好的響應模塊映射起來:
switch($_GET[‘action’]) {
case 「edit_record」:
edit_record();
break;
case 「view_record」:
view_record();
break;
}
以上代碼是典型的如何處理用戶經過HTTP GET方法發送過來的編輯與查詢請求的。這裏其實咱們就在響應用戶的超連接或者按鈕事件,(或者可能js觸發的ajax調用)。
注意到這些系統邊界的處理老是有着極大的重複性,並且當系統不斷壯大時咱們要不停的增長這些映射是多麼繁瑣的事情,並且咱們在違反DRY(don't repeat yourself 不要重複你本身);
在OOP領域對事件驅動有着至關成熟的模式能夠用,設計模式的宗旨之一就是不要重複發明輪子,因此能夠直接拿來用了。在上面函數式的事件處理輪廓中有兩個角色,一個就是事件分發器(由swith語句充當),另外一個就是事件響應器(由那兩個函數來承當)再有一個就是case後面的東西 那個就是事件!!!。
看這個URl:http://myserver/interface.php?event=edit 這個URL就明顯的代表用戶的意圖,要系統對edit事件進行響應,實際上如今好多開源項目的url設計都有不少這種思路,好比act ,ac前綴表明的就是命令/事件 後臺會找對應的處理腳本,你在康盛公司的項目中仔細觀察URL的規律就可看出某些東西。因此設計其實能夠從URL開始的,URL就是接口,這個在面向對象中可認爲是面向接口編程:舉個例子,你想編輯某個書籍的信息,你就能夠先設計URL,如:myserver/book.php?act=edit&bid=334455 ,想查看某個書籍的信息就能夠 myserver/book.php?act=detail&bid=334455。 等依次類推,這也可認爲是URL驅動的設計。
事件分發器的職責是來根據事件查詢事件響應器的,並把處理流程傳遞給事件響應器,他自己並不作事件處理工做。
全部的事件響應器都在作相似的事情:從$_GET,$_POST(或者是$_REQUEST)中提取用戶的請求參數,並作某些操做(文件系統,網絡,或數據庫相關的工做)而後返回處理結果。OO中很重要的一個特性就是繼承,繼承能夠帶來代碼重用的好處:把公共的東西提取到父類中,子類只實現特定於本身的功能,這樣就不用你不停的代碼複製,也就符合了DRY原則了。
看看事件整個生命週期:用戶動做致使事件————》事件從網絡上傳遞到系統內部————》事件被事件分發器捕獲————》事件分發器查詢該事件對應的處理器————》事件分發器把流程轉給響應的事件響應器————》事件響應器處理事件————》返回處理結果。
以上流程都是正常流,固然可能發生沒有註冊事件處理器或異常狀況。以上的事件也就僅是一個字符串而已(固然事件的信息都在POST GET和COOKIE中)。
***
****
事件框架圖:
上面的圖抽象類沒有畫getPdo方法,還有參數問題,本身看着辦吧,可要可不要反正php中參數信息都在全局數組中放着,全局數組($_GET,$_POST,$_COOKIE,$_REQUEST,$_SERVER,$_SESSION就是整個應用的通訊總線); 事件分發器須要具備註冊和註銷事件處理器的能力。全部的事件處理器都實現了同一個接口,爲了方便在中間又引入了一個抽象類,這個類有個pdo屬性,這個屬性是子類共用的,惟獨handle方法還須要被實例化(被具體類來實現)。 這樣設計的目的是規範化編碼,php是弱類型的語言,只要你實現了某個方法,就能夠認爲你是某個接口的實現者,這個要求比較寬泛,但爲了嚴格起見,仍是聲明本身實現了某個接口,註冊時每每也會類型檢查的!。 類細說: class Dispatcher { private $event; function __construct($eventStr){ $this- >event = $eventStr; } function handleEvent(){ $eventReactorClass = 「{$this- >event}_Handler」; if (class_exists($eventReactorClass )){ $handler_obj = new $eventReactorClass ($this- >event); $response = $handler_obj->handle(); return $response; }else{ echo 「I can’t handle this!」; } } } 上面的代碼主要是根據表明事件的字符串來查找對應的事件響應類,並實例化一個事件處理對象,以後調用其方法handle,返回處理的結果便可。這裏有不少策略能夠用好比註冊事件字串跟類的映射關係,事件處理器類所在的文件夾等均可以考慮,固然「慣例優於配置」已經被好多項目所接納,因此全部的事件響應器類都以事件名很後綴_Handler結尾,方便了開發。因此如今流行的MVC框架都有一套命名慣例。 要擴充系統功能,只須要實現事件處理器的那個handle函數便可,類名中暗含有要處理哪一個事件的信息。至於該函數傳遞的參數,能夠選擇性用之,也能夠忽略。固然通常事件分發器會把最完備的信息做爲參數傳遞給事件響應器的。經常自定義一個Request Response類做爲參數傳入,Request即封裝$_GET,$_POST,$_COOKIE或者$_REQUEST數組。Response做爲響應數據的收集容器用 通常帶有緩存性質。 IEventHandle接口類: interface IEventHandle { function handle(); } 提供通信協議,讓全部的子類都聽從此協議(就是實現那個handle方法)。 抽象類 EventHandler: abstract class EventHandler { private $pdo; function getPdo(){ $this->pdo = new PDO('配置串'); return $pdo; } abstract function handle(); } 主要爲子類提供便捷的獲取pdo或者數據庫鏈接句柄的功能,這樣就不用在子類中重複出現獲取數據庫鏈接這段代碼。 固然能夠根據項目須要隨意添加公共資源在這個抽象類中。具體事件處理函數的實現仍是推遲到子類中了。 具體事件響應器類: class Delete_Handler extends EventHandler{ private $event; function __construct($event){ $this->event = $event; } //下面實現本類要完成的任務:參數看狀況弄吧 function handle(...){ $targetId = $_REQUEST['id'];//獲取刪除的目標ID $pdo = parent::getPdo(); try{ //用pdo完成刪除記錄的操做 }catch(PDOException $ex){ throw $ex; } //或者用模板技術顯示某個頁面去 return true; } } **** ***** 引入安全: 一個操縱是否被容許執行,須要參照當前的上下文信息,上下文也指環境,即當前是哪一個行爲者在觸發事件; 常規的實現是這樣的: performSomeMethod(){ $operator = $_SESSION['user']; if(!empty($operator) && $operator == 'someRole'){ //這裏執行常規代碼; }eles{ echo "你無權進行此項操做!"; } } 以上代碼中主要編入了驗證邏輯,判斷用戶是否登陸,而且是某個角色,而後符合要求後再執行正常的執行邏輯,若是這樣的邏輯確實是每一個事件響應器必須執行的那麼能夠提取到抽象類中,並從新設計一個方法叫:secureHandle() 使之爲抽象的強迫子類實現,並改寫分發器:改成調用這個secureHandle()方法,這時接口可能也要改,在php中接口可能只是一個約定而已,若是不進行類型檢查那麼接口看似無關緊要。這樣的方案我的感受不是太好,因此給出一個我認爲還能夠接受的方案: 改寫抽象類: abstract class EventHandler { private $pdo; function getPdo(){ $this->pdo = new PDO('配置串'); return $pdo; } //使用模板方法設計模式 public function handle($eventContext){ try{ //若是有參數就傳進來 $this->_before($eventContext); $response = $this->_handle($eventContext); $this->_after($eventContext); return $response; }catch(Exception $ex){ //是重拋仍是如何處理取決於你的異常處理策略 } } protected funciton _before(&$_context){ //這裏作驗證 不經過 就拋一個驗證失敗的異常。 } protected function _after(&$_context){ //這裏隨便作啥 或者日誌吧! } //強迫子類實現這個方法 abstract function _handle(&$_context); } 通過以上改寫,應用了模板方法設計模式 能夠把驗證提到_before操做中,把_handle方法設計爲抽象的強迫子類實現 若是子類還想更改安全驗證策略只須要覆寫_before操做便可。這樣原先的接口,事件分發器,都不須要改動。另外這裏也用到了before/after設計模式。請本身查找相關資料。如今子類惟一要作的是複寫_handle方法,必要時複寫_before方法。 ***** ************** 結語: 還有許多問題值得考慮,這裏只給出了大概的思路,好比GUI領域中經常出現的事件註冊機制是否須要,主要用來根據事件查找對應的處理器,固然這個映射徹底能夠來自配置文件,或者數據庫。若是結合上訪問控制列表技術,每個用戶都有一個對應的可操做的事件處理器集,能夠根據當前用戶Id加載其全部可用的事件處理器列表,而後再調用相應處理器的方法,若是列表中找不到對應於事件的處理器證實當前用戶沒有這個權利。這個邏輯就能夠提取到抽象類中來實現。 另外一個值得考慮的問題是類加載問題,一個方法是把系統可能用到的類文件提早所有include進來,這在小項目中是可行的,可是對於第三方庫或者有多個文件夾且相互嵌套時 這種方法會很笨拙。另外一種方法是在事件分發器加載類以前從一個配置文件中讀取相關類和類所在文件的映射信息,而後把類文件包含進來。還有一個就是zendFramework用的把文件夾加進類路徑中include_path而後使用自動加載spl_autoload技術。我前面有一個本身寫的自動加載類能夠用的!