# laravel 三層式架構 開發規範
+ 提供一些開發規範,讓大家在各個功能撰寫使用上可以有個區隔的判斷
+ 此規範以可維護性,與可讀性為基礎構思設計
+ 可以依專案需求進行變形
## 三層式架構
+ 商業邏輯層 BLL ( Business Logic Layer )
將商業邏輯或是商業流程放置Service
+ 資料存取層 DAL ( Data Access Layer )
將查詢與DB操作單獨抽出來,放到Repository
+ 表現層 USL ( User Show Layer 或 UI 或 Presentation layer)
將外觀顯示邏輯放置Presenter
## 開發原則
+ 職責分離 (Segregation of Duties, SOD)
職責分離是指遵循不相容職責相分離的原則,實現合理的組織分工。
例如一個公司的授權、簽發、核准、執行、記錄工作,不應該由一個人擔任。
+ 關注點分離 (Separation of Concerns, SOC)
就是將交錯著各種目的的複雜程式碼,依程式碼的概念、目的,進行分類、篩檢、整理。讓高複雜性程式碼切割、轉換為簡潔易懂的單純性程式碼。
## 架構圖

## 開發重點與規範
+ 主要針對Controller、Service、Repository與Presenter比較容易有爭議的部分進行規範
+ 其餘部分Route、Middleware、View、Model等,按照原先系統規劃的方式撰寫即可
### ***Controllers***
+ **接收 HTTP request,調用其他 service**
#### 資料預處理
+ request資料處理,request不能直接帶入service
+ service有使用session也請這在處理帶入service
```
$a = $request["a"];
$b = $request["b"];
$c = $request["c"];
$d = $session->get['a'];
```
#### 清楚的service傳入參數
+ 決定使用哪些service,並將處理過的資料帶入,只能將會使用的資料帶入
+ 不能帶入request
```
$service->onefunction(
$a,
$b,
$c
);
```
#### 回傳類別
+ response
+ 定義return訊息
+ 不直接回傳service return的data
+ 決定return的格式,與訊息
```
return response()->json([
'status' => false,
'data' => [
'a' => '範例',
'b' => [
'type' => 'good',
'text' => 'ABC'
],
'c' => ['c1' => true]
]
]);
```
+ view
+ 決定view與帶入資料
+ 指定輸出的view,資料帶入部分,不同service回傳資料,使用不同變數名稱帶入view
```
return view('view.view',[
'data1' => $serviceResult1,
'data2' => $serviceResult2
]);
```
### ***Service***
+ **輔助 controller,處理商業邏輯,然後注入到 controller**
#### 傳入資料項目明確
+ 請明確定義有哪些資料會使用,不能傳入request不明確內容的變數
```
public function onefunction(
$a,
$b,
$c = null
){
$d = $a + $b;
if($c != null)
$d = $d - $c;
return $d;
}
```
#### service事件盡量單一功能化
+ 事件單一,如果有複雜事件,拆成多個service
+ 單一職責
+ 盡量採取物件導向開發原則
```
舉例
預約訂單功能:
1.檢查訂單
2.新增使用者(新使用者)
3.新增訂單
4.發信給管理者
5.完成預約
public function 新增預約訂單(
$user,
$order
){
$user_id = $this->新增使用者($user);
$order_id = $this->新增訂單(
$user_id,
$order
);
if($order_id){
$state = $this->發信給管理者($order_id);
if($state)
return 成功;
else
return 失敗;
}
}
public function 新增使用者($user){
$id = $userRepository->selectUser($user);
if($id){
$id = $userRepository->creatUser($user);
}
return $id;
}
public function 新增訂單(
$user_id,
$order
){
$order_id = $orderRepository->creatOrder(
$user_id,
$order
);
return $order_id;
}
public function 發信給管理者($order_id){
$data = $orderRepository->selectOrder($order_id);
$eamilService->sendMail($data);
return true;
}
```
#### 固定參數使用config or env
+ 防止太多地方要使用同一參數,修改時遺漏修改
+ 讓程式保持一定程度的修改彈性
+ 不同機器,請用env,例如:DB連線資訊、發信信箱資訊
+ 不同環境情況時,請用config,例如:預設語系、預設時區
#### 不直接使用DB
+ 避免閱讀混亂
+ DB指令的集中管理
+ 在簡單的DB指令也要寫Repository
#### 不用global變數
+ 避免service或者其他功能相互影響
+ 方便抓錯與閱讀理解程式
+ 如有需要共用變數請善用session,且請於controller帶入
#### function 內變數獨立
+ 增加功能的可讀性
+ 修改可以保證不會影響其他function功能
+ 方便進行遷移或移轉至其他的service使用
#### 註解請寫在該行程式碼上方,並且與程式縮排相同
+ 加速程式閱讀,快速理解該行為模式
+ 請說明該步驟是要做什麼功能或效果
```
註解範例
//解查XXX後,新增訂單
public function orderNew(
$user,
$order
){
//判斷並新增用戶
$this->userNew($user);
//新增訂單
$state = $this->新增訂單(
$user,
$order
);
if($state){
//發信給管理者
$this->postEmail($order);
//發信成功回傳成果
return 成功;
}
//訂單新增失敗
return 失敗;
}
```
### ***Repository***
+ **輔助 model,處理資料庫邏輯,然後注入到 service**
#### 不能丟陣列進insert或update直接跑DB
+ 直接使用array新增很方便,但是不可讀
+ 可以保持SQL指令的乾淨,以及修改性
+ 同一個table,如果有多種insert的情況,可以參考下列作法
+ 把重要的變數往前擺放,其餘使用funcction預設值帶入,如舉例1
+ 將insert分成不同的function,如舉例2
```
舉例1
//有三筆資料
$Repository->selectDB(
1,
2,
3
);
//有四筆資料
$Repository->selectDB(
1,
2,
3,
4
);
//有五筆資料
$Repository->selectDB(
1,
2,
3,
4,
5
);
public function selectDB(
$a,
$b,
$c,
$d = 0,
$e = null
)
{
$data = Company::where('id', function ($query) use ($a) {
$query->select('XXX')
->from('XXX')
->where('id', $a);
})
->first();
return $data;
}
```
```
舉例2
//只有三筆資料
$Repository->selectDB1(
1,
2,
3
);
//只有五筆資料
$Repository->selectDB2(
1,
2,
3,
4,
5
);
public function selectDB1(
$a,
$b,
$c
)
{
$data = Company::where('id', function ($query) use ($a) {
$query->select('XXX')
->from('XXX')
->where('id', $a);
})
->first();
return $data;
}
public function selectDB2(
$a,
$b,
$c,
$d,
$e
)
{
$data = Company::where('id', function ($query) use ($a) {
$query->select('XXX')
->from('XXX')
->where('id', $a);
})
->first();
return $data;
}
```
#### 不呼叫其他的Repository function
+ 增加可更動性,Repository function之間不互相干擾
+ 如果需要呼叫其他DB,請用join或service多步驟
#### 盡量不使用if else
+ 讓DB指令單一化,清楚知道該SQL指令的行為
+ 商業行為的條件判斷應該主要給service進行
+ 不影響輸出結果或不涉及商業邏輯,可以使用if else
+ 資料排序
+ 欄位有無
### ***Presenter***
+ **處理顯示邏輯,然後注入到view**
+ **這盡量少使用,如果view的切版有切好,應該不會增加太多可讀性,幫助不大(個人怨念)**
#### 不能連結任何後台的程式
+ 所有邏輯使用,應該於service處理完成
#### 變數只能使用view傳進來
+ 所有參數,應該於controller獲得
+ 在Controllers取值,一併帶入view
#### 不涉及JS行為操作
+ JS不應該使用Presenter內部定義的name或id
+ 嚴重增加可讀性跟Debug困難
+ 如果需要使用JS,可以使用以下方式
+ name或id是由view帶入Presenter,JS在取用時閱讀上直接在blade上可以找到
+ 將JS寫包含在Presenter的輸出,一併輸出至view
#### Presenter只能回傳html或html的內容,不能是參數
+ 資料單向性
+ 程式閱讀上為blade去找Presenter,Presenter不可以回到blade進行其餘操作
#### 不判斷blade區塊顯示與否
+ 不應該利用Presenter的回傳參數決定blade的顯示區塊
+ Presenter為單向由blade傳入,在由Presenter決定或判斷顯示區塊的內容
#### function互相不影響
+ 可讀性
+ 重複使用性
+ 移動性