代码改变世界

YII 事件event和行为Behavior

2014-03-04 18:42  youxin  阅读(1256)  评论(0编辑  收藏  举报

To declare an event in your CComponent child class, you should add a method with a
name starting with on. For example, if you add the onRegister method, you will get a
corresponding event declared.
A method used to declare an event becomes the default
event handler.
Typically, events are used like this:
1 Declare an event by adding a corresponding method
2Attach one or multiple event handlers
3 The component raises an event by using the CComponent::raiseEvent method
4 All subscribed handlers are called automatically

让我们来看看怎么把一个事件句柄附加到事件上去。我们可以通过CComponent::attachEventHandler方法来实现。它接受2个参数:

 

 

    • $name: 事件名
    • $handler: 事件句柄。 这里应该使用一个标准的PHP回调。
 

在PHP中,我们有几种方法定义一个回调:

 

    • 使用一个全局函数,只要通过它的名字,像’my_function’这样的字符串。
    • 使用一个静态类方法,你应该传递一个数组: array('ClassName', 'staticMethodName') .
    • 使用对象方法: array($object, 'objectMethod').
    • 你可以创建和传递匿名函数,通过使用create_function:
 
$component->attachEventHandler('onClick', create_function('$event', 'echo "Click!";'));
 
    • 从PHP5.3以后,你可以直接使用匿名函数,不需create_function:
 
$component->attachEventHandler('onClick', function($event){
    echo "Click!";
});
 

当你使用CComponent::attachEventHandler时候,事件句柄被添加到句柄列表的尾部。

 
    • 了使得你的代码精简,你可以使用组件属性来管理这些事件句柄:
 
$component->onClick=$handler; // 或者:$component->onClick->add($handler);
 
    • 为了更精确的管理事件句柄,你可以使用CComponent::getEventHandlers来获取句柄列表(CList),并且使用它。例如,你可以用下面的代码来附加事件句柄,跟attachEventHandler一样。
 
$component->getEventHandlers('onClick')->add($handler);
 
    • 添加事件句柄到句柄列表头:
 
$component->getEventHandlers('onClick')->insertAt(0, $handler);
    • 删除具体的句柄,你可以使用CComponent::detachEventHandler:
 
$component->detachEventHandler('onClick', $handler);
 
    • 或者像上面的那样获取句柄列表并且从表中删除一些句柄。
 

CComponent::检查指定的事件是否在组件中定义了。
CComponent::hasEventHandler检查指定的事件是否有句柄依附。

 

既然我们都知道了怎样定义和使用这些句柄,那么我们来回顾下一些真实的例子吧。

 
    • 用gzip压缩你的应用输出,来节省客户带宽,来加速页面加载时间,是件司空见惯的事情。如果你有权限能调整你的服务器,你可以让它这样,但是在一些环境下如共享主机,你就不能了。
    • 幸运的是PHP能使用输出缓冲和ob_gzhandler来压缩应用输出。要这样做的话,我们得在应用程序开始的时候开始缓冲输出,并且在它结束的时候释放压缩的输出。
    • 在我们的例子中,Yii应用程序有2个事件迟早有用:CApplication::onBeginRequest和CApplication::onEndRequest。我们现在就来使用它们,将以下代码放置在index.php中的应用程序配置之后,运行之前:
...
require_once($yii);
$app = Yii::createWebApplication($config);
// 在应用程序的开始附加一个句柄
Yii::app()->onBeginRequest = function($event)
{
    // 用ob_gzhandler来开始输出缓冲
    return ob_start("ob_gzhandler");
};
// 附加一个句柄到应用结束
Yii::app()->onEndRequest = function($event)
{
    // 释放输出缓冲
    return ob_end_flush();
};
$app->run();
 

在Yii核心类中定义了很多便捷的事件。你可以使用你喜爱IDE(集成开发环境)在framework文件夹里面通过“function on”的字样来查找到它们。

 

我们来看另外一个例子。在Yii中,你可以使用Yii::t来把字符串翻译成不同的语言。我们都追求完美的项目,所以所有的语言翻译都必须是最新的。倘若它们不是,我们将非常乐意收到您的邮件提醒。

在这里事件再次派上用场。尤其是当传递给Yii::t的翻译字符串丢失时调用CMessageSource::onMissingTranslation事件

这次我们使用应用配置文件protected/config/main.php来附加事件句柄:

...
'components' => array(
       ...
       // messages组件类默认的是CPhpMessageSource
       'messages' => array(
        // 使用静态类方法作为事件句柄
       'onMissingTranslation' => array('MyEventHandler',
            'handleMissingTranslation'),
        ),
        ... 
)
...
 

现在我们应该实现这个句柄,创建protected/components/MyEventHandler.php文件并输入以下代码:

 
class MyEventHandler
{
    static function handleMissingTranslation($event)
    {
        // 这个事件的事件类是 CMissingTranslationEvent
        // 因此我们能获得这个message的一些信息
        $text = implode("\n", array(
            'Language: '.$event->language,
            'Category:'.$event->category,
            'Message:'.$event->message
        ));
        // 发送邮件
        mail('admin@example.com', 'Missing translation', $text);
    }
}
 

我们来看最后一个例子。我们有一个博客应用程序,我们需要在有新评论(Comment)提交到博客(Post)的时候就发一份邮件

Comment是个用Gii生成的标准AR模型。Post也同样是Gii生成的模型,除了一些自定义的方法。我们需要定义一个事件NewCommentEvent来存储Post和Comment 两个模型和句柄类Notifier,它将处理工作。

    1. 让我们开始创建protected/components/NewCommentEvent.php:
 
class NewCommentEvent extends CModelEvent {
    public $comment;
    public $post;
}
 

这相当简单。我们只是添加了2个属性。

    1. 现在我们去看看protected/models/Post.php。为了突出显示添加了什么,我们省略了所有标准的AR方法。
 
class Post extends CActiveRecord {
    // 给添加评论到当前提交上定义方法
    function addComment(Comment $comment){
        $comment->post_id = $this->id;
 
        // 创建事件类实例
        $event = new NewCommentEvent($this);
        $event->post = $this;
        $event->comment = $comment;
        
        // 触发事件 trigger event
        $this->onNewComment($event);
        return $event->isValid;
    }
 
    // 定义 onNewComment 事件
    public function onNewComment($event) {
        // 事实上事件是在这里触发的.这样的话我们可以使用
        // onNewComment 方法来取代 raiseEvent.
        $this->raiseEvent('onNewComment', $event);
    }
}
    1. 现在,实现Notifie。创建 protected/components/Notifier.php输入如下代码:
 
class Notifier {
    function comment($event){
        $text = "There was new comment from {$event->comment->author} 
on post {$event->post->title}";
        mail('admin@example.com', 'New comment', $text);
    }
}
 
    1. 现在是时候在protected/controllers/ PostController.php中一起进行操作了。
 
class PostController extends CController
{
    function actionAddComment()
    {
        $post = Post::model()->findByPk(10);
        $notifier = new Notifier();
        
        // 附加事件句柄
        $post->onNewComment = array($notifier, 'comment');
        // 在真实的应用中,数据应来自$_POST
            $comment = new Comment();
            $comment->author = 'Sam Dark';
            $comment->text = 'Yii events are amazing!';
        // 添加评论
            $post->addComment($comment);
    }
}
 
  1. 所有的评论都将被添加后,管理员将会收到一份相关的邮件。
  2. 还有更多...

     

    附加句柄这步不总是必需的。我们来看看怎样处理一个通过重写基类方法定义在已经存在的组件当中的事件。例如,我们有个表格模型UserForm来收集关于我们应用的用户的一些信息,并且我们要求通过用户输入的姓和名来获取完整的姓名。

     

    幸运的,在CModel里面定义有CModel::afterValidate方法,CModel是提供给所有Yii模型的基类,当然包括表单模型了。在成功的表单验证后,这个方法就被调用了。让我们在UserForm模型中使用它吧:

    class UserForm extends CFormModel
    {
        public $firstName;
        public $lastName;
        public $fullName;
        
        public function rules()
        {
            return array(
                // 姓和名是必填的
                array('firstName, lastName', 'required'),
            );
        }
        
        // 这里的$event参数是CEvent 实例
        // 这个实例在调用事件方法的时候被创建了
        // 在我们的案例,它发生在CModel::afterValidate()里面。
        function afterValidate()
        {
            // 模型已经被填充了有效数据的时候调用这个方法
            // 因此我们安全可靠的使用它:
            $this->fullName = $this->firstName.' '.$this->lastName;
            
            // 把事件传递给父类很重要。
            // 因此其他事件句柄都被调用了。
            return parent::afterValidate();
        }
    }
     

    We need to call parent method inside of afterValidate because parent implementation calls onAfterValidate that actually raises events:

    protected function afterValidate()
    {
        $this->onAfterValidate(new CEvent($this));
    }
     

    一个事件方法名应该一直被定义成这个形式:eventHandler($event){...},其中的$event是CEvent的实例。CEvent类就只有2个属性:sender和handled(发送者和已经处理的)。第一个包含一个叫当前事件的对象,而第二个通过设置自己为false来阻止调用其他尚未被执行的句柄。

     

    上面描述的方法能用来定制你的Active Record 模型和实现你模型的行为。

     

 

 

参考:

http://yiibook.com/book/yii1.1_application_development_cookbook/chapter-1

更多:

http://www.yiiframework.com/wiki/44/behaviors-events

http://www.yiiframework.com/wiki/481/how-to-implement-an-event-and-attach-it-in-a-behavior/

http://blog.csdn.net/kun_php/article/details/8564325

http://www.cnblogs.com/JosephLiu/archive/2011/12/12/2285078.html

http://www.cnblogs.com/davidhhuan/archive/2012/01/19/2326123.html

http://blog.csdn.net/wclovesjl/article/details/11571127

http://luchuan.iteye.com/blog/910251