[轉]php中實現事件驅動

來源:互聯網
上載者:User

標籤:協議   重複   With語句   autoload   com   detail   ref   catch   包含   

原文: 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中實現事件驅動

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.