Javascript在处理事件的时候,使用了观察者模式,使得发生事件的对象和响应事件的对象完全的解耦,提高了系统的可扩展性。例如:
上面的代码为myButton添加了一个响应click事件的函数myListener。myButton对象不需要知道myListener函数是在何处以何种方式实现的,也不需要关心究竟有多少个响应函数会响应click事件,这样就可以极大的提高系统的扩展性和健壮性。
要实现这个功能,最核心的技术就是函数的传递。在Javascript中,由于函数本身也是一个对象,因此可以很方便的当参数传递,但是,在PHP中如何传递函数呢?具体的方法可以参考我的另外一篇文章:
PHP回调函数的实现方法
http://www.cnitblog.com/CoffeeCat/archive/2009/04/21/56541.html
我们已经知道如何在PHP中实现回调函数,因此就可以实现事件模型了,下面给出Event类的代码,这个类维护一个事件的响应队列,并提供注册事件、卸载事件和触发事件的方法。
下面是这个类的用法:
1:用简单函数响应事件
输出效果
2:用类静态函数和对象的方法响应事件
输出结果
可以看到,我们可以将响应事件的函数放置在任何位置,事件对象都能够正确的进行回调。
Event类的包装
Event类提供了基本的事件模型,不过在实际应用的时候,我们应该对这个类进行进一步包装,因为不同的对象有不同的事件。例如,我们之前的PHP代码是通过fire来触发相关事件的,而在实际应用的时候,我们是通过一些操作的发生来触发事件的。
举个例子,我们要开发一个新闻系统,并使新闻在添加操作的时候具有事件响应的能力,我们就需要把Event类包装在News中,如:
以下是调用代码:
观察粗体的代码,可以看到,这个实现方式已经和我们的Javascript的方式非常相似了。
输出结果:
至此,类似Javascript(或者是ActionScript3.0)的事件模型,已经在PHP中实现了。
二次开发的注意事项
继承还是聚合
您可以通过包装Event类来使您自己的对象具有事件处理的能力,一般来说,包装Event类有2种方法,继承和聚合,“Event类的包装”中就是使用聚合来进行包装的,下面还是以新闻系统为例子,提供继承的示例:
class
News
extends
Event
{
public static $ADD_SUCCESS = 'add_success'
;
public static $ADD_FAILED = 'add_failed'
;
public
$title
=
''
;

//
添加一篇新闻到数据库
public
function
add()
{
try
{
//
在这里进行添加新闻的操作
echo
'
adding news into Database
'
;
echo
"
<br />\n
"
;
//
添加完成,触发成功事件
$this->fire(self::$ADD_SUCCESS , array( $this
));
}
catch
(
Exception
$e
)
{
//
添加失败,触发失败事件
$this->fire(self::$ADD_FAILED , array( $this
));
}
}
}
调用示例
function
fnAddSuccess(
$news
)
{
echo
'
news:
'
.
$news
->
title
.
'
has been added
'
;
}
function
fnAddFailed(
$news
)
{
echo
'
news:
'
.
$news
->
title
.
'
is NOT added
'
;
}
$myNews
=
new
News();
$myNews
->
title
=
'
my news title
'
;
$myNews
->
addEventListener(News
::
$ADD_SUCCESS
,
fnAddSuccess );
$myNews
->
addEventListener(News
::
$ADD_FAILED
,
fnAddFailed );
$myNews
->
add();
输出结果
继承的代码比聚合更简洁,但是我还是建议使用聚合。因为PHP是单继承模式,所以如果使用了继承来包装Event类,那么您的类就不能再继承其他类了。除非您确定您的类不会继承别的类,否则就不要使用继承。利用聚合将使代码的灵活性更大。
事件描述文档
如果您的类提供了事件机制,这就意味着您的类可以被别人扩展。例如,前面我的News提供了事件机制,那么别人就可以通过响应onAddSuccess来做一些其他操作(比如发邮件通知管理员)。所以,提供清晰的事件描述文档十分重要。
编写要点
1:事件列表
也就是对象提供的所有事件响应列表,例如前面的News,事件列表就是:
add_success
add_failed
2:触发事件的条件
表示对象提供的事件在满足什么条件下触发,例如:
add_success
成功添加一篇新闻以后触发
add_failed
一篇新闻添加失败后触发
3:回调函数原型
表示用户应该如何来定义用于响应的回调函数,同时要说明函数的参数是什么含义,例如:
add_success
成功添加一篇新闻以后触发
回调函数原型:
function ( $news )
news表示刚刚添加成功的News类的对象
add_failed
一篇新闻添加失败后触发
回调函数原型:
function ( $news )
news表示刚刚添加失败的News类的对象
其他参考
大多数有文档的Javascript代码都有事件描述文档,可以参考JQuery的一个Widget:DatePicker的文档描述进行编写:
http://docs.jquery.com/UI/Datepicker#events
Ferris Xu
2009年04月23日
var
myListener
=
function
( e )
{
// TODO ...
}
myButton.addEventListener( " click " , myListener);
{
// TODO ...
}
myButton.addEventListener( " click " , myListener);
上面的代码为myButton添加了一个响应click事件的函数myListener。myButton对象不需要知道myListener函数是在何处以何种方式实现的,也不需要关心究竟有多少个响应函数会响应click事件,这样就可以极大的提高系统的扩展性和健壮性。
要实现这个功能,最核心的技术就是函数的传递。在Javascript中,由于函数本身也是一个对象,因此可以很方便的当参数传递,但是,在PHP中如何传递函数呢?具体的方法可以参考我的另外一篇文章:
PHP回调函数的实现方法
http://www.cnitblog.com/CoffeeCat/archive/2009/04/21/56541.html
我们已经知道如何在PHP中实现回调函数,因此就可以实现事件模型了,下面给出Event类的代码,这个类维护一个事件的响应队列,并提供注册事件、卸载事件和触发事件的方法。
class
Event {
protected $_handler = array (); // 事件处理器列表,是一个key=>value的数组,其中,key是字符串,表示事件名,value是数组,保存了响应事件的函数句柄
/* *
* 添加一个事件处理器
* 如果已经添加过了,就不重复添加了
*
* @param string 要响应的事件名
* @param string 函数名
* @param Object / string / null 作用域,也就是对象,如果为空,则表示全局作用域,如果为字符串,则表示指定的是类名,可以指定静态方法
*/
public function addEventListener( $evtName , $handler , $scope = null )
{
$item = $this -> _getListener( $evtName , $handler , $scope );
if ( is_null ( $item ) )
{
$item = array ();
$item [ ' handler ' ] = $handler ;
$item [ ' scope ' ] = $scope ;
$this -> _handler[ $evtName ][] = $item ;
}
else
{
echo ' listener ignore ' ;
}
}
/* *
* 删除一个事件处理器
*
* @param string 要响应的事件名
* @param string 函数名
* @param Object / string / null 作用域,也就是对象,如果为空,则表示全局作用域,如果为字符串,则表示指定的是类名,可以指定静态方法
*/
public function removeEventListener( $evtName , $handler , $scope = null )
{
$item = $this -> _getListener( $evtName , $handler , $scope , true );
}
/* *
* 触发函数事件
*
* @param string $evtName 事件名
* @param Array $params 调用时的参数
*/
public function fire( $evtName , $params = null )
{
if ( is_array ( $this -> _handler[ $evtName ] ) )
{
foreach ( $this -> _handler[ $evtName ] as $item )
{
if ( is_null ( $item [ ' scope ' ] ) )
{
// 全局作用域
call_user_func_array ( $item [ ' handler ' ] , $params );
}
else if ( is_string ( $item [ ' scope ' ] ))
{
// 类上的静态调用
call_user_func_array ( array ( $item [ ' scope ' ] , $item [ ' handler ' ] ) , $params );
}
else
{
// 在scope上调用
$strParams = '' ;
$strCode = ' $myobj->$fnName( ' ;
for ( $i = 0 ; $i < count ( $params ) ; $i ++ )
{
$strParams .= ( ' $params[ ' . $i . ' ] ' );
if ( $i != count ( $params ) - 1 )
{
$strParams .= ' , ' ;
}
}
$strCode = $strCode . $strParams . " ); " ;
$anonymous = create_function ( ' $fnName , $myobj , $params ' , $strCode );
$anonymous ( $item [ ' handler ' ] , $item [ ' scope ' ] , $params );
}
}
}
}
/* *
* 获取一个监听者
*
* @param string $evtName
* @param string $handler
* @param mixed $scope
* @param bool $isDelete //获取监听者以后是否删除这个监听者,默认为false
* @return Array / null
*/
private function _getListener( $evtName , $handler , $scope , $isDelete = false )
{
$ret = null ;
if ( is_array ( $this -> _handler[ $evtName ] ) )
{
foreach ( $this -> _handler[ $evtName ] as $key => $listeners )
{
if ( $listeners [ ' handler ' ] == $handler && $listeners [ ' scope ' ] == $scope )
{
$ret = $listeners ;
if ( $isDelete == true )
{
unset ( $this -> _handler[ $evtName ][ $key ] );
}
break ;
}
}
if ( count ( $this -> _handler[ $evtName ]) == 0 )
{
unset ( $this -> _handler[ $evtName ] );
}
}
return $ret ;
}
}
protected $_handler = array (); // 事件处理器列表,是一个key=>value的数组,其中,key是字符串,表示事件名,value是数组,保存了响应事件的函数句柄
/* *
* 添加一个事件处理器
* 如果已经添加过了,就不重复添加了
*
* @param string 要响应的事件名
* @param string 函数名
* @param Object / string / null 作用域,也就是对象,如果为空,则表示全局作用域,如果为字符串,则表示指定的是类名,可以指定静态方法
*/
public function addEventListener( $evtName , $handler , $scope = null )
{
$item = $this -> _getListener( $evtName , $handler , $scope );
if ( is_null ( $item ) )
{
$item = array ();
$item [ ' handler ' ] = $handler ;
$item [ ' scope ' ] = $scope ;
$this -> _handler[ $evtName ][] = $item ;
}
else
{
echo ' listener ignore ' ;
}
}
/* *
* 删除一个事件处理器
*
* @param string 要响应的事件名
* @param string 函数名
* @param Object / string / null 作用域,也就是对象,如果为空,则表示全局作用域,如果为字符串,则表示指定的是类名,可以指定静态方法
*/
public function removeEventListener( $evtName , $handler , $scope = null )
{
$item = $this -> _getListener( $evtName , $handler , $scope , true );
}
/* *
* 触发函数事件
*
* @param string $evtName 事件名
* @param Array $params 调用时的参数
*/
public function fire( $evtName , $params = null )
{
if ( is_array ( $this -> _handler[ $evtName ] ) )
{
foreach ( $this -> _handler[ $evtName ] as $item )
{
if ( is_null ( $item [ ' scope ' ] ) )
{
// 全局作用域
call_user_func_array ( $item [ ' handler ' ] , $params );
}
else if ( is_string ( $item [ ' scope ' ] ))
{
// 类上的静态调用
call_user_func_array ( array ( $item [ ' scope ' ] , $item [ ' handler ' ] ) , $params );
}
else
{
// 在scope上调用
$strParams = '' ;
$strCode = ' $myobj->$fnName( ' ;
for ( $i = 0 ; $i < count ( $params ) ; $i ++ )
{
$strParams .= ( ' $params[ ' . $i . ' ] ' );
if ( $i != count ( $params ) - 1 )
{
$strParams .= ' , ' ;
}
}
$strCode = $strCode . $strParams . " ); " ;
$anonymous = create_function ( ' $fnName , $myobj , $params ' , $strCode );
$anonymous ( $item [ ' handler ' ] , $item [ ' scope ' ] , $params );
}
}
}
}
/* *
* 获取一个监听者
*
* @param string $evtName
* @param string $handler
* @param mixed $scope
* @param bool $isDelete //获取监听者以后是否删除这个监听者,默认为false
* @return Array / null
*/
private function _getListener( $evtName , $handler , $scope , $isDelete = false )
{
$ret = null ;
if ( is_array ( $this -> _handler[ $evtName ] ) )
{
foreach ( $this -> _handler[ $evtName ] as $key => $listeners )
{
if ( $listeners [ ' handler ' ] == $handler && $listeners [ ' scope ' ] == $scope )
{
$ret = $listeners ;
if ( $isDelete == true )
{
unset ( $this -> _handler[ $evtName ][ $key ] );
}
break ;
}
}
if ( count ( $this -> _handler[ $evtName ]) == 0 )
{
unset ( $this -> _handler[ $evtName ] );
}
}
return $ret ;
}
}
下面是这个类的用法:
1:用简单函数响应事件
//
简单回调函数
function fnCallBack( $msg1 = ' default msg1 ' , $msg2 = ' default msg2 ' )
{
echo ' show msg1: ' . $msg1 ;
echo " <br />\n " ;
echo ' show msg2: ' . $msg2 ;
echo " <br />\n " ;
echo " <br />\n " ;
}
$evt = new Event();
$evt -> addEventListener( ' test ' , ' fnCallBack ' );
$evt -> fire( ' test ' , array ( ' my first message ' ) );
function fnCallBack( $msg1 = ' default msg1 ' , $msg2 = ' default msg2 ' )
{
echo ' show msg1: ' . $msg1 ;
echo " <br />\n " ;
echo ' show msg2: ' . $msg2 ;
echo " <br />\n " ;
echo " <br />\n " ;
}
$evt = new Event();
$evt -> addEventListener( ' test ' , ' fnCallBack ' );
$evt -> fire( ' test ' , array ( ' my first message ' ) );
输出效果

