Laravel 的事件提供了一个简单的观察者实现,能够订阅和监听应用中发生的各种事件。事件类保存在
app/Events
目录中,而这些事件的监听器则被保存在app/Listeners
目录下。这些目录只有当你使用 Artisan 命令来生成事件和监听器时才会被自动创建。
观察者模式的核心是将客户端组件(观察者)从中心类(主体)中分离出来。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
关键代码:在抽象类里有一个 ArrayList 存放观察者们。
应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。
事件系统的执行过程大致为:
Laravel 应用中的
EventServiceProvider
有个listen
数组包含所有的事件(键)以及事件对应的监听器(值)来注册所有的事件监听器,可以灵活地根据需求来添加事件。例如,让我们增加一个OrderShipped
事件:
/**
* 应用程序的事件监听器映射。
*
* @var array
*/
protected $listen = [
'App\Events\OrderShipped' => [
'App\Listeners\SendShipmentNotification',
],
];
EventServiceProvider
为服务提供者类,在config/app.php
文件中作为providers
数组的元素被使用,laravel启动过程中,在Illuminate\Foundation\Application::registerConfiguredProviders
方法中被加载。
在EventServiceProvider
父类Illuminate\Foundation\Support\Providers\EventServiceProvider
方法boot
中,读取listen
数组加载到laravel的事件系统中:
/**
* Register the application's event listeners.
*
* @return void
*/
public function boot()
{
foreach ($this->listens() as $event => $listeners) {
foreach ($listeners as $listener) {
Event::listen($event, $listener);
}
}
foreach ($this->subscribe as $subscriber) {
Event::subscribe($subscriber);
}
}
Illuminate\Support\Facades\Event
通过魔术方法把调用Illuminate\Events\Dispatcher
的相应方法;Illuminate\Events\Dispatcher
对象中保存者事件与监听者的关联关系;
事件通常是在
EventServiceProvider
类的 $listen 数组中注册,但是,你也可以在EventServiceProvider
类的boot
方法中注册基于事件的闭包。
/**
* 注册应用程序中的任何其他事件。
*
* @return void
*/
public function boot()
{
parent::boot();
Event::listen('event.name', function ($foo, $bar) {
//
});
}
listen
方法中将会以事件名为键名、一个闭包函数(监听器)为键值添加到listeners
属性数组中。
你可以在注册监听器时使用 *
通配符参数,这样能够在同一个监听器上捕获多个事件。通配符监听器接受事件名称作为其第一个参数,并将整个事件数据数组作为其第二个参数:
Event::listen('event.*', function ($eventName, array $data) {
//
});
使用事件名中含有通配符的事件将会被添加到$wildcards
属性数组中。
事件类其实就只是一个保存与事件相关的信息的数据容器。例如,假设我们生成的 OrderShipped
事件接收一个 Eloquent ORM 对象:
namespace App\Events;
use App\Order;
use Illuminate\Queue\SerializesModels;
class OrderShipped
{
use SerializesModels;
public $order;
/**
* 创建一个事件实例。
*
* @param Order $order
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
}
自定义的事件需要添加到EventServiceProvider
的listen
数组中,在laravel启动中自动被加载到Illuminate\Events\Dispatcher
中。
在客户端代码中使用Event::listen
方式进行注册,以fpm模式运行的laravel工程,仅在当前请求过程中有效。
下次请求会重新创建Illuminate\Events\Dispatcher
类和重新从EventServiceProvider
的listen
数组中加载注册事件。
接下来,让我们看一下例子中事件的监听器。事件监听器在 handle
方法中接收事件实例。 event:generate
命令会自动加载正确的事件类和在 handle
加入的类型提示。在 handle
方法中,你可以执行任何必要的响应事件的操作:
namespace App\Listeners;
use App\Events\OrderShipped;
class SendShipmentNotification
{
/**
* 创建事件监听器。
*
* @return void
*/
public function __construct()
{
//
}
/**
* 处理事件
*
* @param OrderShipped $event
* @return void
*/
public function handle(OrderShipped $event)
{
// 使用 $event->order 来访问 order ...
}
}
Dispatcher
的makeListener
会根据监听器类型(类还是闭包函数)包装监听器:
/**
* Register an event listener with the dispatcher.
*
* @param \Closure|string $listener
* @param bool $wildcard
* @return \Closure
*/
public function makeListener($listener, $wildcard = false)
{
if (is_string($listener)) {
return $this->createClassListener($listener, $wildcard);
}
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return $listener($event, $payload);
}
return $listener(...array_values($payload));
};
}
如果listen
方法参数中的监听器是类,将会执行createClassListener
方法包装成一个闭包函数:
/**
* Create a class based listener using the IoC container.
*
* @param string $listener
* @param bool $wildcard
* @return \Closure
*/
public function createClassListener($listener, $wildcard = false)
{
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return call_user_func($this->createClassCallable($listener), $event, $payload);
}
return call_user_func_array(
$this->createClassCallable($listener), $payload
);
};
}
/**
* Create the class based event callable.
*
* @param string $listener
* @return callable
*/
protected function createClassCallable($listener)
{
list($class, $method) = $this->parseClassCallable($listener);
if ($this->handlerShouldBeQueued($class)) {
return $this->createQueuedHandlerCallable($class, $method);
}
return [$this->container->make($class), $method];
}
/**
* Parse the class listener into class and method.
*
* @param string $listener
* @return array
*/
protected function parseClassCallable($listener)
{
return Str::parseCallback($listener, 'handle');
}
createClassCallable
方法会区分监听器是否应该被放入队列中执行。
parseClassCallable
方法解析监听器字符串。如果只有类名,将使用handle
方法作为该监听器类的回调方法。可以通过@
分割类名和方法名,指定要回调的方法名称。
你可以通过在监听器的
handle
方法中返回false
来阻止事件被其他的监听器获取。
如果你的监听器中要执行诸如发送邮件或者进行 HTTP 请求等比较慢的任务,你可以选择将其丢给队列处理。在开始使用监听器队列之前,请确保在你的服务器或本地开发环境中能够配置并启动 队列 监听器。
要指定监听器启动队列,只需将
ShouldQueue
接口添加到监听器类。由 Artisan 命令event:generate
生成的监听器已经将此接口导入到当前命名空间中,因此你可以直接使用它:
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
//
}
在使用createClassCallable
方法创建类回调方法时,通过handlerShouldBeQueued
方法检测该类是否实现了ShouldQueue
接口,从而判断是否应该放入队列中执行。
/**
* Determine if the event handler class should be queued.
*
* @param string $class
* @return bool
*/
protected function handlerShouldBeQueued($class)
{
try {
return (new ReflectionClass($class))->implementsInterface(
ShouldQueue::class
);
} catch (Exception $e) {
return false;
}
}
createQueuedHandlerCallable
将会返回一个基于队列执行的回调函数:
/**
* Create a callable for putting an event handler on the queue.
*
* @param string $class
* @param string $method
* @return \Closure
*/
protected function createQueuedHandlerCallable($class, $method)
{
return function () use ($class, $method) {
$arguments = array_map(function ($a) {
return is_object($a) ? clone $a : $a;
}, func_get_args());
if ($this->handlerWantsToBeQueued($class, $arguments)) {
$this->queueHandler($class, $method, $arguments);
}
};
}
队列执行是一个比较复杂的过程。
如果要分发事件,你可以将事件实例传递给辅助函数
event
。这个函数将会把事件分发到所有已经注册的监听器上。因为辅助函数event
是全局可访问的,所以你可以在应用中的任何地方调用它:
namespace App\Http\Controllers;
use App\Order;
use App\Events\OrderShipped;
use App\Http\Controllers\Controller;
class OrderController extends Controller
{
/**
* 将传递过来的订单发货。
*
* @param int $orderId
* @return Response
*/
public function ship($orderId)
{
$order = Order::findOrFail($orderId);
// 订单的发货逻辑...
event(new OrderShipped($order));
}
}
下面是辅助方法event
的定义:
if (! function_exists('event')) {
/**
* Dispatch an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
* @return array|null
*/
function event(...$args)
{
return app('events')->dispatch(...$args);
}
}
event
方法内调用了Dispatcher
的dispatch
方法,下面是dispatch
方法的源代码:
/**
* Fire an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
* @return array|null
*/
public function dispatch($event, $payload = [], $halt = false)
{
// When the given "event" is actually an object we will assume it is an event
// object and use the class as the event name and this event itself as the
// payload to the handler, which makes object based events quite simple.
list($event, $payload) = $this->parseEventAndPayload(
$event, $payload
);
if ($this->shouldBroadcast($payload)) {
$this->broadcastEvent($payload[0]);
}
$responses = [];
foreach ($this->getListeners($event) as $listener) {
$response = $listener($event, $payload);
// If a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
if ($halt && ! is_null($response)) {
return $response;
}
// If a boolean false is returned from a listener, we will stop propagating
// the event to any further listeners down in the chain, else we keep on
// looping through the listeners and firing every one in our sequence.
if ($response === false) {
break;
}
$responses[] = $response;
}
return $halt ? null : $responses;
}
dispatch
主要功能是遍历执行该事件关联的监听器。
可以发现当事件监听器回调方法返回false
时,将跳出循环,不再触发之后的监听器。
getListener
方法将从listeners
和wildcards
数组中取出包括使用通配符事件名的监听器:
/**
* Get all of the listeners for a given event name.
*
* @param string $eventName
* @return array
*/
public function getListeners($eventName)
{
$listeners = $this->listeners[$eventName] ?? [];
$listeners = array_merge(
$listeners, $this->getWildcardListeners($eventName)
);
return class_exists($eventName, false)
? $this->addInterfaceListeners($eventName, $listeners)
: $listeners;
}
事件订阅者是一个可以在自身内部订阅多个事件的类,即能够在单个类中定义多个事件处理器。订阅者应该定义一个
subscribe
方法,这个方法接受一个事件分发器的实例。你可以调用给定的事件分发器上的listen
方法来注册事件监听器:
namespace App\Listeners;
class UserEventSubscriber
{
/**
* 处理用户登录事件。
*/
public function onUserLogin($event) {}
/**
* 处理用户注销事件。
*/
public function onUserLogout($event) {}
/**
* 为订阅者注册监听器。
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
'Illuminate\Auth\Events\Login',
'App\Listeners\UserEventSubscriber@onUserLogin'
);
$events->listen(
'Illuminate\Auth\Events\Logout',
'App\Listeners\UserEventSubscriber@onUserLogout'
);
}
}
事件订阅者的主要方法是subscribe
。Dispatcher
类中并没有专门存储订阅者的数组,要在subscribe
中使用Dispacher::listen
方法一个个进行事件注册。
订阅者写好后,就将其注册到事件分发器中。你可以在
EventServiceProvider
类的$subscribe
属性中注册订阅者。例如,将UserEventSubscriber
添加到数组列表中:
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* 应用中事件监听器的映射。
*
* @var array
*/
protected $listen = [
//
];
/**
* 需要注册的订阅者类。
*
* @var array
*/
protected $subscribe = [
'App\Listeners\UserEventSubscriber',
];
}
EventServiceProvider
的父类的
方法中,把定义的该数组传递给Dispatcher
:
/**
* Register the application's event listeners.
*
* @return void
*/
public function boot()
{
foreach ($this->listens() as $event => $listeners) {
foreach ($listeners as $listener) {
Event::listen($event, $listener);
}
}
foreach ($this->subscribe as $subscriber) {
Event::subscribe($subscriber);
}
}
Dispatcher
的subscribe
方法调用了订阅者的subscribe
方法:
/**
* Register an event subscriber with the dispatcher.
*
* @param object|string $subscriber
* @return void
*/
public function subscribe($subscriber)
{
$subscriber = $this->resolveSubscriber($subscriber);
$subscriber->subscribe($this);
}
/**
* Resolve the subscriber instance.
*
* @param object|string $subscriber
* @return mixed
*/
protected function resolveSubscriber($subscriber)
{
if (is_string($subscriber)) {
return $this->container->make($subscriber);
}
return $subscriber;
}
最终执行的还是Dispatcher
的listen
方法。