PHP回調函數的幾種用法

前言


    最近在開發一個PHP系統,爲了提高系統的擴展性,我想在系統中加入類似Javascript的事件處理機制,例如:我想在一篇新聞被添加以後,我想記錄一下日誌,用類似Javascript的代碼,應該是這樣寫的:

None.giffunction fnCallBack( $news )
None.gif{
None.gif     
//將$news的信息記錄到日誌中
None.gif
    writeLog( $news->getTitle().' has been added successfully!');
None.gif}
None.gif
None.gif
$newsEventManager->addEventListener( 'add' , fnCallBack );
None.gif


    其中,fnCallBack函數是回調函數,addEventListener表示監聽newsEventManager的add事件。當一篇news被add以後,系統就會調用fnCallBack函數,從而完成writeLog的動作。

    但是,PHP中的函數傳遞方法和Javascript有很大的不同。在Javascript中,函數也是對象,它可以很方便的當作參數傳遞,但是PHP不行。

None.gif$newsEventManager->addEventListener( 'add' , fnCallBack );


上面這行代碼中的fnCallBack,看上去好像是那個函數的句柄,但實質上它是一個字符串,並不是我們所要的函數。

爲了實現我們的事件模型,有必要研究一下PHP的回調函數的實現方法。



全局函數的回調

     這裏的全局函數的意思,是直接使用function定義的函數,它不包含在任何對象或類之中。請看下面的例子

 示例代碼

None.giffunction fnCallBack( $msg1 , $msg2 )
None.gif{
None.gif    
echo 'msg1:'.$msg1;
None.gif    
echo "<br />\n";
None.gif    
echo 'msg2:'.$msg2;
None.gif}
None.gif
$fnName = "fnCallBack";
None.gif
$params = array'hello' , 'world' );
None.gif
call_user_func_array$fnName , $params );


代碼說明:

    這裏使用了PHP內置的函數call_user_func_array來進行調用。call_user_func_array有兩個參數,第1個參數是一個字符串,表示要調用的函數名,第2個參數是一個數組,表示參數列表,按照順序依次會傳遞給要調用的函數。


效果如下:




類的靜態方法的回調

     如果我們要回調的方法,是一個類的靜態方法,那怎麼辦呢?我們依然可以利用PHP內置的call_user_func_array方法來進行調用,請看示例:

示例代碼:

None.gifclass MyClass
None.gif{
None.gif    
public static function fnCallBack( $msg1 , $msg2 )
None.gif    {
None.gif        
echo 'msg1:'.$msg1;
None.gif        
echo "<br />\n";
None.gif        
echo 'msg2:'.$msg2;
None.gif    }
None.gif}
None.gif
None.gif
$className = 'MyClass';
None.gif
$fnName = "fnCallBack";
None.gif
$params = array'hello' , 'world' );
None.gif
call_user_func_arrayarray$className , $fnName ) , $params );



代碼說明:
   
    這段代碼和第1種方法的代碼很相似,我們將類名(MyClass)也作爲call_user_func_array的第1個參數傳遞進去,就可以實現類的靜態方法的回調了。注意,這時call_user_func_array的第1個參數是一個數組了,數組的第1個元素是類名,第二個元素是要調用的函數名

運行結果:


(其實和第1種方法的結果是一樣的 ^_^ )


繼續研究

    如果我用這種方法調用一個類的非靜態方法(也就是把static去掉),會出現什麼結果呢?請看下面代碼

None.gifclass MyClass
None.gif{
None.gif    
public function fnCallBack( $msg1 , $msg2 )
None.gif    {
None.gif        
echo 'msg1:'.$msg1;
None.gif        
echo "<br />\n";
None.gif        
echo 'msg2:'.$msg2;
None.gif    }
None.gif}
None.gif
None.gif
$className = 'MyClass';
None.gif
$fnName = "fnCallBack";
None.gif
$params = array'hello' , 'world' );
None.gif
call_user_func_arrayarray$className , $fnName ) , $params );


運行結果


和前面的結果還是一樣的。。。


現在我爲這個類添加一點屬性,並在方法中引用

None.gifclass MyClass
None.gif{
None.gif    
private $name = 'abc';
None.gif    
public function fnCallBack( $msg1 , $msg2 )
None.gif    {
None.gif        
echo 'object name:'.$this->name;
None.gif        
echo "<br />\n";
None.gif        
echo 'msg1:'.$msg1;
None.gif        
echo "<br />\n";
None.gif        
echo 'msg2:'.$msg2;
None.gif    }
None.gif}
None.gif
None.gif
$className = 'MyClass';
None.gif
$fnName = "fnCallBack";
None.gif
$params = array'hello' , 'world' );
None.gif
call_user_func_arrayarray$className , $fnName ) , $params );


運行結果


出現解析錯誤,提示$this沒有在對象環境下出現,說明這個方法不能用類來調用,而是要用對象來調用。那我們就修改一下代碼,創建一個對象:

