服務提供者,在laravel里面,其實就是一個工廠類。它最大的作用就是用來進行服務綁定。當我們需要綁定一個或多個服務的時候,可以自定義一個服務提供者,然后把服務綁定的邏輯都放在該類的實現中。在larave里面,要自定一個服務提供者非常容易,只要繼承Illuminate\Support\ServiceProvider這個類即可。下面通過一個簡單的自定義服務提供者來說明服務提供者的一些要點:
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { protected $defer = true; public function boot() { // } public function register() { $this->app->singleton('service1', function(){ return 'service1'; }); $this->app->singleton('service2', function(){ return 'service2'; }); $this->app->singleton('service3', function(){ return 'service3'; }); } public function provides() { return ['service1','service2','service3']; } }
1). 首先,自定義的服務提供者都是放在下面這個目錄的:
其實你放在哪都可以,不過得告訴laravel你的服務提供者在哪,laravel才會幫你注冊。怎么告訴它,后面還有介紹。
2)在這個舉例里面,可以看到有一個register方法,這個方法是ServiceProvider里面定義的。自定義的時候,需要重寫它。這個方法就是用來綁定服務的。你可以在這個服務里面,根據需要加入任意數量的服務綁定。前面要介紹過,在服務提供者里面,始終能通過$this->app拿到容器實例,所以上面的舉例中,我們直接用這種方式來完成服務綁定。這個方法是怎么完成服務綁定的呢?因為當laravel找到這個服務提供者的類以后,就會初始化這個服務提供者類,得到一個服務提供者的對象,然后調用它的register方法,自然它里面的所有服務綁定代碼就都會執行了。
laravel初始化自定義服務提供者的源碼是:
public function registerConfiguredProviders() { $manifestPath = $this->getCachedServicesPath(); (new ProviderRepository($this, new Filesystem, $manifestPath)) ->load($this->config['app.providers']); }
這個代碼是在Illuminate\Foundation\Application的源碼里面拿出來的,從中你能看到laravel會把所有的自定義服務提供者都注冊進來。這個注冊的過程其實就是前面說的實例化服務提供者的類,並調用register方法的過程。
3). 從上一步的源碼也能看到,laravel加載自定義服務提供者的時候,實際是從config/app.php這個配置文件里面的providers配置節找到所有要注冊的服務提供者的。
'providers' => [ /* * Laravel Framework Service Providers... */ Illuminate\Auth\AuthServiceProvider::class, Illuminate\Broadcasting\BroadcastServiceProvider::class, Illuminate\Bus\BusServiceProvider::class, Illuminate\Cache\CacheServiceProvider::class, Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, Illuminate\Cookie\CookieServiceProvider::class, Illuminate\Database\DatabaseServiceProvider::class, Illuminate\Encryption\EncryptionServiceProvider::class, Illuminate\Filesystem\FilesystemServiceProvider::class, Illuminate\Foundation\Providers\FoundationServiceProvider::class, Illuminate\Hashing\HashServiceProvider::class, Illuminate\Mail\MailServiceProvider::class, Illuminate\Notifications\NotificationServiceProvider::class, Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, /* * Package Service Providers... */ // /* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, Xavrsl\Cas\CasServiceProvider::class, ThirdProviders\CasServer\CasServerProvider::class ],
所以你如果自己寫了一個服務提供者,那么只要配置到這里面,laravel就會自動幫你注冊它了。
4)除了register方法,服務提供者里面還有一個boot方法,這個boot方法,會在所有的服務提供者都注冊完成之后才會執行,所以當你想在服務綁定完成之后,通過容器解析出其它服務,做一些初始化工作的時候,那么就可以這些邏輯寫在boot方法里面。因為boot方法執行的時候,所有服務提供者都已經被注冊完畢了,所以在boot方法里面能夠確保其它服務都能被解析出來。
5)前面說的服務提供者的情況,在laravel應用程序初始化的時候,就會去注冊服務提供者,調用register方法。但是還有一種需求,你可能需要在真正用到這個服務提供者綁定的服務的時候,才會去注冊這個服務提供者,以減少不必要的注冊處理,提高性能。這也是延遲處理的一種方式。那么這種服務提供者該怎么定義呢?
其實最前面的這個舉例已經告訴你了,只要定義一個$defer的實例屬性,並把這個實例屬性設置為true,然后添加一個provides的實例方法即可。這兩個成員都是ServiceProvider基類里面定義好的,自定義的時候,只是覆蓋而已。
在基類中,$defer的默認值是false,表示這個服務提供者不需要延遲注冊。provides方法,只要簡單的返回這個服務提供register方法里面,注冊的所有服務綁定名稱即可。
延遲注冊的服務提供者的機制是:
- 當laravel初始化服務提供者的實例后,如果發現這個服務提供者的$defer屬性為true,那么就不會去調用它的register方法
- 當laravel解析一個服務的時候,如果發現這個服務是由一個延遲服務提供的(它怎么知道這個服務是延遲服務提供的,是provides方法告訴它的),那么就會先把這個延遲服務提供者先注冊,再去解析。這個可以看看Illuminate\Foundation\Application的make方法就清楚了:
-
public function make($abstract, array $parameters = []) { $abstract = $this->getAlias($abstract); if (isset($this->deferredServices[$abstract])) { $this->loadDeferredProvider($abstract); } return parent::make($abstract, $parameters); }
6)還記得容器實例結構上幾個帶有providers名稱的屬性數組吧:
在了解以上provider的機制后,這幾個數組的作用也就比較清晰了。其中serviceProviders用來存放所有已經注冊完畢的服務提供者:
loadedProviders跟serviceProviders的作用類似,只是存儲的記錄形式不同:
deferredProviders用來存儲所有的延遲注冊的服務提供者:
跟前面兩個不同的是,deferredProviders存儲的記錄的key值並不是服務提供者的類型名稱,而是服務提供者的provides返回數組里面的名稱。並且如果一個服務提供者的provides里面返回了多個服務綁定名稱的話,那么deferredProviders里面就會存多條記錄:
這樣是方便根據服務綁定名稱,找到對應的服務提供者,並完成注冊。當服務的解析的時候,會先完成延遲類型的服務提供者的注冊,注冊完畢,這個服務綁定名稱在deferredProviders對應的那條記錄就會刪除掉。不過如果一個服務提供者provides了多個服務綁定名稱,解析其中一個服務的時候,只移除該名稱對應的deferredProviders記錄,而不是所有。
7)服務提供者還有一個小問題值的注意,由於php是一門基本語言,在處理請求的時候,都會從入口文件把所有php都執行一遍。為了性能考慮,laravel會在第一次初始化的時候,把所有的服務提供者都緩存到bootstrap/cache/services.php文件里面,所以有時候當你改了一個服務提供者的代碼以后,再刷新不一定能看到期望的效果,這有可能就是因為緩存所致。這時把services.php刪掉就能看到你要的效果了。