如何在 Lumen 中使用 DRY 和服务层?
How to using DRY and service layer in Lumen?
我正在 Lumen 框架中创建 api,最近我阅读了有关 DRY 和服务层的内容。直到今天我都没有在我的代码中使用这些,所有的逻辑都在控制器中。所以我想开始使用它,但我遇到了一些问题。
这是我的控制器的一部分(UsersController.php),因为整个代码太长了。
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
class UsersController extends Controller
{
private $request;
public function __construct(Request $request) {
$this->request = $request;
}
public function destroy($id) {
$user = User::find($id);
if (!$user) {
return response()->json([
'error' => 'User not found'
], 404);
}
if ($user->role === 'admin') {
return response()->json([
'error' => 'You cant edit admin'
], 403);
}
$user->delete();
return response()->json([], 204);
}
}
看完这段代码后,我试图改变两件事。
- 获取用户和 return 错误可以在 UserService.php 中完成(我在其他方法中也有此代码,所以这就是为什么我认为这很好这种方法在服务中的想法)。但是正如你所看到的,当出现错误时我想 return 响应,当我这样做时,我的代码试图在 json 响应上使用 delete 方法,而不是在用户模型上。抛出异常在我看来是不好的,因为不符合DRY原则。知道如何解决吗?
UserService.php
<?php
namespace App\Services;
use App\User;
class UserService
{
public function getUserById($id)
{
$user = User::find($id);
if (!$user) {
return response()->json([
'error' => 'User not found'
], 404);
}
if ($user->role === 'admin') {
return response()->json([
'error' => 'You cant edit admin'
], 403);
}
return $user;
}
}
已修改 UsersController。php/destroy
public function destroy($id) {
$user = $this->userService->getUserById($id);
$user->delete(); // not working because sometimes it can return json response
return response()->json([], 204);
}
- 我在控制器、中间件等中使用了太多 json 响应,我想通过创建新的 class 来统一它,但我不知道如何正确使用它。我的意思是 returning json 在 ResponderService.php 中的响应可能不会在控制器等其他地方停止执行。或者也许我应该将其创建为助手?
ResponderService.php
<?php
namespace App\Services;
class ResponderService
{
private function base($data, $status_code)
{
$data['status_code'] = $status_code;
return response()->json($data, $status_code);
}
public function error($message, $status_code)
{
$data['error'] = $message;
$data['status'] = 'error';
$this->base($data, $status_code);
}
}
我也阅读了有关存储库的内容,但我认为这种模式在我的项目中并不适用。如果您有其他可以改进控制器代码的建议,我愿意接受。
我没有发现在您的场景中使用异常有任何问题。
<?php
namespace App\Services;
use App\User;
class UserService
{
public function getUserById($id)
{
$user = User::find($id);
if (!$user) {
throw UserNotFoundException('User not found');
}
if ($user->role === 'admin') {
throw EditAdminException("You can't edit admin.");
}
return $user;
}
}
如果需要,这些异常是您在 app\Exception
中定义的自定义异常。然后 getUserById()
方法只能 return 一个 User
否则一个异常冒泡并且 return 一个 JSON 对客户端的响应。
Laravel也已经有了一个简单的方法来处理第一个异常。你可以这样做:
<?php
namespace App\Services;
use App\User;
class UserService
{
public function getUserById($id)
{
$user = User::findOrFail($id);
if ($user->role === 'admin') {
throw EditAdminException("You can't edit admin.");
}
return $user;
}
}
如果找不到 User
,Laravel 将处理抛出 Illuminate\Database\Eloquent\ModelNotFoundException
。
这样您就不必担心为 Exceptions 已经可以为您做的事情创建 ResponderService
。
如果您想对资源的响应进行标准化,您可以利用 Eloquent 资源作为您的 API 的转换层:https://laravel.com/docs/5.7/eloquent-resources
最后,如果您发现要从多个位置删除资源并且不想重复响应,则可以将响应放在事件中:https://laravel.com/docs/5.7/eloquent#events
文档显示了处理事件的复杂方式,但我个人只会在您的模型开始感到臃肿时才这样做。
您可以将此作为创建事件和观察者的更简单替代方法 class:
public static function boot()
{
parent::boot();
static::deleted(function ($model) {
return response()->json([], 204);
});
}
该方法适用于您的用户模型。顺便说一句,我发现的方法是查看 Eloquent\Model
.
上的 HasEvents
特征
现在,综上所述,我实际上会将所有删除逻辑放入您的 UserService
并将方法从 getUserById
重命名为 deleteById
。替代方案有点奇怪,因为您是说如果用户是管理员,您不希望能够通过 ID 获取用户。
实际上,您要做的是封装删除用户的逻辑,因此只需将其全部移动到服务的方法中,或者更好的是,只需在模型上使用 delete
事件,然后把所有的逻辑放在那里。这样您甚至不必引入服务。
编辑
根据您在下面的评论,我认为您可能误解了如何在 Laravel 中使用异常。
在一个新的 Laravel 项目中,app\Exeptions\Handler
中有一个 class 可以捕获您应用中所有未处理的异常。 class 首先检查异常是否是 ModelNotFoundException
,然后 return 是 json 响应。
否则,它将捕获的异常传递给其父级的 render
方法。
所以基本上当你想创建一个自定义异常时,你只需创建一个 class 来扩展 Exception
并实现一个 handle
方法。
这是一个示例异常 class:
<?php
namespace App\Exceptions;
use Exception;
class TicketNotPayableException extends Exception
{
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render()
{
return response()->json([
'errors' => [
[
'title' => 'Ticket Not Payable Exception',
'description' =>
'This ticket has already been paid.'
],
],
'status' => '409'
], 409);
}
}
现在响应完全可以重用,我的代码中不需要一堆 try-catch 块。 Laravel 的异常处理程序将捕获它,并调用 render
方法。
所以,如果我想在服务中封装支付机票的逻辑,我只需要 throw App\Exceptions\TicketNotPayableException;
然后我的控制器只需要做类似的事情: $ticketPaymentService->pay($ticket);
并且有无需试探。如果异常被抛出,它将冒泡,被处理程序捕获,并且将调用 render
方法,这将 return 适当的 JSON 响应 - 不需要Responder
.
我正在 Lumen 框架中创建 api,最近我阅读了有关 DRY 和服务层的内容。直到今天我都没有在我的代码中使用这些,所有的逻辑都在控制器中。所以我想开始使用它,但我遇到了一些问题。
这是我的控制器的一部分(UsersController.php),因为整个代码太长了。
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
class UsersController extends Controller
{
private $request;
public function __construct(Request $request) {
$this->request = $request;
}
public function destroy($id) {
$user = User::find($id);
if (!$user) {
return response()->json([
'error' => 'User not found'
], 404);
}
if ($user->role === 'admin') {
return response()->json([
'error' => 'You cant edit admin'
], 403);
}
$user->delete();
return response()->json([], 204);
}
}
看完这段代码后,我试图改变两件事。
- 获取用户和 return 错误可以在 UserService.php 中完成(我在其他方法中也有此代码,所以这就是为什么我认为这很好这种方法在服务中的想法)。但是正如你所看到的,当出现错误时我想 return 响应,当我这样做时,我的代码试图在 json 响应上使用 delete 方法,而不是在用户模型上。抛出异常在我看来是不好的,因为不符合DRY原则。知道如何解决吗?
UserService.php
<?php
namespace App\Services;
use App\User;
class UserService
{
public function getUserById($id)
{
$user = User::find($id);
if (!$user) {
return response()->json([
'error' => 'User not found'
], 404);
}
if ($user->role === 'admin') {
return response()->json([
'error' => 'You cant edit admin'
], 403);
}
return $user;
}
}
已修改 UsersController。php/destroy
public function destroy($id) {
$user = $this->userService->getUserById($id);
$user->delete(); // not working because sometimes it can return json response
return response()->json([], 204);
}
- 我在控制器、中间件等中使用了太多 json 响应,我想通过创建新的 class 来统一它,但我不知道如何正确使用它。我的意思是 returning json 在 ResponderService.php 中的响应可能不会在控制器等其他地方停止执行。或者也许我应该将其创建为助手?
ResponderService.php
<?php
namespace App\Services;
class ResponderService
{
private function base($data, $status_code)
{
$data['status_code'] = $status_code;
return response()->json($data, $status_code);
}
public function error($message, $status_code)
{
$data['error'] = $message;
$data['status'] = 'error';
$this->base($data, $status_code);
}
}
我也阅读了有关存储库的内容,但我认为这种模式在我的项目中并不适用。如果您有其他可以改进控制器代码的建议,我愿意接受。
我没有发现在您的场景中使用异常有任何问题。
<?php
namespace App\Services;
use App\User;
class UserService
{
public function getUserById($id)
{
$user = User::find($id);
if (!$user) {
throw UserNotFoundException('User not found');
}
if ($user->role === 'admin') {
throw EditAdminException("You can't edit admin.");
}
return $user;
}
}
如果需要,这些异常是您在 app\Exception
中定义的自定义异常。然后 getUserById()
方法只能 return 一个 User
否则一个异常冒泡并且 return 一个 JSON 对客户端的响应。
Laravel也已经有了一个简单的方法来处理第一个异常。你可以这样做:
<?php
namespace App\Services;
use App\User;
class UserService
{
public function getUserById($id)
{
$user = User::findOrFail($id);
if ($user->role === 'admin') {
throw EditAdminException("You can't edit admin.");
}
return $user;
}
}
如果找不到 User
,Laravel 将处理抛出 Illuminate\Database\Eloquent\ModelNotFoundException
。
这样您就不必担心为 Exceptions 已经可以为您做的事情创建 ResponderService
。
如果您想对资源的响应进行标准化,您可以利用 Eloquent 资源作为您的 API 的转换层:https://laravel.com/docs/5.7/eloquent-resources
最后,如果您发现要从多个位置删除资源并且不想重复响应,则可以将响应放在事件中:https://laravel.com/docs/5.7/eloquent#events
文档显示了处理事件的复杂方式,但我个人只会在您的模型开始感到臃肿时才这样做。
您可以将此作为创建事件和观察者的更简单替代方法 class:
public static function boot()
{
parent::boot();
static::deleted(function ($model) {
return response()->json([], 204);
});
}
该方法适用于您的用户模型。顺便说一句,我发现的方法是查看 Eloquent\Model
.
HasEvents
特征
现在,综上所述,我实际上会将所有删除逻辑放入您的 UserService
并将方法从 getUserById
重命名为 deleteById
。替代方案有点奇怪,因为您是说如果用户是管理员,您不希望能够通过 ID 获取用户。
实际上,您要做的是封装删除用户的逻辑,因此只需将其全部移动到服务的方法中,或者更好的是,只需在模型上使用 delete
事件,然后把所有的逻辑放在那里。这样您甚至不必引入服务。
编辑
根据您在下面的评论,我认为您可能误解了如何在 Laravel 中使用异常。
在一个新的 Laravel 项目中,app\Exeptions\Handler
中有一个 class 可以捕获您应用中所有未处理的异常。 class 首先检查异常是否是 ModelNotFoundException
,然后 return 是 json 响应。
否则,它将捕获的异常传递给其父级的 render
方法。
所以基本上当你想创建一个自定义异常时,你只需创建一个 class 来扩展 Exception
并实现一个 handle
方法。
这是一个示例异常 class:
<?php
namespace App\Exceptions;
use Exception;
class TicketNotPayableException extends Exception
{
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render()
{
return response()->json([
'errors' => [
[
'title' => 'Ticket Not Payable Exception',
'description' =>
'This ticket has already been paid.'
],
],
'status' => '409'
], 409);
}
}
现在响应完全可以重用,我的代码中不需要一堆 try-catch 块。 Laravel 的异常处理程序将捕获它,并调用 render
方法。
所以,如果我想在服务中封装支付机票的逻辑,我只需要 throw App\Exceptions\TicketNotPayableException;
然后我的控制器只需要做类似的事情: $ticketPaymentService->pay($ticket);
并且有无需试探。如果异常被抛出,它将冒泡,被处理程序捕获,并且将调用 render
方法,这将 return 适当的 JSON 响应 - 不需要Responder
.