昨天介紹了 Laravel 的 Service Container ,其主要的功能就是建立創建類別實例的捷徑,而 Laravel 藉由這個功能實現了依賴注入的架構。
至於何為依賴注入呢? 首先看看下面的例子
use App\Services\TodoService;
class TodoController extends Controller
{
protected $todoService;
public function __construct(
TodoService $todoService,
) {
$this->todoService = $todoService;
}
//...
}
當 TodoController 被建立的時候,我們藉由 Service Container 創建了 TodoService 的實例並傳入,然後把這個實例賦予為 TodoController 的內部屬性。
接著我們就能在 TodoController 中藉由這個內部屬性使用 TodoService 實例的函式
class TodoController extends Controller
{
//...
public function store(Request $request)
{
$data = $request->all();
$this->todoService->create([
'name' => $data['name']
]);
}
}
這種在外部創建類別實例當作參數傳入類別的做法就稱為依賴注入,相反的作法就是在類別內部創建實例。
use App\Services\TodoService;
class TodoController extends Controller
{
protected $todoService;
public function __construct() {
$todoService = new TodoService;
$this->todoService = $todoService;
}
//...
}
這種做法的壞處在於 TodoController 是直接依賴於 TodoService 這個類別,導致沒辦法輕易的替換掉 TodoService 。
這樣在進行測試的時候就很傷腦筋了,只是想做 TodoController 的單元測試卻必須先確保 TodoService 能正常運作,才能夠進行測試。
這時候就顯現出 Service Container 與依賴注入的優點了,當我們要測試時,先在 Service Container 註冊 TodoService::class 的實例為 Mock 版的,就不用擔心測試會受 TodoService 的功能所影響了。
use App\Services\TodoService;
use Mockery;
public function test_with_mocked_instance()
{
$this->instance(
TodoService::class,
Mockery::mock(TodoService::class)
);
}
前面都注入的都是類別,但在 Service Container 中我們也可以注入介面,只要有先註冊好介面對應的實作類別就好。
use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;
$this->app->bind(EventPusher::class, RedisEventPusher::class);
use App\Contracts\EventPusher;
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
好玩的在於,我們可以隨時替換註冊的實作類別,整組整組的替換掉功能。
use App\Contracts\EventPusher;
use App\Services\SqlEventPusher;
$this->app->bind(EventPusher::class, SqlEventPusher::class);
程式碼裡的 EventPusher 通通不用動,只要改一行程式碼就好。
這就是依賴注入帶來的彈性,善用的話可以極大的幫助測試與維護作業。