Я создаю API в Lumen Framework и недавно я прочитал о 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);
}
}
Посмотрев на этот код, я попытался изменить 2 вещи.
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);
}
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()
метод может только вернуть User
в противном случае возникает исключительная ситуация и возвращает клиенту ответ 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;
}
}
И Ларавел будет разбрасывать Illuminate\Database\Eloquent\ModelNotFoundException
если User
не может быть найден
Таким образом, вам не нужно беспокоиться о создании ResponderService
за то, что исключения могут уже сделать для вас.
Если вы хотите стандартизировать ответы ваших ресурсов, вы можете использовать Eloquent Resources, который работает как слой преобразования для вашего API: https://laravel.com/docs/5.7/eloquent-resources
Наконец, если вы обнаружите, что вы удаляете ресурс из более чем одного места и не хотите дублировать ответ, вы можете поместить ответ внутри события: https://laravel.com/docs/5.7/eloquent#events
Документация показывает запутанный способ обработки событий, но я лично сделаю это только тогда, когда ваша модель начнет чувствовать себя раздутой.
Вы можете сделать это как более простую альтернативу созданию класса Event и Observer:
public static function boot()
{
parent::boot();
static::deleted(function ($model) {
return response()->json([], 204);
});
}
Этот метод подходит только для вашей модели User. И, как я обнаружил, кстати, заглянув внутрь HasEvents
черта на Eloquent\Model
,
Теперь, все это говорит, я бы на самом деле поместил всю логику удаления в вашем UserService
и переименуйте метод из getUserById
в deleteById
, Альтернатива немного странная, потому что вы говорите, что не хотите получать пользователя по идентификатору, если это администратор.
На самом деле вы пытаетесь инкапсулировать логику удаления пользователя, поэтому просто перенесите все это в метод службы или, еще лучше, просто используйте delete
событие на модели и положить всю логику там. Таким образом, вам даже не нужно вводить услугу.
редактировать
Исходя из вашего комментария ниже, я думаю, что вы можете неправильно понимать, как использовать исключения в Laravel.
В новом проекте Laravel есть класс app\Exeptions\Handler
который ловит все необработанные исключения в вашем приложении. Этот класс сначала проверяет, является ли исключение ModelNotFoundException
и затем возвращает ответ JSON.
Иначе, оно передает пойманное исключение render
метод его родителя.
Таким образом, в основном, когда вы хотите создать собственное исключение, вы просто создаете класс, который расширяет Exception
и реализует handle
метод.
Вот пример класса исключения:
<?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
будет вызван метод, который вернет соответствующий ответ JSON — нет необходимости Responder
,
Других решений пока нет …