Laravel 中已经有关于 Event 的说明和介绍了,文档中主要讲述了怎么使用和 Event 的示例场景和功能,下面主要说下整个 Event 的执行过程。

Application

从程序的入口 public/index.php,项目加载了 bootstrap/app.php 文件,该文件主要是返回一个 Application 对象 $app,在 Application 这个类里有下面的这个方法,主要是注册 Providers 的,稍后我们会讲解这个方法的调用点。

1
2
3
4
5
6
7
8
9
10
11
12
public function registerConfiguredProviders()
{

$providers = Collection::make($this->config['app.providers'])
->partition(function ($provider) {
return Str::startsWith($provider, 'Illuminate\\'); // 这里将自定义的和系统的区分开,形成 [Ill..., App...]
});

$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}

加载完 bootstrap/app.php 文件以后,看 public/index.php 下面这段代码

1
2
3
4
5
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

在 $app->make 创建 Kernel 对象的时候, Application 被默认注入了,所以 $kernel->app 就是 Application 类的实例。$kernel->handle() 主要调用了下面几个方法,

1
2
3
$kernel->sendRequestThroughRouter();
$kernel->bootstrap();
$kernel->app->bootstrapWith();

其中 $app->bootstrapWith 方法主要是去循环处理 $kernel->bootstrappers 这个数组,实例化每个类,并且调用该类的 bootstrap($app) 方法

1
2
3
4
5
6
7
8
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];

这里面我们需要关注的就是 \Illuminate\Foundation\Bootstrap\RegisterProviders::class 这个类,下面看下这个类的具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class RegisterProviders
{

/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/

public function bootstrap(Application $app)
{

// 这个方法就是上面第一个地方介绍的方法。
$app->registerConfiguredProviders();
}
}

ProviderRepository

上面的代码主要讲了从入口到 ProviderRepository 的过程,下面主要讲 ProviderRepository 之后的过程,在 ProviderRepository 类里有个比较重要的方法 load,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public function load(array $providers)
{

$manifest = $this->loadManifest();

// First we will load the service manifest, which contains information on all
// service providers registered with the application and which services it
// provides. This is used to know which services are "deferred" loaders.
if ($this->shouldRecompile($manifest, $providers)) {
$manifest = $this->compileManifest($providers);
}

// Next, we will register events to load the providers for each of the events
// that it has requested. This allows the service provider to defer itself
// while still getting automatically loaded when a certain event occurs.
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}

// We will go ahead and register all of the eagerly loaded providers with the
// application so their services can be registered with the application as
// a provided service. Then we will set the deferred service list on it.
foreach ($manifest['eager'] as $provider) {
$this->app->register($provider);
}

$this->app->addDeferredServices($manifest['deferred']);
}

注意上面的 load 方法, Providers 会被分为几类,defer when eager,具体看 Provider 的类型来区分的,我们的 App\Providers\EventServiceProvider 就属于 eager。这里 $this->app->register() 调用,注册 Provider,且调用了该 Provider 的 register 方法,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public function register($provider, $options = [], $force = false)
{

if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}

// If the given "provider" is a string, we will resolve it, passing in the
// application instance automatically for the developer. This is simply
// a more convenient way of specifying your service provider classes.
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}

if (method_exists($provider, 'register')) {
$provider->register(); // 注意这里
}

$this->markAsRegistered($provider);

// If the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by this developer's application logic.
if ($this->booted) {
$this->bootProvider($provider); // 注意这里
}

return $provider;
}

调用完 register 方法之后,在 register 这个方法之后调用了 $bootProvider,这个方法调用了 Provider 的 boot 方法。

1
2
3
4
5
6
protected function bootProvider(ServiceProvider $provider)
{

if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']);
}
}

EventServiceProvider

在 EventServiceProvider 类中,boot 这个方法非常重要,他完成了 Event 和 Listener 的初始化工作,代码如下:

1
2
3
4
5
6
7
8
9
10
11
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 的 listen 方法:

1
2
3
4
5
6
7
8
9
10
public function listen($events, $listener)
{

foreach ((array) $events as $event) {
if (Str::contains($event, '*')) {
$this->setupWildcardListen($event, $listener); // 这里,最后调用了 makeListener 方法
} else {
$this->listeners[$event][] = $this->makeListener($listener); // 这个方法对 listener 进行了初始化
}
}
}

上面代码对 Listener 做了初始化工作,这样可以在接下来的调用中使用,并且初始化之后是一个 callable 类型,之后整个事件初始化工作基本完成,接下来就是使用事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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];
}
}

protected function parseClassCallable($listener)
{

return Str::parseCallback($listener, 'handle');
}

Event::fire

Event 系统初始化之后,我们是需要使用事件的,下面就讲下事件的触发,事件的触发主要是 Event::fire 和 event 其实他们并没有什么大区别,底层都是相同的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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) {

// 注意下面这行代码,就是上面所说的 Listener 初始化为一个 callable,这样 $listener($event, $payload) 才可以调用。
$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;
}

以上就是整个事件的主要执行过程,并没有涉及 Broadcast 和 Subscribe,相关代码基本已经穿插其中,异步事件保存到队列,是由 Listener 决定的,如果保存到队列,那么需要通过消耗队列来触发事件,文档有手动触发和自动触发相关的内容。
下面是一个示例存储在队列里的数据结构:

1
{
    "displayName":"App\\Listeners\\EventListener", 
    "job":"Illuminate\\Queue\\CallQueuedHandler@call", 
    "maxTries":null, 
    "timeout":null, 
    "timeoutAt":null, 
    "data":{
        "commandName":"Illuminate\\Events\\CallQueuedListener",
        "command":"O:36:\\"Illuminate\\Events\\CallQueuedListener\\":7:{s:5:\\"class\\";s:27:\\"App\\Listeners\\EventListener\\";s:6:\\"method\\";s:6:\\"handle\\";s:4:\\"data\\";a:1:{i:0;O:16:\\"App\\Events\\Event\\":2:{s:5:\\"input\\";a:2:{s:1:\\"a\\";i:1;s:1:\\"b\\";i:2;}s:6:\\"socket\\";N;}}s:5:\\"tries\\";N;s:9:\\"timeoutAt\\";N;s:7:\\"timeout\\";N;s:6:\\"\\u0000*\\u0000job\\";N;}"
    }, 
    "id":"HI2SZiCD3K4Mmgu46RpLz13k4WhkWQAj",
    "attempts":0
}
`

关于消耗队列里的内容就不写了。

END.