1. 程式人生 > >[轉]php中實現事件驅動

[轉]php中實現事件驅動

例子 容器 之一 web pri 動作 -h 方法 emp

原文: https://blog.csdn.net/yhl27/article/details/8705313

--------------------------------------------------------------------------

php中實現事件驅動

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技術。我前面有一個自己寫的自動加載類可以用的!

[轉]php中實現事件驅動