13

有了之前的《简述 Laravel Model Events 的使用》https://mp.weixin.qq.com/s/XrhDq1S5RC9UdeULVVksoA,大致了解了 Event 的使用。

今天我们就来扒一扒 Event 的源码。

开始之前,需要说下两个 EventServiceProvider 的区别:

  • App\Providers\EventServiceProvider
  • Illuminate\Events\EventServiceProvider

第一个 App\Providers\EventServiceProvider 主要是定义 eventlistener 的关联;第二个 Illuminate\Events\EventServiceProviderLaravel 的三大基础 ServiceProvider 之一,主要负责「分派」工作。

好了,下面开始具体的分析工作。

App\Providers\EventServiceProvider

主要是定义 eventlistener 的关联,如:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        'App\Events\RssPublicEvent' => [
            'App\Listeners\RssPublicListener1',
        ],
        'App\Events\*Event' => [
            'App\Listeners\RssPublicListener2',
            'App\Listeners\RssPublicListener3',
        ],
        'Illuminate\Contracts\Broadcasting\ShouldBroadcast' => [
            'App\Listeners\RssPublicListener4',
            'App\Listeners\RssPublicListener5',
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();
    }
}

主要继承 Illuminate\Foundation\Support\Providers\EventServiceProvider

<?php

namespace Illuminate\Foundation\Support\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event handler mappings for the application.
     *
     * @var array
     */
    protected $listen = [];

    /**
     * The subscriber classes to register.
     *
     * @var array
     */
    protected $subscribe = [];

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

    /**
     * {@inheritdoc}
     */
    public function register()
    {
        //
    }

    /**
     * Get the events and handlers.
     *
     * @return array
     */
    public function listens()
    {
        return $this->listen;
    }
}

把定义 eventlistener 的关联交给用户自己去做,然后父类 EventServiceProvider 只是做关联工作,在 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);
    }
}

这里主要看两个函数:

  • Event::listen($event, $listener);
  • Event::subscribe($subscriber);

就这么简单,我们说完了第一个 EventServiceProvider ,我们开始第二个。

Illuminate\Events\EventServiceProvider

看过之前文章的知道,Event 有个全局函数:

Artisan::command('public_echo', function () {
    event(new RssPublicEvent());
})->describe('echo demo');

...

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

Illuminate\Events\EventServiceProvider,是 Laravel 三个基础 ServiceProvider 之一:

/**
 * Register all of the base service providers.
 *
 * @return void
 */
protected function registerBaseServiceProviders()
{
    $this->register(new EventServiceProvider($this));

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

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

我们接着看 Illuminate\Events\EventServiceProvider

<?php

namespace Illuminate\Events;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;

class EventServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make(QueueFactoryContract::class);
            });
        });
    }
}

它注册了单例形式,并创建和返回 Dispatcher 对象:

use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Contracts\Broadcasting\Factory as BroadcastFactory;
use Illuminate\Contracts\Container\Container as ContainerContract;
class Dispatcher implements DispatcherContract
{
    /**
     * The IoC container instance.
     *
     * @var \Illuminate\Contracts\Container\Container
     */
    protected $container;

    /**
     * The registered event listeners.
     *
     * @var array
     */
    protected $listeners = [];

    /**
     * The wildcard listeners.
     *
     * @var array
     */
    protected $wildcards = [];

    /**
     * The queue resolver instance.
     *
     * @var callable
     */
    protected $queueResolver;
    
...
}    

主要实现 Dispatcher 接口:

<?php

namespace Illuminate\Contracts\Events;

interface Dispatcher
{
    /**
     * Register an event listener with the dispatcher.
     *
     * @param  string|array  $events
     * @param  mixed  $listener
     * @return void
     */
    public function listen($events, $listener);

    /**
     * Determine if a given event has listeners.
     *
     * @param  string  $eventName
     * @return bool
     */
    public function hasListeners($eventName);

    /**
     * Register an event subscriber with the dispatcher.
     *
     * @param  object|string  $subscriber
     * @return void
     */
    public function subscribe($subscriber);

    /**
     * Dispatch an event until the first non-null response is returned.
     *
     * @param  string|object  $event
     * @param  mixed  $payload
     * @return array|null
     */
    public function until($event, $payload = []);

    /**
     * Dispatch 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);

    /**
     * Register an event and payload to be fired later.
     *
     * @param  string  $event
     * @param  array  $payload
     * @return void
     */
    public function push($event, $payload = []);

    /**
     * Flush a set of pushed events.
     *
     * @param  string  $event
     * @return void
     */
    public function flush($event);

    /**
     * Remove a set of listeners from the dispatcher.
     *
     * @param  string  $event
     * @return void
     */
    public function forget($event);

    /**
     * Forget all of the queued listeners.
     *
     * @return void
     */
    public function forgetPushed();
}