2:用类静态函数和对象的方法响应事件
class
MyClass
{
private $name = ' abcde ' ;
public function fnCallBack( $msg1 = ' default msg1 ' , $msg2 = ' default msg2 ' )
{
echo ' object name: ' . $this -> name;
echo " <br />\n " ;
echo ' show msg1: ' . $msg1 ;
echo " <br />\n " ;
echo ' show msg2: ' . $msg2 ;
echo " <br />\n " ;
echo " <br />\n " ;
}
public static function fnCallBacks( $msg1 = ' default msg1 ' , $msg2 = ' default msg2 ' )
{
echo ' show msg1 in static: ' . $msg1 ;
echo " <br />\n " ;
echo ' show msg2 in static: ' . $msg2 ;
echo " <br />\n " ;
echo " <br />\n " ;
}
}
$obj = new MyClass();
$evt = new Event();
$evt -> addEventListener( ' test ' , ' fnCallBacks ' , ' MyClass ' );
$evt -> addEventListener( ' test ' , ' fnCallBack ' , $obj );
$evt -> fire( ' test ' , array ( ' my first message ' ) );
{
private $name = ' abcde ' ;
public function fnCallBack( $msg1 = ' default msg1 ' , $msg2 = ' default msg2 ' )
{
echo ' object name: ' . $this -> name;
echo " <br />\n " ;
echo ' show msg1: ' . $msg1 ;
echo " <br />\n " ;
echo ' show msg2: ' . $msg2 ;
echo " <br />\n " ;
echo " <br />\n " ;
}
public static function fnCallBacks( $msg1 = ' default msg1 ' , $msg2 = ' default msg2 ' )
{
echo ' show msg1 in static: ' . $msg1 ;
echo " <br />\n " ;
echo ' show msg2 in static: ' . $msg2 ;
echo " <br />\n " ;
echo " <br />\n " ;
}
}
$obj = new MyClass();
$evt = new Event();
$evt -> addEventListener( ' test ' , ' fnCallBacks ' , ' MyClass ' );
$evt -> addEventListener( ' test ' , ' fnCallBack ' , $obj );
$evt -> fire( ' test ' , array ( ' my first message ' ) );
输出结果