None.gifclass MyClass
None.gif{
None.gif    
public function fnCallBack( $msg1 , $msg2 )
None.gif    {
None.gif        
echo 'msg1:'.$msg1;
None.gif        
echo "<br />\n";
None.gif        
echo 'msg2:'.$msg2;
None.gif    }
None.gif}
None.gif
None.gif
$myobj = new MyClass();
None.gif
$className = 'myobj';
None.gif
$fnName = "fnCallBack";
None.gif
$params = array'hello' , 'world' );
None.gifcall_user_func_arrayarray$className , $fnName ) , $params );


運行結果:



提示call_user_func_array的第1個參數非法,也就是說,調用失敗。看來我們不能用call_user_func_array方法來回調一個對象的方法了,那麼如何實現對象方法的回調的?


對象的方法的回調
     
    我先用最原始的字符串形式的調用方法嘗試了一下,如下所示:

None.gifclass MyClass
None.gif{
None.gif    
private $name = 'abc';
None.gif    
public function fnCallBack( $msg1 = 'default msg1' , $msg2 = 'default msg2' )
None.gif    {
None.gif        
echo 'object name:'.$this->name;
None.gif        
echo "<br />\n";
None.gif        
echo 'msg1:'.$msg1;
None.gif        
echo "<br />\n";
None.gif        
echo 'msg2:'.$msg2;
None.gif    }
None.gif}
None.gif
None.gif
$myobj = new MyClass();
None.gif
$fnName = "fnCallBack";
None.gif
$params = array'hello' , 'world' );
None.gif
$myobj->$fnName();


成功了,輸出結果



調用是成功了,不過如何把參數params傳給這個方法呢,如果把params直接傳進去,那麼它會作爲1個參數,怎麼把params拆開來傳進去呢?

查了下PHP手冊,找到了create_function函數,這個方法可以用字符串來創建一個匿名函數,好,有思路了,可以創建一個匿名的函數,在這個匿名函數中,調用我們的回調函數,並把參數傳進去。

我先手動創建一個匿名函數anonymous,在這個函數中,用前面試出來的方法調用回調函數,如下所示:

None.gifclass MyClass
None.gif{
None.gif    
private $name = 'abc';
None.gif    
public function fnCallBack( $msg1 = 'default msg1' , $msg2 = 'default msg2' )
None.gif    {
None.gif        
echo 'object name:'.$this->name;
None.gif        
echo "<br />\n";
None.gif        
echo 'msg1:'.$msg1;
None.gif        
echo "<br />\n";
None.gif        
echo 'msg2:'.$msg2;
None.gif    }
None.gif}
None.gif
None.gif
$myobj = new MyClass();
None.gif
$fnName = "fnCallBack";
None.gif
$params = array'hello' , 'world' );
None.gif
None.gif
function anonymous()
None.gif{
None.gif    
global $myobj;
None.gif    
global $fnName;
None.gif    
global $params;
None.gif    
$myobj->$fnName$params[0, $params[1] );
None.gif}
None.gifanonymous();


 成功了,可以看到,對象的屬性name也輸出來了



然後,我用create_function來創建這個匿名函數,同時,代碼中的params[0],params[1]應該是動態生成的,代碼如下:

None.gif$strParams = '';
None.gif
$strCode = 'global $myobj;global $fnName;global $params;$myobj->$fnName(';
None.gif
for ( $i = 0 ; $i < count$params ) ; $i ++ )
None.gif{
None.gif    
$strParams .= ( '$params['.$i.']' );
None.gif    
if ( $i != count$params )-1 )
None.gif    {
None.gif        
$strParams .= ',';
None.gif    }
None.gif}
None.gif
$strCode = $strCode.$strParams.");";
None.gif
$anonymous = create_function'' , $strCode);
None.gif
$anonymous();



這段代碼可以定義一個匿名函數,並保存在$anonymous變量中,最後調用這個$anonymous,實現了方法的回調,如圖




PHP事件模型(觀察者模式)的實現思路


至此,PHP中的3種常見的函數類型(全局函數,類靜態函數,對象的方法)都可以回調了,可以實現文章一開始說的事件模型了 :)

事件模型模仿Firefox的Javascript實現,有3個方法,分別是

None.gifaddEventListener:註冊一個事件上的響應回調函數
None.gifremoveEventListener:刪除一個事件上的響應回調函數
None.giffire:觸發一個事件,也就是循環調用所有響應這個事件的回調函數



    不過,由於第2、第3種方法需要傳遞上下文(也就是類名和對象名),所以addEventListener和removeEventListener應該有3個參數,我是這樣設計的:

None.giffunction addEventListener( $evtName , $handler , $scope = null )

  
    第1個參數表示事件名,字符串類型
    第2個參數表示回調函數名,字符串類型
    第3個參數$scope是上下文環境,一共有3種類型,null表示傳入的handler函數是一個全局函數,字符串類型表示傳入的handler函數是scope類的靜態函數,對象類型表示傳入的scope是一個對象,handler函數是對象的一個方法。

None.giffunction fire( $evtName , $params = null )


    這個方法內,會讀取出所有響應evtName的handler,然後判斷它對應的scope,如果是null,則用本文第1種方法回調,如果是字符串,則用本文第2種方法回調,如果是對象,則用本文第3種方法回調。這樣,一個PHP的事件模型就可以實現了,而且可以將回調函數放在某個對象中。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章