下面我们来解说每一个函数。

listen()

Register an event listener with the dispatcher.
public function listen($events, $listener)
{
    foreach ((array) $events as $event) {
        if (Str::contains($event, '*')) {
            $this->wildcards[$event][] = $this->makeListener($listener, true);
        } else {
            $this->listeners[$event][] = $this->makeListener($listener);
        }
    }
}

这就好理解了,把通配符的放在 wildcards 数组中,另一个放在 listeners 数组中。接下来看函数 makeListener()

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

如果传入的 $listener 为字符串,则执行函数 createClassListener

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

先来看看函数 createClassCallable()

protected function createClassCallable($listener)
{
    list($class, $method) = Str::parseCallback($listener, 'handle');

    if ($this->handlerShouldBeQueued($class)) {
        return $this->createQueuedHandlerCallable($class, $method);
    }

    return [$this->container->make($class), $method];
}

第一个函数还是很好理解:

public static function parseCallback($callback, $default = null)
{
    return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
}

就看传入的 listener 是不是 class@method 结构,如果是就用 @ 分割,否则就默认的就是 class 类名,然后 method 就是默认的 handle 函数 —— 这也是我们创建 Listener 类提供的做法。

接着就看是否可以放入队列中:

protected function handlerShouldBeQueued($class)
{
    try {
        return (new ReflectionClass($class))->implementsInterface(
            ShouldQueue::class
        );
    } catch (Exception $e) {
        return false;
    }
}

也就判断该 listener 类是否实现了接口类 ShouldQueue。如果实现了,则可以将该类放入队列中 (返回闭包函数):

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

我们接着看 handlerWantsToBeQueued

protected function handlerWantsToBeQueued($class, $arguments)
{
    if (method_exists($class, 'shouldQueue')) {
        return $this->container->make($class)->shouldQueue($arguments[0]);
    }

    return true;
}

所以说,如果在 listener 类中写了 shouldQueue 方法,则就看该方法是不是返回 true 或者 false 来决定是否放入队列中:

protected function queueHandler($class, $method, $arguments)
{
    list($listener, $job) = $this->createListenerAndJob($class, $method, $arguments);

    $connection = $this->resolveQueue()->connection(
        $listener->connection ?? null
    );

    $queue = $listener->queue ?? null;

    isset($listener->delay)
                ? $connection->laterOn($queue, $listener->delay, $job)
                : $connection->pushOn($queue, $job);
}
注:和队列相关的放在之后再做分析,此处省略

好了,回到开始的地方:

// createClassCallable($listener)
return [$this->container->make($class), $method];

到此,也就明白了,如果是 通配符 的,则对应的执行函数 (默认的为: handle) 传入的参数有两个:$event 事件对象和 $payload;否则对应执行函数,传入的参数就只有一个了:$payload

同理,如果传入的 listener 是个函数的话,返回的闭包就这样的:

return function ($event, $payload) use ($listener, $wildcard) {
    if ($wildcard) {
        return $listener($event, $payload);
    }

    return $listener(...array_values($payload));
};

整个流程就通了,listener 函数的作用就是:在 Dispatcher 中的 $listeners$wildcards 的数组中,存储 ['event' => Callback] 的结构数组,以供执行使用。

说完了第一个函数 Event::listen(),第二个函数了:Event::subscribe(),留着之后再说。

好了,整个 eventlistener 就关联在一起了。接下来就开始看执行方法了。

dispatch()

Dispatch an event and call the listeners.

正如上文的 helpers 定义的,所有 Event 都是通过该函数进行「分发」事件和调用所关联的 listeners

/**
 * 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;
}

先理解注释的函数 parseEventAndPayload()

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.
protected function parseEventAndPayload($event, $payload)
{
    if (is_object($event)) {
        list($payload, $event) = [[$event], get_class($event)];
    }

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

如果 $event 是个对象,则将 $event 的类名作为事件的名称,并将该事件 [$event] 作为 $payload

接着判断 $payload 是否可以「广播」出去,如果可以,那就直接广播出去:

protected function shouldBroadcast(array $payload)
{
    return isset($payload[0]) &&
           $payload[0] instanceof ShouldBroadcast &&
           $this->broadcastWhen($payload[0]);
}

就拿上文的例子来说吧:

<?php

namespace App\Events;

use Carbon\Carbon;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class RssPublicEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('public_channel');
    }

    /**
     * 指定广播数据。
     *
     * @return array
     */
    public function broadcastWith()
    {
        // 返回当前时间
        return ['name' => 'public_channel_'.Carbon::now()->toDateTimeString()];
    }
}

首先它实现接口 ShouldBroadcast,然后看是不是还有额外的条件来决定是否可以广播:

