Laravel 服務容器中最強大的功能之一就是通過反射來自動解析類的依賴。反射是一種在運行時檢查類和方法的能力,比如,PHP 的 `ReflectionClass` 類可以動態檢查給定類的所有方法,PHP 函數 `method_exists` 從某種意義上說也是一種反射(關于反射的更多細節可以查看 [PHP 反射文檔](http://php.net/manual/zh/book.reflection.php))。在開始進入正題之前,我們先來看看 PHP 中反射類的使用:
```php
$reflection = new ReflectionClass(\App\Services\StripeBiller::class);
dump($reflection->getMethods()); # 獲取 StripeBiller 類中的所有方法
dump($reflection->getNamespaceName()); # 獲取 StripeBiller 的命名空間
dump($reflection->getProperties()); # 獲取 StripeBiller 上的所有屬性
```
打印結果如下:

> 注:還可以獲取類的很多其它信息,你可以自己查看[相應 API](http://php.net/manual/zh/class.reflectionclass.php),然后去玩玩。
依靠這個強大的 PHP 特性,Laravel 的服務容器可以做一些很奇妙的事情!例如,考慮下面這個類:
```php
class UserController extends BaseController
{
public function __construct(StripBiller $biller)
{
$this->biller = $biller;
}
}
```
注意這個控制器的構造函數在形參里[類型約束](http://php.net/manual/zh/language.oop5.typehinting.php)了一個 `StripBiller` 類,通過反射我們可以獲取這個類型約束指定的類。當 Laravel 的服務容器中沒有某個顯式綁定類的解析器時(即沒有注冊接口與對應實現的綁定),將會嘗試使用反射來解析。程序流程類似于下面這樣:
1. 已經有一個 `StripBiller` 的解析器了嗎?
2. 沒有?那用反射來檢查一下 `StripBiller` 吧,看看它有沒有依賴。
3. 解析 `StripBiller` 需要的所有依賴(遞歸處理)。
4. 使用 `ReflectionClass->newInstanceArgs()` 來創建一個新的 `StripBiller` 實例。
> 注:底層邏輯詳見 `Illuminate\Container\Container` 的 `resolve` 方法。
如你所見,容器替我們干了好多重活,這能幫你省去為每個類編寫解析器的麻煩。這就是 Laravel 服務容器最強大也是最獨特的特性,熟練掌握這個功能對構建大型 Laravel 應用是十分有用的。
下面我們修改一下控制器,看看改成這樣會發生什么事:
```php
class UserController extends BaseController
{
public function __construct(BillerInterface $biller)
{
$this->biller = $biller;
}
}
```
假設我們沒有為 `BillerInterface` 顯式綁定過任何解析器,即沒有在服務提供者中定義過下面這樣的綁定代碼:
```php
$this->app->bind(BillerInterface::class, function ($app) {
return new StripeBiller($app->make(BillingNotifierInterface::class));
});
```
容器該怎么知道要注入什么類呢?要知道,接口(或抽象類)本身是不能被實例化的。如果我們沒有告知容器更多信息的話,容器是無法實例化這個依賴的。我們需要明確指出哪個類是這個接口的默認實現(正如我們之前在服務提供者 `AppServiceProvider` 中所做的那樣),這就需要用到 `bind` 方法:
```php
$this->app->bind(BillerInterface::class, StripeBiller::class);
```
> 注:這種方式會在綁定時就會實例化 `StripeBiller`,性能不及匿名函數。
這里,我們只傳了一個字符串進去,而不是一個匿名函數。這個字符串告訴容器總是使用 `StripBiller` 類作為 `BillerInterface` 接口的默認實現類。此外,我們也獲得了在容器綁定中只修改一行代碼即可輕松切換服務實現的能力。例如,如果我們需要切換到余額支付作為我們的支付提供者,只需要新寫一個 `BalancedBiller` 來實現 `BillerInterface` 接口,然后修改容器綁定如下:
```php
$this->app->bind(BillerInterface::class, BalancedBiller::class);
```
這樣,新的實現就可以在整個應用中生效了!
在綁定實現到接口時,你也可以使用 `singleton` 方法,這樣容器在整個請求生命周期中只會實例化這個實現類一次,從而實現單例模式:
```php
$this->app->singleton(BillerInterface::class, StripeBiller::class);
```
> 掌握容器:想了解更多關于容器的知識?去讀源碼吧!容器在底層只有一個類`Illuminate\Container\Container`,讀完了你就會對容器如何工作有更深的理解。