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的注册机制。方便后面阅读。
- 查看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
所属类的路径,然后返回。
在我们当前的例子中,$provider
是RoutingServiceProvider($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类中你可以配置两个数组, 分别叫bindings
和singletons
,在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通常提供bindings
和singletons
两个数组变量,用来提供一些provider需要的依赖绑定到容器中。在注册完成之后绑定。
4.注册完成后,绑定完成后,额外的逻辑可以在provider的boot方法中实现。当然没有额外逻辑。可以不写boot
本作品采用《CC 协议》,转载必须注明作者和本文链接