abei1982 2017-05-22 14:54:51 20932次浏览 14条回复 30 13 0

相信上一节你一定看了观察者模式,我们现在开始说Yii2的事件,请大家用观察者的思维去看它。

为了让话题轻松一点,我们模拟一个场景,假设经理小X要北哥做一个登陆,登陆后还要做另一些事情:

  • 本地留一个log
  • 告诉登陆者的朋友它登陆了
  • 发送一个邮件给管理员

如何实现“另一些事情”那?他们将来要具有多变性,如何保证他们最小程度污染登陆逻辑那?

这时我突然想到了“耦合度最低、但是依然牛逼交互中的观察者模式”,话说我们自己又何尝不是那,白百合被小鲜肉摸了一下屁股而已,有人发朋友圈、有人发微博、有人发声明,观察者是真操碎了心。????

@@nai8@@

先来熟悉一下登陆的代码 app\controllers\UserController.php

class UserController extends Controller {
	
	public function actionIndex(){
		// 这里有一些代码.....
		Yii::$app->user->login($user);
		//	todo 登陆后要做的事情				
	}
}

没错,为了实现经理的需求,我必须用观察者实现这段逻辑,因为它具有很强的扩展性,能轻松应付小X经理多变的性格,随时增减登陆后的事情。

而Yii中有一个观察者的深度执行者,那就是事件机制

挖掘主题和观察者

首先我们要知道一切都是因为会员登陆,这就是主题。因此我们要为登陆起一个事件名字,对于事件我们喜欢用大写的常量标识,这就类似于js中的click、change这些关键词,它代表一些事情发生了。

根据观察者模式的原理,在 Yii::$app->user->login($user); 之前,我们需要订阅(事件的绑定),登陆后需要通知订阅者(事件触发)。

好,从需求看现在一共有三个观察者,我们暂时命名为

  • OLog 记录日志
  • Admin 给管理员发邮件
  • Friend 通知登陆者朋友

我们先来实现这些观察者

// OLog   app\models\OLog.php
class OLog {
	static public function add($event){
		echo "我记录了一条登陆记录";
	}
}
// Admin app\models\Admin.php
class Admin {
	static public function sendMail($event){
		echo "我给管理员发了邮件";
	}
}
// User app\models\User.php
class User {
	static public function notifyFirend($event){
		echo "告诉了朋友们我登陆了";
	}
}

上面三个类,我们实现了每个观察者自行的代码,你一定注意到了,这些方法通通有一个叫做$event的形参,它会将本次事件一些必要的参数传递给每个观察者的方法,本文后面会对其有讲解。

ok

观察者订阅主题(事件的绑定)

接下来我们要让三个观察者订阅登陆主题,就是事件中的绑定,它应该在登陆之前就完成。

为了实现方便,我决定在 UserController 的构造函数里做这个事情 app\controllers\UserController.php

class UserController extends Controller {
	
	//	定义事件名字
	const EVENT_USER_LOGIN = 'user_login';
	
	public function __construct(){
		//	绑定事件
		$this->on(self::EVENT_USER_LOGIN,['app\models\OLog','add']); 
		$this->on(self::EVENT_USER_LOGIN,['app\models\Admin','sendMail']); 
		$this->on(self::EVENT_USER_LOGIN,['app\models\User','notifyFirend']); 
	}
	
	public function actionIndex(){
		.....
		//	login				
	}
}

因为我知道Yii的 Component 类引入了Event事件,所有继承于Component的类都可以使用它,Controller继承了Component类。

我们可以通过

$this->on("事件名称","方法")

绑定一个方法到某个指定事件上,这个方法可以是一个全局的方法、一个类的静态方法、一个对象的方法,还能是一个匿名方法,这个后续会讲到。

本次我用的是类的静态方法。

ok,订阅(事件的绑定)完活。

主题通知观察者(事件的触发)

接下来就是等待,等待某个会员登陆后通知所有我们上面绑定的方法,那么如何通知那?这就是事件的触发,Yii已经为我们提供了方法。

app\controllers\UserController.php

class UserController extends Controller {
	
	//	定义事件名字
	const EVENT_USER_LOGIN = 'user_login';
	
	public function __construct(){
		//	绑定事件
		$this->on(self::EVENT_USER_LOGIN,['app\models\OLog','add']); 
		$this->on(self::EVENT_USER_LOGIN,['app\models\Admin','sendMail']); 
		$this->on(self::EVENT_USER_LOGIN,['app\models\User','notifyFirend']); 
	}
	
	public function actionIndex(){
		// 这里有一些代码.....
		Yii::$app->user->login($user);
		$this->trigger(self::EVENT_USER_LOGIN); 						
	}
}

没错,就是一句

$this->trigger(self::EVENT_USER_LOGIN); 	

它通知了所有绑定了该事件的方法,写日志的写日志,发邮件的发邮件。

事件成功应用于此。

完了么?

没完,你应该发现了,这个代码有一个问题,就是trigger函数的确告诉了所有的订阅者会员登陆了,但是,但是它没有告诉是哪个会员登陆了。。。。。

