laravel裏所謂的provider服務提供者,實際上是對某一類功能進行整合,與作一些使用前的初始化引導工做。laravel裏的服務提供者也分爲,系統核心服務提供者、與通常系統服務提供者。例如上一篇博文裏介紹的,最先在application中進行註冊的event、log、routing這些就是系統的核心服務,laravel的初始化須要他們。那麼如今就先來看一下provider的運行流程。php
1 protected function registerBaseServiceProviders() 2 { 3 $this->register(new EventServiceProvider($this)); 4 5 $this->register(new LogServiceProvider($this)); 6 7 $this->register(new RoutingServiceProvider($this)); 8 }
其餘的serviceProvider則是指config/app.php中providers數組所配置的provider了,基本都是些laravel系統提供的工具型providerjava
1 'providers' => [ 2 3 /* 4 * Laravel Framework Service Providers... 5 */ 6 Illuminate\Auth\AuthServiceProvider::class, 7 Illuminate\Broadcasting\BroadcastServiceProvider::class, 8 Illuminate\Bus\BusServiceProvider::class, 9 Illuminate\Cache\CacheServiceProvider::class, 10 Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, 11 Illuminate\Cookie\CookieServiceProvider::class, 12 Illuminate\Database\DatabaseServiceProvider::class, 13 Illuminate\Encryption\EncryptionServiceProvider::class, 14 Illuminate\Filesystem\FilesystemServiceProvider::class, 15 Illuminate\Foundation\Providers\FoundationServiceProvider::class, 16 Illuminate\Hashing\HashServiceProvider::class, 17 Illuminate\Mail\MailServiceProvider::class, 18 Illuminate\Notifications\NotificationServiceProvider::class, 19 Illuminate\Pagination\PaginationServiceProvider::class, 20 Illuminate\Pipeline\PipelineServiceProvider::class, 21 Illuminate\Queue\QueueServiceProvider::class, 22 Illuminate\Redis\RedisServiceProvider::class, 23 Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, 24 Illuminate\Session\SessionServiceProvider::class, 25 Illuminate\Translation\TranslationServiceProvider::class, 26 Illuminate\Validation\ValidationServiceProvider::class, 27 Illuminate\View\ViewServiceProvider::class, 28 //Maatwebsite\Excel\ExcelServiceProvider::class, 這個是我本身測試的時候加的 29 30 /* 31 * Package Service Providers... 32 */ 33 34 /* 35 * Application Service Providers... 36 */ 37 App\Providers\AppServiceProvider::class, 38 App\Providers\AuthServiceProvider::class, 39 // App\Providers\BroadcastServiceProvider::class, 40 App\Providers\EventServiceProvider::class, 41 App\Providers\RouteServiceProvider::class, 42 43 ],
那麼這些配置中的provider會在何時加載呢?上一篇博文中介紹的當$kernel對象經過handle方法傳入request時,會執行sendRequestThroughRouter方法,這個方法中的bootstrap方法會加載laravel系統初始化所需的對象並運行,其中RegisterProviders類即是用來註冊剛剛config文件內所記錄的provider的laravel
1 public function bootstrap() 2 { 3 if (! $this->app->hasBeenBootstrapped()) { 4 $this->app->bootstrapWith($this->bootstrappers()); 5 } 6 } 7 8 protected $bootstrappers = [ 9 \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, 10 \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, 11 \Illuminate\Foundation\Bootstrap\HandleExceptions::class, 12 //註冊facade門面類 13 \Illuminate\Foundation\Bootstrap\RegisterFacades::class, 14 //註冊provider 15 \Illuminate\Foundation\Bootstrap\RegisterProviders::class, 16 //引導provider執行其中boot方法內的代碼 17 \Illuminate\Foundation\Bootstrap\BootProviders::class, 18 ];
這幾個文件的內容都很簡單,而且都是調用了application中的方法web
1 public function bootstrapWith(array $bootstrappers) 2 { 3 $this->hasBeenBootstrapped = true; 4 5 foreach ($bootstrappers as $bootstrapper) { 6 $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]); 7 //make了剛剛傳入的$bootstrappers數組,並執行了其中的bootstrap方法,暫且只看provider 8 $this->make($bootstrapper)->bootstrap($this); 9 10 $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]); 11 } 12 } 13 14 //Illuminate\Foundation\Bootstrap\RegisterProviders.php 15 public function bootstrap(Application $app) 16 { 17 $app->registerConfiguredProviders(); 18 } 19 20 //Illuminate\Foundation\Bootstrap\BootProviders.php 21 public function bootstrap(Application $app) 22 { 23 $app->boot(); 24 }
這裏繞了一大圈,最終仍是回到了application文件中,還記得上一篇博文中介紹的registerConfiguredProviders方法嗎?bootstrap
application的registerConfiguredProviders()方法對服務提供者進行了註冊,經過框架的文件系統收集了配置文件中的各類provicers並轉化成數組,在G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Foundation\ProviderRepository.php類的load方法中進行加載,但最終仍是會在application類中的register()方法中經過字符串的方式new出對象,在執行provider中自帶的register()方法數組
1 public function registerConfiguredProviders() 2 { 3 //laravel的集合類,將以前初始化時存入的config中的數組取出 4 $providers = Collection::make($this->config['app.providers']) 5 ->partition(function ($provider) { 6 //並過濾出系統providers 7 return Str::startsWith($provider, 'Illuminate\\'); 8 }); 9 //以前在registerBaseBindings方法中綁定在PackageManifest類中的providers數組拼接,經過load方法加載它們 10 $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]); 11 //new了provider庫,傳入服務容器、文件系統操做對象、與以前緩存的服務提供者路徑 12 (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) 13 ->load($providers->collapse()->toArray()); 14 }
1 //Illuminate\Foundation\ProviderRepository.php 2 3 public function load(array $providers) 4 { 5 // 查看bootstrap/cache/services.php有沒有這個緩存文件 6 // 第一次啓動時是沒有的 7 $manifest = $this->loadManifest(); 8 // 開始沒有這個緩存文件,那就把$providers[ ]裏的值 9 if ($this->shouldRecompile($manifest, $providers)) { 10 // 而後根據$providers[ ]編譯出services.php這個緩存文件 11 $manifest = $this->compileManifest($providers); 12 } 13 14 foreach ($manifest['when'] as $provider => $events) { 15 // 註冊包含有事件監聽的service provider 16 // 包含有事件監聽的service provider都要有when()函數返回 17 $this->registerLoadEvents($provider, $events); 18 } 19 20 foreach ($manifest['eager'] as $provider) { 21 // 把'eager'字段中service provider註冊進容器中, 22 // 即遍歷每個service provider,調用其中的register()方法 23 // 向容器中註冊具體的服務 24 $this->app->register($this->createProvider($provider)); 25 } 26 27 // 註冊延遲的service provider, 28 // deferred的service provider, 一是要設置$defer = true,二是要提供provides()方法返回綁定到容器中服務的名稱 29 $this->app->addDeferredServices($manifest['deferred']); 30 }
而boot操做就更簡單了緩存
1 public function boot() 2 { 3 if ($this->booted) { 4 return; 5 } 6 7 // Once the application has booted we will also fire some "booted" callbacks 8 // for any listeners that need to do work after this initial booting gets 9 // finished. This is useful when ordering the boot-up processes we run. 10 //調用引導方法的鉤子函數 11 $this->fireAppCallbacks($this->bootingCallbacks); 12 //使每一個provider運行bootProvider,$p爲provider 13 array_walk($this->serviceProviders, function ($p) { 14 $this->bootProvider($p); 15 }); 16 //改變引導狀態 17 $this->booted = true; 18 //調用引導方法的鉤子函數 19 $this->fireAppCallbacks($this->bootedCallbacks); 20 } 21 22 protected function bootProvider(ServiceProvider $provider) 23 { 24 //判斷傳入的provier,運行它們的boot方法完成引導 25 if (method_exists($provider, 'boot')) { 26 return $this->call([$provider, 'boot']); 27 } 28 }
到這裏,provider經過register註冊在了服務容器內,provider的初始化工做也由boot函數完成,這個provider所提供的對象即可以直接拿來使用了。app
還記得學習laravel框架使用方式的時候,文檔建議咱們把全部在應用初始化時須要完成的事情,都寫在AppServiceProvider的boot方法裏嗎?看到這裏咱們能明白做爲系統核心prvider的app是最先被加載的,所以也充當了一個鉤子函數的角色。框架
在瞭解了provider的註冊流程以後,就能夠本身來自定義一個provider了。咱們上一篇博客裏還有一個契約的概念沒有說明,這裏簡單舉一個小例子來講明。ide
一、新建一個接口。
1 namespace App\Contracts; 2 3 interface Test 4 { 5 public function doing(); 6 }
二、新建兩個接口的實現
1 namespace App\Services; 2 3 use App\Contracts\Test; 4 5 class TestService implements Test 6 { 7 public function doing() 8 { 9 echo 'this is TestService'; 10 } 11 } 12 13 14 namespace App\Services; 15 16 use App\Contracts\Test; 17 18 class SecondTestService implements Test 19 { 20 public function doing() 21 { 22 echo 'this is SecondTestService'; 23 } 24 }
三、新建一個provider,可以使用artisan 命令行 php artisan make:provider TestServiceProvider 建立一個provider,契約上下文就在這個地方進行綁定。上一篇博文裏講到make方法的時候,容器在解析類的時候,有一個獲取上下文的步驟,所要獲取的concrete就是在provider中經過when方法綁定的類了,不過惋惜這個綁定只能具體到類,不能具體到方法。
1 namespace App\Providers; 2 3 use Illuminate\Support\ServiceProvider; 4 5 class TestServiceProvider extends ServiceProvider 6 { 7 /** 8 * Bootstrap any application services. 9 * 10 * @return void 11 */ 12 public function boot() 13 { 14 // 15 } 16 17 public function register() 18 { 19 $this->app->bind('App\Contracts\Test', 'App\services\TestService'); 20 //重點在於when方法肯定運行環境,也就是執行上下文,needs爲make所需的abstract類名或別名,give所傳入的參數則是實際調用的實現類了 21 $this->app->when('App\Http\Controllers\IndexController') 22 ->needs('App\Contracts\Test') 23 ->give('App\Services\SecondTestService'); 24 } 25 }
四、在config/app.php文件的providers數組中添加剛剛生成的provider
1 'providers' => [ 2 3 /* 4 * Laravel Framework Service Providers... 5 */ 6 Illuminate\Auth\AuthServiceProvider::class, 7 Illuminate\Broadcasting\BroadcastServiceProvider::class, 8 Illuminate\Bus\BusServiceProvider::class, 9 Illuminate\Cache\CacheServiceProvider::class, 10 Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, 11 Illuminate\Cookie\CookieServiceProvider::class, 12 Illuminate\Database\DatabaseServiceProvider::class, 13 Illuminate\Encryption\EncryptionServiceProvider::class, 14 Illuminate\Filesystem\FilesystemServiceProvider::class, 15 Illuminate\Foundation\Providers\FoundationServiceProvider::class, 16 Illuminate\Hashing\HashServiceProvider::class, 17 Illuminate\Mail\MailServiceProvider::class, 18 Illuminate\Notifications\NotificationServiceProvider::class, 19 Illuminate\Pagination\PaginationServiceProvider::class, 20 Illuminate\Pipeline\PipelineServiceProvider::class, 21 Illuminate\Queue\QueueServiceProvider::class, 22 Illuminate\Redis\RedisServiceProvider::class, 23 Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, 24 Illuminate\Session\SessionServiceProvider::class, 25 Illuminate\Translation\TranslationServiceProvider::class, 26 Illuminate\Validation\ValidationServiceProvider::class, 27 Illuminate\View\ViewServiceProvider::class, 28 29 /* 30 * Package Service Providers... 31 */ 32 33 /* 34 * Application Service Providers... 35 */ 36 App\Providers\AppServiceProvider::class, 37 App\Providers\AuthServiceProvider::class, 38 // App\Providers\BroadcastServiceProvider::class, 39 App\Providers\EventServiceProvider::class, 40 App\Providers\RouteServiceProvider::class, 41 //添加剛剛生成的provider 42 App\Providers\TestServiceProvider::class, 43 ],
五、在IndexController文件中添加執行代碼
1 namespace App\Http\Controllers; 2 3 use App\Contracts\Test; 4 5 class IndexController extends Controller 6 { 7 8 public function __construct(Test $test) 9 { 10 $this->test = $test; 11 } 12 13 public function index(Test $test) 14 { 15 app()->make('App\Contracts\Test')->doing(); 16 17 echo '<br>'; 18 //只有經過構造方法進行自動加載依賴的方式才能觸發契約的when綁定 19 $this->test->doing(); 20 21 echo '<br>'; 22 //由於laravel中的上下文綁定只能具體到類,因此這裏的$test實例依然爲普通綁定 23 $test->doing(); 24 25 } 26 }
運行後,會發現只有經過構造函數實例化的對象,才能觸發額外的分支綁定。經過這個小例子,咱們能夠很清楚的理解契約了,就是在不一樣狀況下的一個對接口的動態調用,算是java中多態和策略模式的另外一實現方式。使用了這種實現方式,可使咱們在開發過程當中的代碼更加靈活,在改變實現方式的時候,只需改變provider中的實現綁定,便可快速實現需求變動。
可能有人會發現咱們的demo在執行時須要顯示的使用make方法,一點也不優雅,這和laravel所宣揚的思想仍是有差距。那是由於還有一個facade門面功能尚未用上,後面咱們會來探尋一下facade究竟是個什麼東西。