可以看到,我们可以将响应事件的函数放置在任何位置,事件对象都能够正确的进行回调。
Event类的包装
Event类提供了基本的事件模型,不过在实际应用的时候,我们应该对这个类进行进一步包装,因为不同的对象有不同的事件。例如,我们之前的PHP代码是通过fire来触发相关事件的,而在实际应用的时候,我们是通过一些操作的发生来触发事件的。
举个例子,我们要开发一个新闻系统,并使新闻在添加操作的时候具有事件响应的能力,我们就需要把Event类包装在News中,如:
class
NewsEvent
extends
Event
{
public static $ADD_SUCCESS = ' add_success ' ;
public static $ADD_FAILED = ' add_failed ' ;
}
class News
{
public $title = '' ;
private $evt = null ;
public function __construct( )
{
$this -> evt = new NewsEvent();
}
// 添加一篇新闻到数据库
public function add()
{
try
{
// 在这里进行添加新闻的操作
echo ' adding news into Database
'
;
echo " <br />\n " ;
// 添加完成,触发成功事件
$this -> evt -> fire(NewsEvent :: $ADD_SUCCESS , array ( $this ));
}
catch ( Exception $e )
{
// 添加失败,触发失败事件
$this -> evt -> fire(NewsEvent :: $ADD_FAILED , array ( $this ));
}
}
// 提供了事件注册和卸载的接口
public function addEventListener( $evtName , $handler , $scope = null )
{
$this -> evt -> addEventListener( $evtName , $handler , $scope );
}
public function removeEventListener( $evtName , $handler , $scope = null )
{
$this -> evt -> removeEventListener( $evtName , $handler , $scope );
}
}
{
public static $ADD_SUCCESS = ' add_success ' ;
public static $ADD_FAILED = ' add_failed ' ;
}
class News
{
public $title = '' ;
private $evt = null ;
public function __construct( )
{
$this -> evt = new NewsEvent();
}
// 添加一篇新闻到数据库
public function add()
{
try
{
// 在这里进行添加新闻的操作
echo ' adding news into Database

echo " <br />\n " ;
// 添加完成,触发成功事件
$this -> evt -> fire(NewsEvent :: $ADD_SUCCESS , array ( $this ));
}
catch ( Exception $e )
{
// 添加失败,触发失败事件
$this -> evt -> fire(NewsEvent :: $ADD_FAILED , array ( $this ));
}
}
// 提供了事件注册和卸载的接口
public function addEventListener( $evtName , $handler , $scope = null )
{
$this -> evt -> addEventListener( $evtName , $handler , $scope );
}
public function removeEventListener( $evtName , $handler , $scope = null )
{
$this -> evt -> removeEventListener( $evtName , $handler , $scope );
}
}
以下是调用代码:
function
fnAddSuccess(
$news
)
{
echo ' news: ' . $news -> title . ' has been added ' ;
}
function fnAddFailed( $news )
{
echo ' news: ' . $news -> title . ' is NOT added ' ;
}
$myNews = new News();
$myNews -> title = ' my news title ' ;
$myNews ->addEventListener(NewsEvent:: $ADD_SUCCESS , fnAddSuccess );
$myNews ->addEventListener(NewsEvent:: $ADD_FAILED , fnAddFailed );
$myNews ->add();
{
echo ' news: ' . $news -> title . ' has been added ' ;
}
function fnAddFailed( $news )
{
echo ' news: ' . $news -> title . ' is NOT added ' ;
}
$myNews = new News();
$myNews -> title = ' my news title ' ;
$myNews ->addEventListener(NewsEvent:: $ADD_SUCCESS , fnAddSuccess );
$myNews ->addEventListener(NewsEvent:: $ADD_FAILED , fnAddFailed );
$myNews ->add();
观察粗体的代码,可以看到,这个实现方式已经和我们的Javascript的方式非常相似了。
输出结果:

