3.1.1 - Laravel - 5.6 - Route - Application的Register方法源码分析

Application 提供了一个 register 方法用来注册 service provider。举例使用如下:


protected function registerBaseServiceProviders()
{
    $this->register(new EventServiceProvider($this));
    $this->register(new LogServiceProvider($this));
    $this->register(new RoutingServiceProvider($this));
}

这个方法是 Application 初始化时注册 Serviceprovider 的方法。
最后一个 ServiceProvider 涉及到了 RoutingServiceProvider 的注册。这个 RoutingServiceProvider 对路由机制来说很重要,所以在分析它之前,先具体来看看 Application 的注册机制。方便后面阅读。

  1. 查看 application 中的 register 方法源码
public function register($provider, $force = false)
{
    if (($registered = $this->getProvider($provider)) && ! $force) {
        return $registered;
    }

    if (is_string($provider)) {
        $provider = $this->resolveProvider($provider);
    }

    $provider->register();

    if (property_exists($provider, 'bindings')) {
        foreach ($provider->bindings as $key => $value) {
        $this->bind($key, $value);
        }
    }

    if (property_exists($provider, 'singletons')) {
        foreach ($provider->singletons as $key => $value) {
        $this->singleton($key, $value);
        }
    }

    $this->markAsRegistered($provider);

    if ($this->isBooted()) {
        $this->bootProvider($provider);
    }

    return $provider;
}

1.1 第一步

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

1.1.1 主要就是调用 getProvider($provider)。作用就是检查前面是否已经加载了我们需要的 provider,如果是,返回这个 provider,不用再加载。如果不是,就返回 null。

array_values: 返回数组的 value 值不包含 key

看下 getProvider() 方法源码

public function getProvider($provider)
{
    return array_values($this->getProviders($provider))[0] ?? null;
}

$this->getProviders($provider))[0] 方法获取的数组的第一个值,说明返回的是一个数组。

这个方法没什么好说的就是调用方法 getProviders()

注意最后有个 s 和上面的 getProvider() 的区别。

1.1.1.1 我们看下 getProviders () 方法:

public function getProviders($provider)
{
    $name = is_string($provider) ? $provider : get_class($provider);
        return Arr::where($this->serviceProviders, function ($value) use ($name) {
            return $value instanceof $name;
    });
}

这个方法分两步,

a. 如果参数 $provider 是字符串,直接赋值给变量 $name,如果不是,使用 get_class 获取该参数 $provider 所属类的路径,然后返回。
在我们当前的例子中,$providerRoutingServiceProvider($this) 对象,所以返回的是类 RoutingServiceProvider 的类路径。

这里我们知道 provider 可以直接是一个类路径的字符串,联想到 serviceprovider 注册的另外一种形式是通过配置类路径来实现就很好理解了。

b. 然后调用了工具类 Arr 的 where 方法。

public static function where($array, callable $callback)
{
    return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
}

where 方法的功能是使用第二个参数(回调函数)对第一个参数(数组)进行过滤查找,找出需要的值。

在这里:
I. 第一个参数 $array$this->serviceProviders 数组
II.$this->serviceProviders 是前面所有已经注册了的 serviceprovider

所以就是在 $this->serviceProviders 这个存放前面已经注册的 service provider 中找到对应的 provider 返回。

总结:1.1#

现在我们知道了 $this->getProvider($provider)) 得到的是在 serviceprovider 数组中获取已经存在的 provider。
那这句就是判断如果已经注册了需要的 provider,同时变量 force 是 false 的情况下(这个情况下 force 用来控制是否需要查找存在的 serviceprovider),直接返回存在的 $provider。



1.2 如果这个 provider 是一个 string,我们使用 `resolveProvider` 方法来处理

if (is_string($provider)) {
    $provider = $this->resolveProvider($provider);
}

1.2.1 看下 resolveProvider 方法,很简单直接 new 一个 provider 对象返回。

public function resolveProvider($provider)
{
    return new $provider($this);
}


1.3 第三步代码:

$provider->register();

这里 $provider 通过第二步,已经肯定是一个 provider 对象了。就直接调用该对象的 register 方法。

当前例子中就是 RoutingServiceProvider($this) 对象的 register 方法。

1.3.1 因为 RoutingServiceProvider 的实现逻辑不是本文的目的,所以简单看一下。

进入 RoutingServiceProvider 类的 register 方法。

方法如下,提供了几个方法。


