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 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!