事件系统Laravel的事件提供了一个简单的观察者实现,可以订阅和监听应用程序中发生的各种事件。事件机制是解耦应用程序的好方法,因为一个事件可以有多个互不依赖的监听器。laravel中的事件系统由两部分组成,一是事件的名称,事件的名称可以是字符串,如event.email,也可以是事件类,如App\Events\OrderShipped;另一个是事件监听器listener,可以是闭包或者监听类,比如App\Listeners\SendShipmentNotification。我们还是通过官方文档给出的例子来分析事件系统的源码实现,但是在应用注册事件和监听器之前,Laravel会在应用启动时先注册事件服务,用于处理事件。Laravel注册事件服务Laravel应用有一个Event服务命名空间Illuminate\Foundation;classApplicationextendsContainer实现在创建Laravel应用时注册的基本服务...{publicfunction__construct($basePath=null){...$this->registerBaseServiceProviders();...}受保护的函数registerBaseServiceProviders(){$this->register(newEventServiceProvider($this));$this->register(newLogServiceProvider($this));$this->register(newRoutingServiceProvider($this));}}其中EventServiceProvider是/Illuminate/Events/EventServiceProviderpublicfunctionregister(){$this->app->singleton('events',function($app){return(newDispatcher($app))->setQueueResolver(function()use($app){return$app->make(QueueFactoryContract::class);});});}Illuminate\Events\Dispatcher是events服务的真正实现类,Eventfacade是静态的事件服务的代理和事件系统相关的方法由Illuminate\Events\Dispatcher提供。在应用中注册事件和监听,我们还是以官方文档给出的例子来分析事件系统的源码实现。注册事件和监听器有两种方法。App\Providers\EventServiceProvider有一个包含所有事件(key)和事件对应的监听器(value)的listen数组来注册所有的事件监听器,可以根据需要灵活添加事件。/***应用程序的事件监听器映射。**@vararray*/protected$listen=['App\Events\OrderShipped'=>['App\Listeners\SendShipmentNotification',],];您还可以在App\Providers\EventServiceProvider闭包类的引导方法中注册基于事件的事件。/***注册应用程序中的任何其他事件。**@returnvoid*/publicfunctionboot(){parent::boot();Event::listen('event.name',function($foo,$bar){//});}可以看出\App\Providers\EventProvider类的主要工作是在应用程序中注册事件。这个注册类的主要功能是启动事件系统。该类继承自\Illuminate\Foundation\Support\Providers\EventServiceProvide。我们在做服务提供者的时候说过,Laravel应用注册完所有服务后,会通过\Illuminate\Foundation\Bootstrap\BootProviders调用所有Provider的boot方法来启动这些服务,所以Laravel中的事件和监听器application注册发生在\Illuminate\Foundation\Support\Providers\EventServiceProvide类的引导方法中。让我们看一下:}}foreach($this->subscribeas$subscriber){Event::subscribe($subscriber);}}可以看到事件系统的启动是通过事件服务的监听和订阅方法在系统中创建事件和对应的监听器和事件订阅者。命名空间Illuminate\Events;类Dispatcher实现DispatcherContract{publicfunctionlisten($events,$listener){foreach((array)$eventsas$event){if(Str::contains($event,'*')){$this->setupWildcardListen($event,$listener);}else{$this->listeners[$event][]=$this->makeListener($listener);}}}保护函数setupWildcardListen($event,$listener){$this->wildcards[$event][]=$this->makeListener($listener,true);}}对于包含通配符的事件名,统一放入通配符数组,使用makeListener创建监听器的事件对应:is_string($listener)){//如果监听器是一个类,则创建一个监听类return$this->createClassListener($listener,$wildcard);}返回函数($event,$payload)使用($listener,$wildcard){如果($wildcard){返回$listener($event,$payload);}else{return$listener(...array_values($payload));}};}}创建监听器时,会判断监听对象是监听类还是闭包函数,对于闭包监听,makeListener会另外包裹一层,返回一个闭包函数作为事件监听器。对于监听类,会继续通过createClassListener创建监听器classDispatcherimplementsDispatcherContract{publicfunctioncreateClassListener($listener,$wildcard=false){returnfunction($event,$payload)use($listener,$wildcard){如果($wildcard){returncall_user_func($this->createClassCallable($listener),$event,$payload);}else{returncall_user_func_array($this->createClassCallable($listener),$payload);}};}protectedfunctioncreateClassCallable($listener){列表($class,$method)=$this->parseClassCallable($listener);if($this->handlerShouldBeQueued($class)){//如果当前监听类是一个队列,任务会被推送到队列中return$this->createQueuedHandlerCallable($class,$method);}else{return[$this->container->make($class),$method];}}}对于监听器类创建监听器的字符串也是一个返回的闭包,ifwhen如果前置监听类要执行队列任务,返回的闭包会在执行完后将任务推送到队列中。如果是普通的监听器类,监听器对象返回的闭包会被弄出来并执行该对象的handle方法,所以监听器返回闭包的目的是为了包装事件注册的上下文,以及在触发事件时调用闭包来执行任务。监听器创建后,会放入监听器数组中以对应事件名称为key的数组中。监听器数组中一个事件名对应的数组中可以有多个监听器,就像我们之前讲的观察者模式一样。Subject类中的观察者数组是相同的,但Laravel比它更复杂。它的listener数组会记录多个Subject与对应观察者的对应关系。触发事件可以通过事件名称或事件类来触发。触发事件时使用Event::fire(newOrdershipmentNotification),同样来自事件服务publicfunctionfire($event,$payload=[],$halt=false){return$this->dispatch($event,$payload,$halt);}publicfunctiondispatch($event,$payload=[],$halt=false){//如果参数$event是事件对象,那么就用对象的类名作为事件名称,对象本身作为负载,通过`listener`方法的$payload参数的实参传递给监听器的数据//list($event,$payload)=$this->parseEventAndPayload($事件,$有效载荷);如果($this->shouldBroadcast($payload)){$this->broadcastEvent($payload[0]);}$响应=[];foreach($this->getListeners($event)as$listener){$response=$listener($event,$payload);//如果事件触发时传递了halt参数,监听器有返回值,则不会再调用该事件剩下的监听器//否则将返回值加入返回值列表,等待所有监听器执行并返回if($halt&&!is_null($response)){return$response;}//如果一个监听器返回false,那么它就不会再调用剩下的监听器if($response===false){休息;}$响应[]=$响应;返回$halt?null:$responses;}protectedfunctionparseEventAndPayload($event,$payload){if(is_object($event)){list($payload,$event)=[[$event],get_class($event)];}return[$event,Arr::wrap($payload)];}//获取事件名对应的所有监听器publicfunctiongetListeners($eventName){$listeners=isset($this->listeners[$eventName])?$this->listeners[$eventName]:[];$listeners=array_merge($listeners,$this->getWildcardListeners($eventName));返回class_exists($eventName,false)?$this->addInterfaceListeners($eventName,$listeners):$listeners;}事件被触发后,会从之前注册的事件产生的监听器中,找出所有事件名对应的监听器闭包,然后调用这些闭包要在侦听器中执行任务,需要注意的是,如果事件名称参数是事件对象,那么事件对象的类名将被用作事件名称,事件名称本身将作为时间参数传递给侦听器。如果事件触发时传递了halt参数,监听器返回非false后,事件将不会继续传播给剩余的监听器,否则所有监听器的返回值将在所有监听器后以数组的形式返回被执行。如果侦听器返回布尔值false,则事件将立即停止传播给其余的侦听器。Laravel的事件系统原理还是和前面说的观察者模式一样,但是框架的作者功力深厚,巧妙的结合了闭包的应用实现了事件系统,对于需要队列处理的事件,应用事件是在一些复杂的业务场景中,可以利用关注点分离原则,对应用中的代码逻辑进行有效解耦。当然,它并不总是适合使用事件编写代码。在事件驱动编程之前写过一篇文章来说明事件的应用。现场,有兴趣的可以去看看。本文已收录在Laravel源码学习系列文章中,欢迎访问阅读。