那观察者如何发邮件、如何群发好友、如何如何那?

还记得我们实现观察者类时候的那个形参么$event,我们知道它能接收一些事件相关信息,但是,是谁传递给他们的那?

这就要欢迎trigger的第二个参数出场了

// Component类中
public function trigger($name, Event $event = null)

我们可以传递一个事件类对象给触发函数,你可能有点蒙,简单点说就是Yii中有一个与事件紧密相关的 yii\base\Event 类,它封装了与事件相关的有关数据,并提供一些功能函数作为辅助。

我们可以自己定义事件类,继承于它就完事了。

开始吧,这个事件类能帮我把会员的ID传递给每个观察者。

现在我们在@app下建立一个events的文件夹,新建一个类叫做UserLoginEvent.php

// event/UserLoginEvent.php
namespace app\events;

use yii\base\Event;

class UserLoginEvent extends Event {

    public $userId = 0;
}

这样就完事了,现在我们重写触发函数。 app\controllers\UserController.php

use app\events\UserLoginEvent;

class UserController extends Controller {
	
	.......
	.......
	
	public function actionIndex(){
		// 这里有一些代码.....
		Yii::$app->user->login($user);
		
		$event = new UserLoginEvent();
		$event->userId = $user->id;
		
		$this->trigger(self::EVENT_USER_LOGIN,$event); 						
	}
}

这样$event对象就带着会员id飞鸽传书到每个订阅者方法中去了。

我们看看订阅者如何使用它那?

// User app\models\User.php
class User {
	static public function notifyFirend($event){
		$userId = $event->userId;
		echo "告诉了朋友们我登陆了";
	}
}

看明白了吧~

ok,到此我实现了小X经理的需求,开始提交代码了。

想知道小X经理看到后的结果么?等北哥下回分解。

觉得很赞
  • 回复于 2017-05-22 16:01 举报

    其实Yii2中登陆login本来就已经有事件了.Yii::$app->user->login($user);登陆成功会调用afterLogin方法,触发EVENT_AFTER_LOGIN

    protected function afterLogin($identity, $cookieBased, $duration)
        {
            $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent([
                'identity' => $identity,
                'cookieBased' => $cookieBased,
                'duration' => $duration,
            ]));
        }
    

    返回了完整的用户信息.所以事件没必要新增,UserLoginEvent也不需要了.

    3 条回复
    回复于 2017-05-22 17:15 回复

    只的,这是一个系列情景教程,下一步才会设计系统自带的事件,本篇仅仅让大家熟悉什么是事件,内容太多不好接受。

    回复于 2017-10-06 16:01 回复

    Yii2中登陆login有事件了,假如现在初学者不是用的login呢?用的是lo或者og其他杂七杂八的。

    回复于 2017-10-09 18:01 回复

    那就用楼主的方法咯,自己定义.

    , 觉得很赞
  • 回复于 2017-05-24 22:50 举报

    温习一遍,理解更深刻

  • 回复于 2017-05-26 21:53 举报

    写的不错.赞一个

    1 条回复
    回复于 2017-05-26 22:00 回复

    灰常感谢。

    觉得很赞
  • 回复于 2017-10-06 15:40 举报

    UserController.php中不应该使用__constrouct,因为会报如下错误“Call to a member function getUniqueId() on null”,应该使用public function init()来初始化。

    , , 觉得很赞
  • 回复于 2017-10-06 15:41 举报

    非常感谢作者,让我理解了事件的一些基本的操作概念。

  • 回复于 2017-10-10 18:07 举报

    简洁实用,北哥加油。

  • 回复于 2017-12-26 17:56 举报
  • 回复于 2018-02-08 11:29 举报

    有水准的文章不错

  • 回复于 2018-02-13 11:38 举报

    如果还要发邮件呢?

    是这么写吗?

    userId属性会传入到发送邮件函数了,但是发送邮件函数可能不会用到。
    同样,email属性也会传入到用户登录通知add函数里。但是登录通知可能不会用到email。
    这样可以吗?

    
    	public function actionIndex(){
    		//这里有一些代码....
    		$event = new UserLoginEvent();
    		$event->userId = 999;
    		$event->email = 'sssss@163.com';//这里是新增的
    
    		Yii::$app->user->login($user);
    		$this->trigger(self::EVENT_USER_LOGIN,$event);
    	}
        
        namespace app\events;
        use yii\base\Event;
        class UserLoginEvent extends Event{
            public $userId = 0;
            public $email = '';//这里是新增的
        }
    
  • 回复于 2018-03-07 00:30 举报

    首先要明白在哪里事件绑定处理器,在哪里触发事件。

    事件一旦被触发,事件绑定的处理器被执行 。 逻辑一定要明白,否则被懵逼!

  • 回复于 2018-07-17 16:32 举报

    很给力!

  • 回复于 2018-09-01 17:07 举报

    讲的很清楚

  • 回复于 2020-04-09 16:40 举报

    真的讲的很清楚了,帮助很大的

  • 回复于 2021-10-22 11:10 举报

    我TM反手就是一个赞

您需要登录后才可以回复。登录 | 立即注册