Laravel核心解读 -- 事件系统

事件系统

Laravel 的事件提供了一个简单的观察者实现,可以订阅和监听应用中发生的各类事件。事件机制是一种很好的应用解耦方式,由于一个事件能够拥有多个互不依赖的监听器。laravel 中事件系统由两部分构成,一个是事件的名称,事件的名称能够是个字符串,例如 event.email,也能够是一个事件类,例如 App\Events\OrderShipped;另外一个是事件的 监听器listener,能够是一个闭包,还能够是监听类,例如 App\Listeners\SendShipmentNotificationlaravel

咱们仍是经过官方文档里给出的这个例子来向下分析事件系统的源码实现,不过在应用注册事件和监听器以前,Laravel在应用启动时会先注册处理事件用的events服务。git

Laravel注册事件服务

Laravel应用在建立时注册的基础服务里就有Event服务github

namespace Illuminate\Foundation;

class Application extends Container implements ...
{
    public function __construct($basePath = null)
    {
        ...
        $this->registerBaseServiceProviders();
        ...
    }
    
    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));

        $this->register(new LogServiceProvider($this));

        $this->register(new RoutingServiceProvider($this));
    }
}

其中的 EventServiceProvider 是 /Illuminate/Events/EventServiceProvider编程

public function register()
{
    $this->app->singleton('events', function ($app) {
        return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
            return $app->make(QueueFactoryContract::class);
        });
    });
}

Illuminate\Events\Dispatcher 就是 events服务真正的实现类,而Event门面时events服务的静态代理,事件系统相关的方法都是由Illuminate\Events\Dispatcher来提供的。segmentfault

应用中注册事件和监听

咱们仍是经过官方文档里给出的这个例子来向下分析事件系统的源码实现,注册事件和监听器有两种方法,App\Providers\EventServiceProvider 有个 listen 数组包含全部的事件(键)以及事件对应的监听器(值)来注册全部的事件监听器,能够灵活地根据需求来添加事件。数组

/**
 * 应用程序的事件监听器映射。
 *
 * @var array
 */
protected $listen = [
    'App\Events\OrderShipped' => [
        'App\Listeners\SendShipmentNotification',
    ],
];

也能够在 App\Providers\EventServiceProvider 类的 boot 方法中注册基于事件的闭包。闭包

/**
 * 注册应用程序中的任何其余事件。
 *
 * @return void
 */
public function boot()
{
    parent::boot();

    Event::listen('event.name', function ($foo, $bar) {
        //
    });
}

能够看到\App\Providers\EventProvider类的主要工做就是注册应用中的事件,这个注册类的主要做用是事件系统的启动,这个类继承自 \Illuminate\Foundation\Support\Providers\EventServiceProvideapp

咱们在将服务提供器的时候说过,Laravel应用在注册完全部的服务后会经过\Illuminate\Foundation\Bootstrap\BootProviders调用全部Provider的boot方法来启动这些服务,因此Laravel应用中事件和监听器的注册就发生在 \Illuminate\Foundation\Support\Providers\EventServiceProvide类的boot方法中,咱们来看一下:框架

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);
    }
}

能够看到事件系统的启动是经过events服务的监听和订阅方法来建立事件与对应的监听器还有系统里的事件订阅者。ide

namespace Illuminate\Events;
class Dispatcher implements DispatcherContract
{
    public function listen($events, $listener)
    {
        foreach ((array) $events as $event) {
            if (Str::contains($event, '*')) {
                $this->setupWildcardListen($event, $listener);
            } else {
                $this->listeners[$event][] = $this->makeListener($listener);
            }
        }
    }
    
    protected function setupWildcardListen($event, $listener)
    {
        $this->wildcards[$event][] = $this->makeListener($listener, true);
    }
}

对于包含通配符的事件名,会被统一放入 wildcards 数组中,makeListener是用来建立事件对应的listener的:

class Dispatcher implements DispatcherContract
{
    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);
            } else {
                return $listener(...array_values($payload));
            }
        };
    }
}

建立listener的时候,会判断监听对象是监听类仍是闭包函数。

对于闭包监听来讲,makeListener 会再包装一层返回一个闭包函数做为事件的监听者。

