IoC 容器

介紹

Laravel 的依賴反轉 ( IoC, inversion of control ) 容器是管理類別依賴的強力工具。 依賴注入 ( Dependency injection ) 是一種移除 hard-coded 類別依賴的方式。相較之下,在執行的時候才注入依賴, 可以擁有更好的彈性,在替換依賴實體時相當容易。

理解 Laravel IoC 容器對於建立強力且大型的應用程式,以及改進 Laravel 核心是很重要的。

基本用法

綁定型別到容器

IoC 容器有兩種解析依賴的方式:經由閉合函數或自動解析。我們將先探索閉合函數的用法。首先,綁定一個「型別」到容器:

App::bind('foo', function($app)
{
    return new FooBar;
});

從容器解析型別

$value = App::make('foo');

App::make 方法被呼叫,綁定的閉合函數會被呼叫並返回結果。

綁定「共享」的型別到容器

有時候,你可能希望綁定到容器的型別只會被解析一次,之後的呼叫都返回相同的實例:

App::singleton('foo', function()
{
    return new FooBar;
});

綁定已存在的實例到容器

你也可以使用 instance 方法,綁定一個已經存在的實例到容器:

$foo = new Foo;

App::instance('foo', $foo);

何處註冊綁定

IoC 綁定跟「註冊事件處理」或是「註冊路由」一樣,通常稱為「起始碼」。換句話說,IoC 綁定後等待請求,在路由或控制器呼叫時才實際執行。像其他的「起始碼」一樣,start 檔案總是註冊 IoC 綁定的一個選擇。或者,你可以建立一個 app/ioc.php 檔案(檔名不重要),並且從 start 檔案引入。

如果你的應用程式有很多 IoC 綁定,或是想要分門別類,在不同檔案組織綁定,你可以註冊綁定在 服務提供者

自動解析

在很多情境下,IoC 容器不需要額外設定就有能力自動解析類別。例如:

class FooBar {

    public function __construct(Baz $baz)
    {
        $this->baz = $baz;
    }

}

$fooBar = App::make('FooBar');

雖然我們沒有註冊 FooBar 類別綁定到容器,它還是可以解析類別,甚至自動注入 Baz

如果容器裡沒有找到對應的型別綁定,容器會利用 PHP 的 Reflection 檢查類別,並且解讀傳入建構子的型別提示。利用這些資訊,讓容器可以自動建立類別實例。

綁定實例的介面

然而,有些時候,一個類別可能需要依賴介面,而不是一個「具體的型別」。這些情況下,App::bind 方法用來通知容器要注入哪個介面實例:

App::bind('UserRepositoryInterface', 'DbUserRepository');

考慮以下的控制器:

class UserController extends BaseController {

    public function __construct(UserRepositoryInterface $users)
    {
        $this->users = $users;
    }

}

既然我們已經綁定 UserRepositoryInterface 到一個具體型別,DbUserRepository 會在控制器建立時自動注入。

應用

在 Laravel 裡,很多時候使用 IoC 容器,可以增加應用程式的彈性與可測試性。一個基本的範例是用在解析控制器。所有的控制器都會經由 IoC 容器解析,意味著你可以在控制器的建構子注入型別提示依賴,之後依賴就會自動被注入。

注入型別提示 ( Type-Hinting ) 依賴到控制器

class OrderController extends BaseController {

    public function __construct(OrderRepository $orders)
    {
        $this->orders = $orders;
    }

    public function getIndex()
    {
        $all = $this->orders->all();

        return View::make('orders', compact('all'));
    }

}

在這個範例裡,OrderRepository 類別會自動被注入到控制器。意味著在 單元測試 時,可以綁定一個「mock」的 OrderRepository 到容器裡,之後被注入到 控制器,讓你不用在測試時一定要和資料庫層互動。

其他 Ioc 應用範例

Filterscomposers事件處理 也可以使用 IoC 容器解析。只要在註冊的時候設定要使用的類別名稱:

Route::filter('foo', 'FooFilter');

View::composer('foo', 'FooComposer');

Event::listen('foo', 'FooHandler');

服務提供者( Service Provider )

使用服務提供者是一個很好的方式,可以把相關的 IoC 註冊放到到同一個地方。可以將服務提供者想像成是一個在應用程式裡啟動元件的方式。你可以在裡面註冊自定的會員認證,綁定應用程式的儲存庫類別到 IoC 容器,或甚至設定自定的 Artisan 指令。

事實上,大部份的 Laravel 核心元件都有服務提供者,所有被註冊的服務提供者都列在 app/config/app.php 設定檔的 providers 陣列裡。

定義一個服務提供者

要建立一個服務提供者,只要繼承 Illuminate\Support\ServiceProvider 類別,然後在裡面定義一個 register 方法:

use Illuminate\Support\ServiceProvider;

class FooServiceProvider extends ServiceProvider {

    public function register()
    {
        $this->app->bind('foo', function()
        {
            return new Foo;
        });
    }

}

注意,在 register 方法裡,經由 $this->app 使用 IoC 容器。當你建立了一個服務 而且準備要註冊到你的應用程式裡時,只要把它加到你的 app 設定檔的 providers 陣列裡即可。

在執行期間註冊服務提供者

你也可以使用 App::register 在執行期間註冊服務提供者:

App::register('FooServiceProvider');

容器事件

註冊解析事件的監聽

每當容器解析一個物件時就會觸發事件,你可以使用 resolving 方法監聽這個事件:

App::resolvingAny(function($object)
{
    //
});

App::resolving('foo', function($foo)
{
    //
});

注意,被解析的物件會被傳到閉合函式。