概述
tp6的事件,是通过观察者模式设计的,事件的基本原理是,首先在系统初始化的时候注册一些监听事件,然后观察事件的触发,只要事件触发的时候就就能执行这些观察者。
事件机制再不修改框架代码的情况下,提升了框架的扩展性。
源码解析
别名功能
事件的别名,触发的时候可以直接触发这个别名,系统自定义的基本事件别名已经写死在事件类的属性里了。
class Event
{
// 事件的别名,触发的时候可以直接触发这个别名
protected $bind = [
'AppInit' => event\AppInit::class,
];
// 批量注册别名
public function bind(array $events)
{
$this->bind = array_merge($this->bind, $events);
return $this;
}
}
事件配置,因为事件相关的配置,最好放在一个配置文件里,所以通过key来分开
return [
'bind' => [
'UserLogin' => 'app\event\UserLogin',
],
];
然后系统初始化加载配置的时候,会批量加载别名,配置文件如下
$this->event->bind($event['bind']);
绑定别名后,就可以通过别名触发事件
$this->event->trigger('AppInit');
当然这个别名不设置也是可以的,只要触发的时候,直接触发事件类名称就好了,不要触发别名,下面触发事件的方式和上面别名触发的方式效果是一样的
$this->event->trigger(AppInit::class);
需要注意的是,这个别名最好是大写开头,因为事件自动订阅模式,截取on开头后,第一个字符是大写,详情见下面的事件订阅
事件的监听
事件的监听就是观察者,是事件的核心
class Event
{
// 监听类的类名
protected $listener = [];
// 批量注册监听
public function listenEvents(array $events)
{
foreach ($events as $event => $listeners) {
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
$this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners);
}
return $this;
}
// 单个监听
public function listen(string $event, $listener)
{
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
$this->listener[$event][] = $listener;
return $this;
}
}
由上我门可以知道,监听基本的参数是事件的名称、监听类(到时候会执行监听类里的handle方法)或者闭包,例如批量监听的时候,是通过系统配置的方式
return [
'listen' => [
'UserLogin' => ['app\listener\UserLogin'],
],
];
这个配置文件,会在系统初始化的是被批量注册
$this->event->listenEvents($event['listen']);
当然,我们还可以注册单个
// 类名称,触发的时候会执行UserLogin里的handle方法
Event::listen('UserLogin', UserLogin::class);
// 闭包
Event::listen('UserLogin', function($user) {
//
});
// 对象的具体方法
$this->listen('UserLogin', [$userLoginObject, 'onUserLogin']);
订阅功能
订阅的概念跟关注差不多,类似一次性可以关注多个明星,因此我们也可以一次性订阅多个事件,例如用户相关的事件,可以放在用户订阅类里,这样好管理事件的监听
// 事件类
class Event
{
// 批量订阅,可以传入字符串或者对象
public function subscribe($subscriber)
{
// 如果不是数组的,转成数组,支持订阅单个
$subscribers = (array) $subscriber;
foreach ($subscribers as $subscriber) {
if (is_string($subscriber)) {
$subscriber = $this->app->make($subscriber);
}
// 如果订阅类里有subscribe方法,代表需要手动订阅
if (method_exists($subscriber, 'subscribe')) {
// 手动订阅,在该方法里,手工动态监听事件
$subscriber->subscribe($this);
} else {
// 智能订阅,根据规则,自动注册事件监听
$this->observe($subscriber);
}
}
return $this;
}
// 根据方法on+事件别名名称的方法,自动注册对应事件监听
public function observe($observer, string $prefix = '')
{
if (is_string($observer)) {
$observer = $this->app->make($observer);
}
$reflect = new ReflectionClass($observer);
$methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC);
if (empty($prefix) && $reflect->hasProperty('eventPrefix')) {
$reflectProperty = $reflect->getProperty('eventPrefix');
$reflectProperty->setAccessible(true);
$prefix = $reflectProperty->getValue($observer);
}
foreach ($methods as $method) {
$name = $method->getName();
if (0 === strpos($name, 'on')) {
$this->listen($prefix . substr($name, 2), [$observer, $name]);
}
}
return $this;
}
}
同样,我们可以同配置文件,批量订阅
return [
'subscribe' => [
'app\subscribe\User',
],
];
系统初始化的时候,会加载配置文件
if (isset($event['subscribe'])) {
$this->event->subscribe($event['subscribe']);
}
当然,我们也可以动态订阅
Event::subscribe('app\subscribe\User');
订阅类有subscribe方法,手动监听到指定的方法
class User
{
public function onUserLogin($user)
{
// UserLogin事件响应处理
}
public function subscribe(Event $event)
{
$event->listen('UserLogin', [$this,'onUserLogin']);
}
}
订阅类没有subscribe方法,自动监听
class User
{
// UserLogin必须在事件别名
public function onUserLogin($user)
{
// UserLogin事件响应处理
}
}
事件的触发
我们一般在可能需要扩展的地方触发响应的事件,触发逻辑如下所示
class Event
{
public function trigger($event, $params = null)
{
// 由此可知,触发可以传入事件类的对象
if (is_object($event)) {
// 对象的话,把对象作为参数传入观察者
$params = $event;
$event = get_class($event);
}
// 由此可知,触发可以传入事件类的别名
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
$result = [];
$listeners = $this->listener[$event] ?? [];
$listeners = array_unique($listeners, SORT_REGULAR);
// 执行事件的所有监听
foreach ($listeners as $key => $listener) {
// 触发可以传入参数
$result[$key] = $this->dispatch($listener, $params);
}
// 可以返回每个监听者的返回值
return $result;
}
// 执行监听者
protected function dispatch($event, $params = null)
{
if (!is_string($event)) {
// 闭包类型的监听者
$call = $event;
} elseif (strpos($event, '::')) {
// 静态方法类型的监听者
$call = $event;
} else {
// 类名称类型的监听者
$obj = $this->app->make($event);
$call = [$obj, 'handle'];
}
return $this->app->invoke($call, [$params]);
}
}
可以通过别名触发
$this->event->trigger("AppInit");
可以通过,监听类名称触发
$this->event->trigger(AppInit::class);
可以通过对象触发
$this->event->trigger(new UserLogin([
'login_time'=>time()// 事件对象可以设置一些参数,如果事件对象有这个属性的话
]));
可以通过静态方法触发,并且传入参数
$this->event->trigger("events\UserLogin::OnLogin",[$userId]);