簡述
當你接觸一段時間Laravel的Service Container, Service Provider,Contracts和Facade后,也許已經知道它們是什么了,但是對於如何使用,在什么時候使用,以及它們之間的關系是什么,還不是非常清楚。 而關鍵是如果你反復看文檔,你會被它坑死,因為文檔有些部分不但沒有解釋清楚,反而有誤導的內容; 現在我們就來一次性把它們搞定;
基本概念
在繼續本教程之前,你需要先對以上概念有基本了解,知道它們是什么;
Service Container和 Service Provider
Service Container,也就是IOC容器的使用並不依賴Service Provider,例如:
$app->make('App\Models\Post');
這句話和 new App\Models\Post;
的結果完全一樣; 另外你在控制器里使用構造函數,type-hint進行依賴注入,也完全和Service Provider沒有半毛錢關系。
總之,你可以完全不使用Service Provider;
Service Provider 和Contracts
如果說IOC容器的使用並不依賴Service Provider,那么為什么我們用composer下載擴展包的時候總是要在config/app.php
里綁定一下Service Provider呢,有時候還需要綁定一下Facade;
理解的思路是這樣的,Laravel核心類(Services)都是用接口(contracts)+實現來構成的, 如果不理解這個概念,仔細看文檔接口那一章。而你在使用的時候,如果要拿到某個接口實現的實例的話,需要用到Service Container,而要用Service Container去解析一個接口,而不是直接解析一個類,這時就要用到Service Provider
了,可以說,Service Provider的主要功能,就是來綁定接口的。
下我准備要講坑爹的事情了,在講接口綁定前,先了解一些基本的事實:
一些事實
$app->make('App\Models\Post');
你可以這樣寫,
$app->make('post');
也可以這樣寫,這里的post是一個別名,這個別名是造成混淆的主要地方; 這個時候你肯定在想,這樣寫有啥用,我去哪里關聯這個別名到App\Models\Post
呢?
Service Provider 的 bind方法
對,就是在Service Provider里用bind方法來綁定別名:
$this->app->bind('post', function ($app) {
return new App\Models\Post;
});
這樣綁定后你就可以$app->make('post');
這樣寫了;然而搞個別名到目前為止也沒什么卵用。沒關系,稍后會講到,它和Facade有關系;我們先來解釋文檔坑爹的地方:
文檔是這樣寫這個bind方法的:
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app['HttpClient']);
});
哇擦,您的這第一個參數到底填的啥啊,事實上,第一個參數可以填類的全稱,但是如果不是填簡稱,我這樣綁定有任何意義么? 后面再返回一個一樣的類實例? 咦?$app['HttpClient']這個是什么?? 其實它是告訴你可以在解析類的時候可以再接着注入一個其他類的實例;文檔大哥,拜托你解釋一下好不好,能不能舉個靠譜點的例子...
如果你到其他的擴展包中去看別人的bind的寫法,你會發現千奇百怪的綁定寫法,先不管他們,現在我們來看Service Provider對接口的使用方法,最最基本的原理是這樣的:
//給一個接口起個別名
$this->app->bind('event_pusher', function ($app) {
return new App\Contracts\EventPusher;
});
//指定這個接口應該解析的實例
$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');
通過這兩步,我們讓這個接口有了別名,也有了解析時對應的實現;
這樣,我們就可以:
$app->make('event_pusher');
得到App\Services\RedisEventPusher
;
Service Provider 和 Facades
我們來看Facade的寫法,比如說Illuminate\Support\Facades\Cache:
class Cache extends Facade
{
protected static function getFacadeAccessor() { return 'cache'; }
}
這個cache就是上面提到過的別名;
下面我們來看Facade的對應關系圖:
Facade Name | Facade Class | Resolved Class | Service Provider Binding Alias |
---|---|---|---|
Cache | Illuminate\Support\Facades\Cache | Illuminate\Cache\Repository | cache |
所以你調用Cache::get('user_id')的時候,實際上是調用了Illuminate\Support\Facades\Cache 這個類,get並不是這個類的靜態方法,事實上,get這個方法在Facade這個類里根本不存在,這正是它設計的本意,當get這個方法不存在的時候,它就會調用Facade基類里的__callStatic魔術方法(需要提前了解這個魔術方法),這個方法中就會把Service Provider中綁定的類(或接口)解析並返回出來,本例中也就是Illuminate\Cache\Repository 這個類,所以get其實是Illuminate\Cache\Repository這個類的方法;
然后我們在再看文檔,有的Facade怎么沒有別名呢?比如:
Facade Name | Facade Class | Resolved Class | Service Provider Binding Alias |
---|---|---|---|
Response | Illuminate\Support\Facades\Response | Illuminate\Contracts\Routing\ResponseFactory |
是的,你可以直接寫類的全稱,而不是別名,如果你看這個Illuminate\Support\Facades\Response源碼,它是這樣寫的:
class Response extends Facade
{
protected static function getFacadeAccessor()
{
return 'Illuminate\Contracts\Routing\ResponseFactory';
}
}
可以直接返回該類;
Facade的命名空間到底是什么
我們發現,在使用Cache::get('user_id')的時候,你可以使用use Cache
; 也可以使用use Illuminate\Support\Facades\Cache
;
這是為什么呢?
別忘了,你在config/app.php
里面Class Aliases 那里綁定過 Facade 別名,也就是:
'Cache' => Illuminate\Support\Facades\Cache::class,
這樣綁定過,你就可以直接use Cache
來使用Facade了;