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 協議》,轉載必須註明作者和本文連結