没人能嘲笑你的梦想,他们都是在嘲笑你的实力
若将商业逻辑都写在 controller,会造成 controller 肥大而难以维护,基于SOLID原则,我们应该使用 Service 模式辅助 controller,将相关的商业逻辑封装在不同的 service,方便中大型程序的维护。
问题的提出
商业逻辑中,常见的如 :
- 牵涉到外部行为 : 如发送Email,使用外部API。
- 使用PHP写的逻辑 : 如根据购买的件数,有不同的折扣。
若将商业逻辑写在 controller,会造成 controller 肥大,日后难以维护。
如发送Email,初学者常会在 controller 直接调用 Mail::queue():
1 2 3 4 5 6 7 8
| 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 注入到 controller。
问题的解决
EmailService.php
app/Services/EmailService.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| namespace App\Services;
use Illuminate\Mail\Mailer; use Illuminate\Mail\Message;
class EmailService { private $mail;
public function __construct(Mailer $mail) { $this->mail = $mail; }
public function send(array $request) { $this->mail->queue('email.index', $request, function (Message $message) { $message->sender(env('MAIL_USERNAME')); $message->subject(env('MAIL_SUBJECT')); $message->to(env('MAIL_TO_ADDR')); }); } }
|
将相依的Mailer注入到EmailService。
1 2 3 4 5 6 7 8 9 10 11
| private $mail;
public function __construct(Mailer $mail) { $this->mail = $mail; }
|
将发送 Emai的商业逻辑写在send()。不是使用Mail facade,而是使用注入的$this->mail。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public function send(array $request) { $this->mail->queue('email.index', $request, function (Message $message) { $message->sender(env('MAIL_USERNAME')); $message->subject(env('MAIL_SUBJECT')); $message->to(env('MAIL_TO_ADDR')); }); }
|
UserController.php
app/Http/Controllers/UserController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| namespace App\Http\Controllers;
use App\Http\Requests; use Illuminate\Http\Request; use MyBlog\Services\EmailService;
class UserController extends Controller { protected $emailService;
public function __construct(EmailService $emailService) { $this->emailService = $emailService; }
public function store(Request $request) { $this->emailService->send($request->all()); } }
|
将相依的 EmailService 注入到 UserController
1 2 3 4 5 6 7 8 9 10 11
| protected $emailService;
public function __construct(EmailService $emailService) { $this->emailService = $emailService; }
|
从原本直接相依于 Mail facade,改成相依于注入的 EmailService
1 2 3 4 5 6 7 8 9 10
|
public function store(Request $request) { $this->emailService->send($request->all()); }
|
实际的问题
问题提出
如根据购买的件数,有不同的折扣,初学者常会在 controller 直接写 if…else 逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public function store(Request $request) { $qty = $request->input('qty');
$price = 500;
if ($qty == 1) { $discount = 1.0; } elseif ($qty == 2) { $discount = 0.9; } elseif ($qty == 3) { $discount = 0.8; } else { $discount = 0.7; }
$total = $price * $qty * $discount;
echo($total); }
|
在中大型程序中,会有几个问题 :
- 将 PHP 写的商业逻辑直接写在 controller,造成 controller 的肥大难以维护。
- 违反 SOLID的 单一职责原则 : 商业逻辑不应该写在 controller。
- 违反 SOLID的 单一职责原则 : 若未来想要改变折扣与加总的算法,都需要改到此 method,也就是说,此 method 同时包含了计算折扣与计算加总的职责,因此违反 SOLID 的单一职责原则。
- 直接写在 controller 的逻辑无法被其他 controller 使用。
比较好的方式是使用 service:
- 将相依物件注入到 service。
- 在 service 写 PHP逻辑使用相依物件。
- 将 service 注入到 controller。
OrderService.php
app/Services/OrderService.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| namespace App\Services;
class OrderService {
public function getDiscount($qty) { if ($qty == 1) { return 1.0; } elseif ($qty == 2) { return 0.9; } elseif ($qty == 3) { return 0.8; } else { return 0.7; } }
public function getTotal($qty, $discount) { return 500 * $qty * $discount; } }
|
OrderController.php
app/Http/Controllers/OrderController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| namespace App\Http\Controllers;
use App\Http\Requests; use App\MyBlog\Services\OrderService; use Illuminate\Http\Request;
class OrderController extends Controller { protected $orderService;
public function __construct(OrderService $orderService) { $this->orderService = $orderService; }
public function store(Request $request) { $qty = $request->input('qty');
$discount = $this->orderService->getDiscount($qty); $total = $this->orderService->getTotal($qty, $discount);
echo($total); } }
|
详细链接
- 如何使用Repository模式
- 如何使用Presenter模式
- 如何使用Service模式
- laravel的中大型架构
http://oomusou.io/laravel/laravel-service/
http://blog.iwanli.me/article/B8ApLAab.html