[譯]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');
這很方便, 因為
- 在應用中可以只定義一次 Mailer 的具體實現, 而不是每次都要指定.
- 由於採用了依賴注入, 更便於進行測試.
衝突
假如只是物件中的某一個方法需要用到注入的類呢? 建構函式會因為很多隻用到一次的注入變得非常凌亂.
另一種情況, 假如需要通過注入類執行某些操作, 但只針對特定的方法執行呢? 比如 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. 但別讓這些案例侷限了你的思維. 它只是讓程式碼保持精簡的一個手段, 而我們都需要簡潔的程式碼.