对于监听类来讲,会继续经过 createClassListener 来建立监听者

class Dispatcher implements DispatcherContract
{
    public function createClassListener($listener, $wildcard = false)
    {
        return function ($event, $payload) use ($listener, $wildcard) {
            if ($wildcard) {
                return call_user_func($this->createClassCallable($listener), $event, $payload);
            } else {
                return call_user_func_array(
                    $this->createClassCallable($listener), $payload
                );
            }
        };
    }

    protected function createClassCallable($listener)
    {
        list($class, $method) = $this->parseClassCallable($listener);

        if ($this->handlerShouldBeQueued($class)) {
            //若是当前监听类是队列的话,会将任务推送给队列
            return $this->createQueuedHandlerCallable($class, $method);
        } else {
            return [$this->container->make($class), $method];
        }
    }
}

对于经过监听类的字符串来建立监听者也是返回的一个闭包,若是当前监听类是要执行队列任务的话,返回的闭包是在执行后会将任务推送给队列,若是是普通监听类返回的闭包中会将监听对象make出来,执行对象的handle方法。 因此监听者返回闭包都是为了包装好事件注册时的上下文,等待事件触发的时候调用闭包来执行任务。

建立完listener后就会把它放到listener数组中以对应的事件名称为键的数组里,在listener数组中一个事件名称对应的数组里能够有多个listener, 就像咱们以前讲观察者模式时Subject类中的observers数组同样,只不过Laravel比那个复杂一些,它的listener数组里会记录多个Subject和对应观察者的对应关系。

触发事件

能够用事件名或者事件类来触发事件,触发事件时用的是Event::fire(new OrdershipmentNotification), 一样它也来自events服务

public function fire($event, $payload = [], $halt = false)
{
    return $this->dispatch($event, $payload, $halt);
}

public function dispatch($event, $payload = [], $halt = false)
{
    //若是参数$event事件对象,那么就将对象的类名做为事件名称,对象自己做为携带数据的荷载经过`listener`方法
    //的$payload参数的实参传递给listener
    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);

        //若是触发事件时传递了halt参数,而且listener返回了值,那么就不会再去调用事件剩下的listener
        //不然就将返回值加入到返回值列表中,等全部listener执行完了一并返回
        if ($halt && ! is_null($response)) {
            return $response;
        }
        //若是一个listener返回了false, 那么将不会再调用事件剩下的listener
        if ($response === false) {
            break;
        }

        $responses[] = $response;
    }

    return $halt ? null : $responses;
}

protected function parseEventAndPayload($event, $payload)
{
    if (is_object($event)) {
        list($payload, $event) = [[$event], get_class($event)];
    }

    return [$event, Arr::wrap($payload)];
}

//获取事件名对应的全部listener
public function getListeners($eventName)
{
    $listeners = isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [];

    $listeners = array_merge(
        $listeners, $this->getWildcardListeners($eventName)
    );

    return class_exists($eventName, false)
                ? $this->addInterfaceListeners($eventName, $listeners)
                : $listeners;
}

事件触发后,会从以前注册事件生成的listeners中找到事件名称对应的全部listener闭包,而后调用这些闭包来执行监听器中的任务,须要注意的是:

  • 若是事件名参数事件对象,那么会用事件对象的类名做为事件名,其自己会做为时间参数传递给listener。
  • 若是触发事件时传递了halt参数,在listener返回非false后那么事件就不会往下继续传播给剩余的listener了,不然全部listener的返回值会在全部listener执行日后做为一个数组统一返回。
  • 若是一个listener返回了布尔值false那么事件会当即中止向剩余的listener传播。

Laravel的事件系统原理仍是跟以前讲的观察者模式同样,不过框架的做者功力深厚,巧妙的结合应用了闭包来实现了事件系统,还有针对须要队列处理的事件,应用事件在一些比较复杂的业务场景中能利用关注点分散原则有效地解耦应用中的代码逻辑,固然也不是什么状况下都能适合应用事件来编写代码,我以前写过一篇文章事件驱动编程来讲明事件的应用场景,感兴趣的能够去看看。

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

相关文章
相关标签/搜索