至此,类似Javascript(或者是ActionScript3.0)的事件模型,已经在PHP中实现了。
二次开发的注意事项
继承还是聚合
您可以通过包装Event类来使您自己的对象具有事件处理的能力,一般来说,包装Event类有2种方法,继承和聚合,“Event类的包装”中就是使用聚合来进行包装的,下面还是以新闻系统为例子,提供继承的示例:


























调用示例













输出结果

继承的代码比聚合更简洁,但是我还是建议使用聚合。因为PHP是单继承模式,所以如果使用了继承来包装Event类,那么您的类就不能再继承其他类了。除非您确定您的类不会继承别的类,否则就不要使用继承。利用聚合将使代码的灵活性更大。
事件描述文档
如果您的类提供了事件机制,这就意味着您的类可以被别人扩展。例如,前面我的News提供了事件机制,那么别人就可以通过响应onAddSuccess来做一些其他操作(比如发邮件通知管理员)。所以,提供清晰的事件描述文档十分重要。
编写要点
1:事件列表
也就是对象提供的所有事件响应列表,例如前面的News,事件列表就是:
add_success
add_failed
2:触发事件的条件
表示对象提供的事件在满足什么条件下触发,例如:
add_success
成功添加一篇新闻以后触发
add_failed
一篇新闻添加失败后触发
3:回调函数原型
表示用户应该如何来定义用于响应的回调函数,同时要说明函数的参数是什么含义,例如:
add_success
成功添加一篇新闻以后触发
回调函数原型:
function ( $news )
news表示刚刚添加成功的News类的对象
add_failed
一篇新闻添加失败后触发
回调函数原型:
function ( $news )
news表示刚刚添加失败的News类的对象
其他参考
大多数有文档的Javascript代码都有事件描述文档,可以参考JQuery的一个Widget:DatePicker的文档描述进行编写:
http://docs.jquery.com/UI/Datepicker#events
Ferris Xu
2009年04月23日
转载于:https://blog.51cto.com/myceo/725432