如果将商业逻辑都写在 Controller 中,随着业务复杂度的提高,会造成 Controller 臃肿而难以维护的困境。基于 SOLID 原则,我们应该使用 Service 模式,将相关的商业逻辑封装在不同的 Service 中,以方便中大型项目的维护。
商业逻辑中,常见的有:
涉及外部行为。如:发送邮件,调用第三方 API 等。
使用 PHP 写的逻辑。如:根据购买的件数,获得不同的折扣。
如发送邮件,初学者常会在 Controller 中直接调用 Mail::queue()
:
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 臃肿而难以维护。
违反 SOLID 单一职责原则:外部行为不应该写在 Controller。
Controller 直接依赖于外部行为,无法对其做单元测试。
比较好的方式是使用 Service:
将外部行为注入到 Service。
在 Service 中使用外部行为。
将 Service 注入到 Controller。
代码修改后如下:
namespace App\Services;
use Illuminate\Mail\Mailer;
use Illuminate\Mail\Message;
class EmailService
{
/** @var Mailer */
private $mail;
/**
* EmailService constructor.
* @param Mailer $mail
*/
public function __construct(Mailer $mail)
{
$this->mail = $mail;
}
/**
* 发送邮件
* @param array $request
*/
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。将发送邮件的商业逻辑写在 send()
中。没有使用 Mail 的 Facade,而是使用注入的 $this->mail
。
然后进行调用:
namespace App\Http\Controllers;
use App\Http\Requests;
use Illuminate\Http\Request;
use MyBlog\Services\EmailService;
class UserController extends Controller
{
/** @var EmailService */
protected $emailService;
/**
* UserController constructor.
* @param EmailService $emailService
*/
public function __construct(EmailService $emailService)
{
$this->emailService = $emailService;
}
/**
* Store a newly created resource in storage.
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$this->emailService->send($request->all());
}
}
将依赖的 EmailService 注入到 UserController。原本依赖 Mail 的 Facade,现在改为注入 EmailService。
改用这种写法,有如下优点:
将外部行为写在 Service,解决了 Controller 臃肿的问题。
符合 SOLID 的单一职责原则。
符合 SOLID 的依赖反转原则。Controller 并非直接依赖于 Service,而是将 Service 注入到 Controller。
如根据购买的件数,获得不同的折扣,初学者常常会在 Controller 中直接写条件判断逻辑。
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);
}
同样,比较好的方式是使用 Service 模式将商业逻辑抽取出来:
namespace App\Services;
class OrderService
{
/**
* 计算折扣
* @param int $qty
* @return float
*/
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;
}
}
/**
* 计算最终价格
* @param integer $qty
* @param float $discount
* @return float
*/
public function getTotal($qty, $discount)
{
return 500 * $qty * $discount;
}
}
为了符合 SOLID 的单一职责原则,我们分别使用 getDiscount()
来获取折扣,使用 getTotal()
来获取总价格。
最后在 Controller 中调用:
namespace App\Http\Controllers;
use App\Http\Requests;
use App\MyBlog\Services\OrderService;
use Illuminate\Http\Request;
class OrderController extends Controller
{
/** @var OrderService */
protected $orderService;
/**
* OrderController constructor.
* @param OrderService $orderService
*/
public function __construct(OrderService $orderService)
{
$this->orderService = $orderService;
}
/**
* Store a newly created resource in storage.
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$qty = $request->input('qty');
$discount = $this->orderService->getDiscount($qty);
$total = $this->orderService->getTotal($qty, $discount);
echo($total);
}
}
将 OrderService 注入到 UserController。
业务会包含很多 Service,需要自己根据 SOLID 原则去判断是否应该封装为单独的 Service。
Service 使得商业逻辑从 Controller 中分离出来,不仅更易维护、更易扩展、更易重复使用,而且也方便测试。