## 如何使用 Service 模式
若將數據庫邏輯都寫在 Controller 里,會造成 Controller 代碼的臃腫難以維護,基于 SOLID 原則,我們應該使用 **Service** 模式輔助 Controller,將相關的業務邏輯封裝在不同的 Service,方便項目的后期維護。
### Laravel 框架版本
Laravel 5.4.17
## 業務邏輯
業務邏輯中,常見的如:
* 牽涉到外部行為: 如 `發送 Email 郵件`,`使用外部API` ..
* 使用 PHP 寫的邏輯: 如 `根據購買的數量,給予不同的折扣`
## Service
### 牽涉到外部的行為
如 `發送Email`,常常會在 Controller 中直接調用 `Mail::queue()`
```
/**
* @param \Illuminate\Http\Request $request
*/
public function store(Request $request)
{
\Mail::queue('email.index', $request->all(), function (Message $message) {
$message->sender(env('MAIL_USERNAME'));
$message->subject(env('MAIL_SUBJECT'));
$message->to(env('MAIL_TO_ADDR'));
});
}
```
在中大型的項目中,會有幾個問題:
* 將牽涉到外部行為的邏輯寫在 Controller,造成 Controller 代碼臃腫難以維護
* 違反 SOLID 的單一職責原則:外部行為不應該寫在 Controller
* Controller 直接相依于外部行為,使得我們無法對 Controller 做單元測試
比較好的方式是使用 Service,使用的步驟如下:
* 將外部行為注入到 Service
* 在 Service 使用外部行為
* 將 Service 注入到 Controlelr
`app\Services\EmailService.php`
```
<?php
namespace App\Services;
use Illuminate\Mail\Message;
use Mail;
/**
* Class EmailService
*
* @package \App\Services
*/
/**
* Class EmailService
*
* @package App\Services
*/
class EmailService
{
/**
* @var \Mail
*/
protected $mailer;
/**
* 將相依的 Mailer 注入到 EmailService
* EmailService constructor.
*
* @param $mailer
*/
public function __construct(Mail $mailer)
{
$this->mailer = $mailer;
}
/**
* 發送 Email的邏輯寫在 send() 不是使用 Mail Facade,而是使用 $this->mailer
* @param array $request
*/
public function send(array $request)
{
$this->mailer->queue('email.index',$request,function(Message $message){
$message->sender(env('MAIL_USERNAME'));
$message->subject(env('MAIL_SUBJECT'));
$message->to(env('MAIL_TO_ADDR'));
});
}
}
```
`app\Controllers\UserController.php`
```
<?php
namespace App\Http\Controllers;
use App\Services\EmailService;
use Illuminate\Http\Request;
/**
* Class UserController
*
* @package App\Http\Controllers
*/
class UserController extends Controller
{
/**
* @var \App\Services\EmailService
*/
protected $emailService;
/**
* @param \Illuminate\Http\Request $request
*/
public function store(Request $request)
{
$this->emailService->send($request->all());
}
}
```
從原本相依于 `Mail Facade` ,改成相依于注入的 `EmailService`。
改用這種寫法有幾個優點,如下:
* 將外部行為寫在 Service,解決了 Controller 代碼臃腫的問題。
* 符合 SOLID 的單一職責原則: 外部行為寫在 Service ,沒寫在 Controller。
* 符合 SOLID 的依賴反轉原則:Controller 并非直接相依于 Service,而是將 Service 依賴注入進 Controller。
### 使用 PHP 寫的邏輯
如 `根據用戶購買數量,給予同步的折扣`,可能我們會在 Controller 直接寫 `if () { ... } else { ... }` 邏輯。如下`app\Controllers\UserController.php`:
```
public function index(Request $request)
{
$number = $request->input('number');
$price = 500;
$discount = 1;
if ($number == 1) {
$discount = 1;
} elseif ($number == 2) {
$discount = 0.9;
} elseif ($number == 3) {
$discount = 0.8;
} else {
$discount = 0.7;
}
$total = $price * $number * $discount;
return $total;
}
```
在中大型項目中,會有幾個問題:
* 將 PHP 寫的業務邏輯直接寫在 Controller ,造成 Controller 的代碼臃腫難以維護
* 違反了 SOLID 的單一職責原則:業務邏輯不應該寫在 Controller
* 違反了 SOLID 的單一職責原則:若未來想要改變折扣的寫算法,都需要用到此 Method,也也就是說這個 Method 同時包含了計算折扣于計算加總的職責,因此違反了 SOLID 的單一職責原則
* 直接寫在 Controller 的邏輯無法被其他 Controller 使用
`app\Services\OrderService.php`
```
<?php
namespace App\Services;
/**
* Class OrderService
*
* @package App\Services
*/
/**
* Class OrderService
*
* @package App\Services
*/
class OrderService
{
/**
* 計算折扣
*
* @param $number
*
* @return float
*/
public function getDisCount($number)
{
switch ($number) {
case 1:
return 1.0;
break;
case 2:
return 0.9;
break;
case 3:
return 0.8;
break;
default:
return 0.7;
}
}
/**
* 計算最后價格
*
* @param $number
* @param $discount
*
* @return int
*/
public function getTotal($number, $discount)
{
return 500 * $number * $discount;
}
}
```
在 Controller 中調用代碼,如下:
```
<?php
namespace App\Http\Controllers;
use App\Services\OrderService;
use Illuminate\Http\Request;
/**
* Class UserController
*
* @package App\Http\Controllers
*/
class UserController extends Controller
{
/**
* @var \App\Services\EmailService
*/
protected $orderService;
/**
* UserController constructor.
*
* @param \App\Services\OrderService $orderService
*/
public function __construct(OrderService $orderService)
{
$this->orderService = $orderService;
}
/**
* @param \Illuminate\Http\Request $request
*
* @return int
*/
public function index(Request $request)
{
$number = $request->input('number');
$discount = $this->orderService->getDisCount($number);
return $this->orderService->getTotal($number, $discount);
}
}
```
將原本的 `if () { .. } else { .. }` 邏輯改寫成使用 `OrderService`,Controller 變得非常感覺,也達成原來 Controller 接受 Http Request,調用其他 Class 的責任。
改用這種寫法的幾個優點:
* 將 PHP 寫的業務邏輯寫在 Service ,解決了 Controller 代碼臃腫的問題
* 符合 SOLID 的單一職責原則: 業務邏輯寫在 Service,沒寫在 Controller
* 符合 SOLID 的單一職責原則:計算折扣與計算加總分開在不同的 Method,且歸屬于 `OrderService`,而非 `Controller`
* 符合 SOLID 的依賴反轉原則: Controller 并非直接相依于 Service,而是將 Service 依賴注入進 Controller
* 其他 Controller 也可以重復使用這段業務邏輯
### 結束
* 實際上會有很多 Service ,需要自行依照 SOLID 原則去判斷是否該建立 Service
* Service 使得業務邏輯從 Controller 中解放,不僅更容易維護、更容易拓展、更容易重復使用且更容易測試
- 介紹
- Laravel5發送郵件使用Service隔離業務
- 如何使用Repository模式
- 如何使用Service模式
- 如何使用Presenter模式
- Laravel 5.* 執行遷移文件報錯:Specified key was too long error
- EloquentORM關聯關系
- EloquentORM關聯關系之一對一
- EloquentORM關聯關系之一對多
- EloquentORM關聯關系之遠層一對多
- EloquentORM關聯關系之多對多
- EloquentORM關聯關系之多態關聯
- EloquentORM關聯關系之多對多多態關聯
- Laravel測試
- Laravel中涉及認證跳轉地址的修改的地方
- Laravel中Collection的基本使用
- all
- avg
- chuck
- collapse
- combine
- contains
- containsStrict
- count
- diff
- diffAssoc
- diffKeys
- each
- every
- except
- filter
- first
- flatMap
- flatten
- flip
- forget
- forPage
- get
- groupBy
- has
- implode
- intersect
- intersectKey
- isEmpty
- isNotEmpty
- keyBy
- keys
- last
- map
- mapWithKeys
- max
- median
- merge
- min
- mode
- nth
- only
- partition
- pipe
- pluck
- pop
- prepend
- pull
- push
- put
- random
- reduce
- reject
- reverse
- search
- shift
- shuffle
- slice
- sort
- sortBy
- sortByDesc
- splice
- split
- sum
- take
- tap
- times
- toArray
- toJson
- transform
- union
- unique
- uniqueStrict
- values
- when
- where
- whereStrict
- whereIn
- whereInStrict
- whereNotIn
- whereNotInStrict
- zip
- Laravel中Collection的實際使用
- collection中sum求和
- collection格式化計算數據
- collection格式化計算數據計算github事件得分總和
- collection格式化markdown數據列表
- collection格式化計算兩個數組的數據
- collection中reduce創建lookup數組
- TODO