聲明:本文並不是博主原創,而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,固然也不是原汁原味的翻譯,能保證90%的原汁性,另外由於是理解翻譯,確定會有錯誤的地方,歡迎指正。web
歡迎轉載,轉載請註明出處,謝謝!數據庫
Laravel框架的基礎在於其IoC容器。要想真正瞭解框架的核心,須要對容器有必定的概念。然而,咱們須要注意的是IoC僅是軟件設計模式:依賴注入的一種便利的實現形式。容器自己不是依賴注入的必要條件,在框架他只是讓其變得更加簡便。設計模式
首先,讓咱們探索下爲何依賴注入是有益的。考慮到以下代碼中的類和方法:架構
class UserController extends BaseController { public function getIndex() { $users = User::all(); return View::make('users.index', compact('users')); } }
代碼簡潔易懂,可是在沒有鏈接到數據庫的狀況下,咱們是沒法進行測試的。換句話說, Eloquent ORM 被緊密耦合到控制器中了。在未鏈接數據庫的狀況下,咱們沒法測試當前引用了Eloquent ORM的控制器的方法。這段代碼一樣違背了軟件設計原則 關注點分離(SoC) 。簡言之:控制器知道的太多。控制器無需知道數據_從何而來_,只需關注如何接入;無需關心數據庫在MySQL中是否可用,而只關心數據在_某處_可用。框架
關注點分離(Separation Of Concerns):測試
每一個類都應有其單一的職責,而且這個職責由這個類徹底封裝this
因此,將web層(controller)從數據層解耦分離出來會是有益的。這在咱們對數據進行存儲遷移時是有利的,也會使代碼的測試更爲簡單。將「Web」認爲是到「真正」應用的傳輸層。翻譯
想象一下,應用是一臺有着多種電纜接口的顯示器。咱們能經過HDMI,VGA或者DVI接入顯示功能。也可將應用比喻成你接入互聯網的電纜。顯示器的主要功能大部分依賴着電纜。而電纜僅僅是一種相似HTTP接入你應用的傳輸部件。因此咱們不想將這部份內容(控制器)和應用邏輯糅合在一塊。這種作法能夠容許任何傳輸層,好比API或者移動應用程序來接入咱們的應用邏輯。設計
因此,咱們再次注入一個存儲類,來代替現有將控制器和Eloquent ORM糅合在一塊的作法。code
首先,咱們定義一個接口和相應的實現:
interface UserRepositoryInterface { public function all(); } class DbUserRepository implements UserRepositoryInterface { public function all() { return User::all()->toArray(); } }
接下來,咱們向控制器中注入此接口的實現。
class UserController extends BaseController { public function __construct(UserRepositoryInterface $users) { $this->users = $users; } public function getIndex() { $users = $this->users->all(); return View::make('users.index', compact('users')); } }
如今,咱們的控制器根本不曉得數據存儲在何處,無知是福啊!咱們的數據能夠來自MySQL,MongoDB,甚至是來自Redis。咱們不知道這其中的區別,也不須要關心。僅僅這一點小小的改變,咱們就能夠將web層從數據層脫離,固然當數據存儲改變時也不會影響到咱們。
服從邊限
記得服從職責限定。應用中控制器和路由是HTTP和程序交互的中簡介,在大型程序中,不能將他們糅合到你的主要邏輯中。
爲了鞏固上面的知識,咱們從一個測試案例開始。首先,模擬一個庫並綁定到IoC容器中,而後確保控制器正確的調用了該庫:
public function testIndexActionBindsUsersFromRepository() { // Arrange... $repository = Mockery::mock('UserRepositoryInterface'); $repository->shouldReceive('all')->once()->andReturn(array('foo')); App::instance('UserRepositoryInterface', $repository); // Act... $response = $this->action('GET', 'UserController@getIndex'); // Assert... $this->assertResponseOk(); $this->assertViewHas('users', array('foo')); }
你在模仿我麼
示例中,咱們使用了
Mockery
模擬庫,它提供了一套表述簡潔的方法來模仿你的程序。Mockery可經過Composer進行安裝。
讓咱們經過另外一個示例來加深對依賴注入的理解。有這樣一個場景,咱們須要對用戶帳戶中發生的財務變動用進行通知。這裏咱們定義兩個接口,或者叫約定。這些約定將會使需求變動變的很便捷。
interface BillerInterface { public function bill(array $user, $amount); } interface BillingNotifierInterface { public function notify(array $user, $amount); }
緊接着,咱們來實現BillerInterface
接口:
class StripeBiller implements BillerInterface { public function __construct(BillingNotifierInterface $notifier) { $this->notifier = $notifier; } public function bill(array $user, $amount) { // Bill the user via Stripe... $this->notifier->notify($user, $amount); } }
因爲各個類之間已經進行了職責分離,爲財務帳單(billing)類注入不一樣的通知程序將會很方便。好比注入短信通知類SmsNotifier
或者郵件通知類EmailNotifier
。咱們的帳單系統不須要考慮帳單通知的實現,只須要根據約定執加載通知便可。凡是遵照約定的帳單,都能實現對用戶財務變動的通知。此外,不光咱們添加方便,咱們也能夠單獨模擬BillingNotifierInterface
接口,來測試帳單系統。
善用接口
接口寫起來看似添加了不少額外的東西,實際是在加速咱們的開發。咱們能夠在不實現接口的狀況下,模擬已開發的接口,來對整個底層邏輯進行測試。
那問題來了,怎麼實現依賴注入呢?
$biller = new StripeBiller(new SmsNotifier);
如上,簡單吧,這就是依賴注入。只須要將通知器傳入到帳單系統,而不用擔憂通知器的使用。微小的改動就能是代碼很清晰,這種清晰的職責界定設計,讓咱們使代碼維護簡單,固然也方便模擬測試。
那IoC容器是怎麼一回事?依賴注入必需要用到他麼?這裏固然不是!在之後的章節中,咱們會看到IoC容器只是爲了更好的組織管理依賴注入,但它並不是必須。只要遵循本章中介紹的設計原則,你能夠在任何項目中實現依賴注入,也不用管是否有這樣一個容器可用。
不少人指責,在PHP中使用接口把代碼變的太過冗長,太象「JAVA」。你必須定義一個接口並實現一個類,這得多敲多少代碼。
在小而簡的項目中,我認可這種批判。這樣的項目中,接口是沒必要要的,由於就你本身用,之後也不會去改。即便架構上牛逼的架構師也會說「需求永遠不會肯定」,可是須要認可的是,總有tm那麼一些地方就是改不着。
接口在大型項目中是很是有用的,這樣額外的代碼是爲了保證將來你代碼的靈活性和可測試性。當你快速切換代碼實現的時候,必定會閃瞎某些人的狗眼。固然咱們的目的是爲了讓代碼可以適應各類操蛋需求的變動。
總之,咱們一直提倡「簡潔」架構。若是若是你的項目很小,不須要遵循這麼多規範,也別很差意思。代碼敲的怎麼爽怎麼來。若是不寫接口,也行,之後再說唄,又不是結婚買房,都tm逼的。