public function register()
{
    $this->registerRouter();
    $this->registerUrlGenerator();
    $this->registerRedirector();
    $this->registerPsrRequest();
    $this->registerPsrResponse();
    $this->registerResponseFactory();
    $this->registerControllerDispatcher();
}

我们先只看其中第一个 registerRouter() 方法为例.

protected function registerRouter()
{
    $this->app->singleton('router', function ($app) {
        return new Router($app['events'], $app);
    });
}

使用 singleton 单例绑定 router, 对应的回调函数会返回一个 Router 对象。
同时在调用回调函数的时候需要外部依赖 $app 容器作为参数。

这样就得到了路由器 router 对象用于后面操控 route 路由对象了。


总结 1.3
现在看来 register 方法就是每个 provider 的基础方法,基本包含了所有的 provider 基本逻辑程序。


1.4 调用 provider 对象的 register 方法完成以后,如果 provider 中存在两个属性 `bindings` 和 `singletons`, 就会在这个时候把这两个属性(数组集合)中的每个字段值都绑定到容器中。

其实看名字就能明白。bindings 就是表示普通绑定。singletons 就是单例绑定。

简单说就是,provider 类中你可以配置两个数组,分别叫 bindingssingletons,在 laravel 执行完 provider 的 register 方法后就可以被 laravel 依次绑定到容器中。便于后面的使用。


if (property_exists($provider, 'bindings')) {
    foreach ($provider->bindings as $key => $value) {
        $this->bind($key, $value);
    }
}

if (property_exists($provider, 'singletons')) {
    foreach ($provider->singletons as $key => $value) {
        $this->singleton($key, $value);
    }
}


1.5 第五步

$this->markAsRegistered($provider);

到这里要把这个已经注册完成的 provider 对象进行保存,就是存入 serviceProviders 数组中,后面再有需求的时候不需要再次进行注册。对应了第一步的作用。避免重复加载。

1.5.1 我们看下源码就知道了,做了两件事。

a. 存储这个注册的 provider 到 serviceProviders 数组中。

b. 把对应的类名存储到 loadedProviders 数组中,以备后用。

protected function markAsRegistered($provider)
{
    $this->serviceProviders[] = $provider;
    $this->loadedProviders[get_class($provider)] = true;
}


1.6 第六步

if ($this->isBooted()) {
    $this->bootProvider($provider);
}

首先判断当前的 application 这个容器对象是否已经启动过了,什么意思呢,简单说,就是 application 的 boot 方法是否已经执行过了。

每个 provider 都提供了一个 boot 方法用来提供一些额外的逻辑在完成 provider 注册后执行。通常这些 boot 方法会在 Application 的 boot 方法中会统一执行。
代码如下:

public function boot()
{
    ...
    array_walk($this->serviceProviders, function ($p) {
        $this->bootProvider($p);
    });
    ...
}

boot 方法的执行相对靠后,(在 application 初始化后)他是通过类 BootProviders 的 bootstrap 方法触发的。BootProviders 类又会被当做一个 serviceprovider 在 application 初始化后期和其他 serviceprovider 一起加载然后触发。先这样简单描述,不在本章讨论范围。
\Illuminate\Foundation\Bootstrap\BootProviders::class,

我们可以看到 在 laravel 中,serviceprovider 的注册一部分发生在 application 初始化后。一部分发生在 application 初始化时,当前的这个 RoutingServiceProvider 就是在初始化时。(具体参考后面 RoutingServiceProvider 源码分析)

但是有些 provider 是在 application 初始化后注册的,如果注册的时机晚于 BootProviders 的注册,这个时候 application 不会再执行它的 boot 方法了,所以需要在这一步中主动去触发 provider 的 boot。

1.6.1 provider 中是否存在 boot 方法,如果存在,触发 boot 方法中的逻辑。

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

1.7 最后返回注册好后的 provider。
整个注册完成。

总结:#

1. 注册一个 provider 之前,先去查看是不是已经注册这个服务了,避免反复注册浪费资源

2. 注册一个 provider 简单来说就是执行 provider 中的 register 方法的逻辑。

3. 注册完成后会把当前这个 provider 对象存入指定的数组中存储以便后面不再反复注册。

4.provider 通常提供 bindingssingletons 两个数组变量,用来提供一些 provider 需要的依赖绑定到容器中。在注册完成之后绑定。

4. 注册完成后,绑定完成后,额外的逻辑可以在 provider 的 boot 方法中实现。当然没有额外逻辑。可以不写 boot

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。