/**
 * Check if event should be broadcasted by condition.
 *
 * @param  mixed  $event
 * @return bool
 */
protected function broadcastWhen($event)
{
    return method_exists($event, 'broadcastWhen')
            ? $event->broadcastWhen() : true;
}

由于本实例没有实现 broadcastWhen 方法,所以返回默认值 true

所以可以将本实例广播出去:

/**
 * Broadcast the given event class.
 *
 * @param  \Illuminate\Contracts\Broadcasting\ShouldBroadcast  $event
 * @return void
 */
protected function broadcastEvent($event)
{
    $this->container->make(BroadcastFactory::class)->queue($event);
}

这就交给 BroadcastManager 来处理了,此文不再继续深挖。

:下篇文章我们再来扒一扒 BroadcastManager 源码

当把事件广播出去后,我们就开始执行该事件的各个监听了。通过之前的文章知道,一个 Event,不止一个 Listener 监听,所以需要通过一个 foreach 循环来遍历执行 Listener,首先获取这些 Listener

/**
 * 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;
}

该方法主要通过三种方式累加获取所有 listeners:该类中的属性:$listeners$wildcards,以及如果该 $event 是个对象的,还包括该类的所有接口关联的 listeners 数组。

protected function addInterfaceListeners($eventName, array $listeners = [])
{
    foreach (class_implements($eventName) as $interface) {
        if (isset($this->listeners[$interface])) {
            foreach ($this->listeners[$interface] as $names) {
                $listeners = array_merge($listeners, (array) $names);
            }
        }
    }

    return $listeners;
}
注:class_implements — 返回指定的类实现的所有接口。

接下来就是执行每个 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;

由上文可以知道 $listener,实际上就是一个闭包函数,最终的结果相当于执行 handle 函数:

public function handle(RssPublicEvent $event)
{
    info('listener 1');
}

...

public function handle(RssPublicEvent $event, array $payload)
{
    info('listener 2');
}

写个 demo

我们写个 demo,在 EventServiceProviderlisten 数组,填入这三种方式的关联情况:

protected $listen = [
    'App\Events\RssPublicEvent' => [
        'App\Listeners\RssPublicListener1',
    ],
    'App\Events\*Event' => [
        'App\Listeners\RssPublicListener2',
        'App\Listeners\RssPublicListener3',
    ],
    'Illuminate\Contracts\Broadcasting\ShouldBroadcast' => [
        'App\Listeners\RssPublicListener4',
        'App\Listeners\RssPublicListener5',
    ],
];

然后在每个 RssPublicListener*handle 方法输出对应的值,最后运行 php artisan public_echo,看结果:

[2018-10-06 20:05:57] local.INFO: listener 1  
[2018-10-06 20:05:58] local.INFO: listener 2  
[2018-10-06 20:05:59] local.INFO: listener 3  
[2018-10-06 20:05:59] local.INFO: listener 4  
[2018-10-06 20:06:00] local.INFO: listener 5

其他函数

说完了执行函数,基本上也就说完了整个 Event 事件流程了。剩下的只有一些附属函数,一看基本都理解:

/**
 * Register an event and payload to be fired later.
 *
 * @param  string  $event
 * @param  array  $payload
 * @return void
 */
public function push($event, $payload = [])
{
    $this->listen($event.'_pushed', function () use ($event, $payload) {
        $this->dispatch($event, $payload);
    });
}

/**
 * Flush a set of pushed events.
 *
 * @param  string  $event
 * @return void
 */
public function flush($event)
{
    $this->dispatch($event.'_pushed');
}

/**
 * Determine if a given event has listeners.
 *
 * @param  string  $eventName
 * @return bool
 */
public function hasListeners($eventName)
{
    return isset($this->listeners[$eventName]) || isset($this->wildcards[$eventName]);
}

/**
 * Fire an event until the first non-null response is returned.
 *
 * @param  string|object  $event
 * @param  mixed  $payload
 * @return array|null
 */
public function until($event, $payload = [])
{
    return $this->dispatch($event, $payload, true);
}

/**
     * Remove a set of listeners from the dispatcher.
 *
 * @param  string  $event
 * @return void
 */
public function forget($event)
{
    if (Str::contains($event, '*')) {
        unset($this->wildcards[$event]);
    } else {
        unset($this->listeners[$event]);
    }
}

/**
 * Forget all of the pushed listeners.
 *
 * @return void
 */
public function forgetPushed()
{
    foreach ($this->listeners as $key => $value) {
        if (Str::endsWith($key, '_pushed')) {
            $this->forget($key);
        }
    }
}

总结

Event 做了比较详细的梳理,大致了解了它的整个流程,下一步就是看看怎么和队列结合在一起,和利用「观察者模式」的那部分代码逻辑了。


Coding01
831 声望123 粉丝