Как использовать СУХОЙ и сервисный слой в Lumen? - PullRequest
0 голосов
/ 27 октября 2018

Я создаю 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 вещи.

  1. Получить пользователя и вернуть ошибки можно в UserService.php (у меня этот код есть и в других методах, поэтому я считаю, что это хорошая идея для этого метода в сервисе). Но, как вы видите, я хочу вернуть ответ, когда есть ошибки, и когда я это делаю, мой код, как и предполагалось, пытался использовать метод удаления в ответе json, а не в пользовательской модели. Бросать исключения на мой взгляд нехорошо, потому что не совместим с принципом СУХОЙ. Есть идеи как это исправить?

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);
}
  1. Я использую так много ответов json в контроллерах, промежуточном программном обеспечении и т. Д., И я хочу объединить это, создав новый класс, но я не знаю, как правильно его использовать. Я имею в виду возвращение ответа 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);
    }
}

Я тоже читал о репозиториях, но я не думаю, что этот шаблон будет хорошим в моем проекте. Если у вас есть другие предложения, которые можно улучшить в коде контроллера, я открыт для них.

1 Ответ

0 голосов
/ 27 октября 2018

Я не вижу проблем с использованием исключений для вашего сценария.

<?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;
    }
}

И Laravel будет обрабатывать бросок 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.

Теперь, после всего сказанного, я бы фактически поместил всю логику удаления в вашу * 1034.* и переименуйте метод с getUserById на deleteById.Альтернатива немного странная, потому что вы говорите, что не хотите получать пользователя по идентификатору, если это администратор.

На самом деле вы пытаетесь инкапсулировать логику удаленияпользователь, поэтому просто перенесите все это в метод службы, или, что еще лучше, просто используйте событие delete в модели и поместите всю логику туда.Таким образом, вам даже не нужно вводить услугу.

Редактировать

Исходя из вашего комментария ниже, я думаю, что вы, возможно, неправильно понимаете, как использовать исключения в Laravel..

В новом проекте Laravel в app\Exeptions\Handler есть класс, который перехватывает все необработанные исключения в вашем приложении.Этот класс сначала проверяет, является ли Exception значение 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.

нет необходимости.
...