1. 程式人生 > 其它 >[譯]Laravel 5.0 之方法注入

[譯]Laravel 5.0 之方法注入

本文譯自 Matt Stauffer 的系列文章.


Laravel 5.0 中, 容器可以對其解析的方法進行自動分析, 然後根據型別限制把方法所需要的依賴項自動注入. 本文將介紹這一機制的原理, 何時解析, 如何注入等.

依賴注入的背景知識

在現代程式設計實踐中, PHP 開發者要學會的首要知識之一就是使用依賴注入. 這就是 SOLID(單一功能, 開閉原則, 里氏替換, 介面隔離以及依賴反轉) 中的 依賴反轉(Dependency Inversion).

Laravel 的 容器 被稱為 IOC(Inversion of Control) 容器, 之所以如此命名, 是因為它允許開發者掌控應用底層所發生的事件: 在頂層程式碼(controllers, 擴充套件類等)中請求一個例項, 比如 "mailer", 容器就會返回一個 "mailer" 的例項. 這樣, 頂層程式碼不關注底層到底是由哪個服務來發送郵件--不管是 Mandrill, Mailgun 還是 SendMail, 都不重要, 因為所有 mailer 類都實現相同的介面.

Laravel 4 中的建構函式注入

下面是一個以前的依賴注入的示例:

...
class Listener
{
    protected $mailer;

    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public function userWasAdded(User $user)
    {
        // Do some stuff...

        $this->mailer->send('emails.welcome', ['user' => $user], function($message)
        {
            $message->to($user->email, $user->name)->subject('Welcome!');
        });
    }
}

從例子中可以看到, 可以通過建構函式把 Mailer 類注入到物件. Laravel 的容器讓例項化這樣的一個類變得很容易, 因為它會自動把依賴項注入建構函式. 比如, 我們可以建立該類的一個新例項, 但不需要傳入 Mailer. 因為 Laravel 自動分析建構函式, 知道並且自動替我們注入了這個物件.

$listener = App::make('Listener');

這很方便, 因為

  1. 在應用中可以只定義一次 Mailer 的具體實現, 而不是每次都要指定.
  2. 由於採用了依賴注入, 更便於進行測試.

衝突

假如只是物件中的某一個方法需要用到注入的類呢? 建構函式會因為很多隻用到一次的注入變得非常凌亂.

另一種情況, 假如需要通過注入類執行某些操作, 但只針對特定的方法執行呢? 比如 FormRequests 和 ValidatesUponResolved.

解決方案

上述問題的解決方案就是方法注入: 類似建構函式注入, 但允許容器要呼叫某個方法的時候直接給該方法注入依賴項.

我覺得方法注入最普遍的應用場景就是控制器(controllers). 比如前文提到的 FormRequests 就是一個最好的例子. 但有關 FormRequests 之前已經有詳細的介紹, 所以這次我們舉點別的例子:

...
class DashboardController extends Controller
{
    public function showMoneyDashboard(MoneyRepository $money)
    {
        $usefulMoneyStuff = $money->getUsefulStuff();
        return View::make('dashboards.money')
            ->with('stuff', $usefulMoneyStuff);
    }

    public function showTasksDashboard(TasksRepository $tasks)
    {
        $usefulTasksStuff = $tasks->getUsefulStuff();
        return View::make('dashboards.tasks')
            ->with('stuff', $usefulTasksStuff);        
    }

    public function showSupervisionDashboard(SupervisionRepository $supervision)
    {
        $usefulSupervisionStuff = $supervision->getUsefulStuff();
        return View::make('dashboards.supervision')
            ->with('stuff', $usefulSupervisionStuff);
    }
}

我們把控制器的 public methods 對映到路由, 使用者訪問對應的路由時, 容器會呼叫這些方法, 並自動注入指定的依賴項. 非常棒, 非常簡潔.

容器在什麼時候會解析方法

前文介紹的控制器方法會被容器解析. ServiceProvider 的 boot 方法也會. 實際上你可以根據你的需要指定容器對任何方法進行解析. 比如:

...
class ThingDoer
{
    public function doThing($thing_key, ThingRepository $repository)
    {
        $thing = $repository->getThing($thing_key);
        $thing->do();
    }
}

然後可以在控制器中通過 App::call() 來呼叫它. App::call() 的第二個引數是可選的, 它接受以陣列方式提供的被呼叫方法所需的引數:

namespace AppHttpControllers;

use IlluminateContractsContainerContainer;
use IlluminateRoutingController;

class ThingController extends Controller
{
    public function doThing(Container $container)
    {
        $thingDoer = $container->make('ThingDoer');

        // 呼叫 $thingDoer 物件的 doThing 方法, 並傳入 $thing_key 引數,
        // 引數的值是 'awesome-parameter-here'
        $container->call(
            [$thingDoer, 'doThing'],
            ['thing_key' => 'awesome-parameter-here']
        );
    }
}

寫在最後

在 Laravel 核心程式碼中, 用方法注入實現了一些有用的系統功能, 比如 FormRequest. 但別讓這些案例侷限了你的思維. 它只是讓程式碼保持精簡的一個手段, 而我們都需要簡潔的程式碼.