Laravel的依賴注入常見問題和Repository設計模式
laravel的依賴注入是理解整個框架的核心,有點時間,寫幾個常見的錯誤和設計技巧:
1.依賴注入的類,必須是完整可用的功能類(所有的屬性都必須初始化)
這句話怎麼理解呢?是這樣的,比如我們寫一個類,
class Person{
protected $name;
public function setName($name){
$this->name=$name;
}
}
這樣的一個類,在其他應用上沒有問題,但是不是一個可用的依賴注入類,為什麼這麼說呢?因為$name這個屬性在物件生成的時候沒有賦值,而必須用一個setter賦值,laravel框架在解析這樣的類依賴關係的時候,不知道應用$name的時候,發現空值會報錯,這個類應該改寫成如下的類:
class Person{
protected $name;
public function __construct($name){
$this->name=$name;
}
}
這樣在物件初始化的時候,就對屬性進行賦值,這樣的類才能進行依賴注入。
所謂完整功能的類,就是一個類在生成物件的時候,必須對所有的屬性都進行初始化,這樣的類才可以依賴注入。
2.被依賴注入的類(也可以稱為服務)中的方法儘可能在最後丟擲一個異常
例如,我們常常會用Repository設計模式來處理與資料庫的互動動作,那麼,當資料庫操作的方法很多的時候,在哪個方法出錯了,debug就是一件非常痛苦的事情,一個良好的做法是,在Repository中間層中正常的操作返回資料,而如果出錯了,就丟擲一個異常,這樣對程式的除錯非常有幫助。
例如,我們在Repository設計模式中有一個修改密碼的方法,一般的寫法如下:
public function updatePassword(User $user, $input) : User
{
$user->update(['password' => $input['password']]);
return $user;
}
上面的方法不利於除錯,我們可以稍作修改:
public function updatePassword(User $user, $input) : User
{
if ($user->update(['password' => $input['password']])) {
return $user;
}
throw new GeneralException(__('exceptions.update_password_error'));
}
這個異常類可以是自定義的異常物件或標準異常物件,但是在異常中標註錯誤方法名,這樣的操作會有目的性。另外,如果不是針對公用API開發,而僅僅是做前後端分離的Api開發,可以不用去catch這些異常的處理,只要有了這個異常標註後,交給框架的異常處理機制就好了。
3.Postman除錯Api時,PUT/PATCH方法不能攜帶檔案或body引數
在使用資源控制器的開發Api程式的時候,用put方法或patch方法修改資源,用Postman除錯的話,無法在引數中攜帶檔案,這在後端進行除錯的時候挺棘手,一個簡單的處理方法,是在資源路由前面加一個Post路由來修改資源,並將post路由指向控制器的update方法,例如這樣:
Route::post('roles/{id}','Api\RoleController@update');
Route::resource('roles','Api\RoleController',['except'=>['create','edit']]);
這樣訪問的時候,在Postman中直接用post替代put就可以正常攜帶body檔案了,這樣,路由可以接收前端用axios發的ajax請求,同時沒有開發前端的時候可以用post暫時替代(測試後post方法也可以直接刪除)。
4.Repository設計技巧
現在的laravel設計web後臺的主要架構,無過於以下幾種:
1.模型寫資料庫的關聯關係,控制器寫獲取資料和展示檢視(web設計)
2.模型寫資料庫關聯關係和返回json資料的方法,不需要控制器(API設計)
3.模型寫資料庫關聯關係,控制器寫獲取資料和返回json資料邏輯(API設計)
4.模型寫關聯關係,獨立抽象層(Repository)寫資料庫操作方法,控制器寫返回資料/檢視邏輯(web和API)
前三種的方法比較常見,也是目前培訓機構的主要方法,第四種架構比較少見。
主要原因如下:
1.前三種設計的主要特點是,程式碼量比較少,不需要設計操作介面,不需要標準化,主要針對小型應用,邏輯關係簡單的web應用,同時靈活性比較低,程式碼維護困難。因為培訓機構講課專案的業務邏輯一般不會很複雜,所以對這個設計模式應用的比較少。
2.第四種方法比較不常見,主要因為這種方法適用於多人協作開發的大型專案,以下,我專門針對這種設計模式說一下操作的思路,您可以自己進行一些嘗試,慢慢就能體會到,這種模式下,多寫幾個介面帶來的程式碼量和它所帶來的好處相比,根本就微不足道。
Repository設計模式的一般架構:
首先,您得新建一個Repository資料夾,專門用來放置這個中間層的程式碼,位置放哪兒都無所謂,但是一般建議放在src下,並且我本人不是很推崇自己更改laravel整個專案的核心目錄結構,因為這樣的目錄邏輯便於大家遵循同一個目錄標準,這樣對交流很有好處。
其次,您可以在Repository資料夾下建立一個用於放置介面的資料夾,比如Repository/Contract資料夾,這個資料夾用來存放中間層的介面檔案,當然,如果您的資料庫操作不是很多,同時沒有多人協作的需求,就是您一個人單槍匹馬的寫後臺邏輯,那也可以不用寫Contract,直接寫Repository服務就好了,我們還是按照一般的流程來講吧。
再次,在Contract目錄下寫一系列的介面設計檔案,這是整個Repository抽象層操作的綱要,如果您剛接觸面向物件一般是寫不出來的,這個工作一般是比較有經驗的人來寫這個抽象介面,我拿其中一個介面來做例子:
interface BaseRepository{
public function query()
public function select(array $columns = ['*'])
public function make(array $attributes = []);
public function getModelById($modelClass,$id);
}
這個介面就定義了四個方法,然後,可以在Repository目錄下新建一個目錄,來存放實現這個介面的類(服務):
class EloquentBaseRepository implements BaseRepository
{
protected $model;
protected $filter=[];
public function __construct(Model $model)
{
$this->model=$model;
}
public function query()
{
return $this->model->newQuery();
}
//......省略一堆實現
}
這樣,就寫好了一個介面和一個實現類(服務),同時介面可以存在繼承關係,不過為了實現類的邏輯和介面一致,一定要講實現類目錄介面和類繼承結構與介面的層級關係保持一致!也就是說,介面怎麼繼承,實現類就怎麼繼承,介面擺哪兒,實現類就擺哪兒。
然後,可以把這個介面和實現類(服務)的關係註冊到服務提供者中,這裡可以直接註冊到app服務提供者,也可以建立一個新的服務提供者,再把服務提供者註冊到app的provider中,這樣就可以在控制器的建構函式中依賴注入介面,而後通過依賴注入物件直接呼叫服務中的方法。
由此可見,使用這種模式設計,利於講資料庫操作規範化,同時如果後續要修改,不用修改控制器中的程式碼,只要再寫一個服務,然後註冊服務到提供者中繫結,就可以隨時抽換實現的方法,這也是控制反轉帶來的好處。
同時,值得一提的是,Repository設計模式,一般會將一個操作標準對應一個模型操作,同時對應一個控制器操作,也就是說,從Contract介面->EloquentRepository實現(實現)->控制器->模型,基本保持檔案一致,就是一個介面對應一個實現類,對應一個控制器類,對應一個模型,這樣寫的邏輯思路會很清晰,但是從另外一個角度講,這種設計模式本身就是針對團隊協作的標準化而量身定製的,如果您是一個人寫一個小專案,就沒必要這樣設計了,因為在設計的時候,改一行程式碼要來回切換四個檔案,在開發效率或過度開發上來講,只要追蹤的方法在您的可控範圍之內,就完全沒必要這樣把一個完整操作拆成四步操作來做。
晚了,先寫這麼多吧,有時間再慢慢寫。。。。。。。。。。。。。。