@lexstile

Как правильно выстраивать архитектуру метода контроллера на laravel?

У есть метод на удаление проекта:
public function destroy($id)
    {
        $code = 400;
        $message = Response::getErrorMessage('Не удалось удалить проект');
        
        if (!ProjectPolicy::requestDelete($id)) {
            $code = 403;
            $message = Response::getErrorMessage('Ошибка доступа');
        }

        $project = Project::find($id) ?: [];

        if ($project) {
            $project->delete();
            $this->deleteImageProject($project->logo);
            $code = 200;
            $message = Response::getSuccessMessage('Вы успешно удалили проект');
        }
        
        return response()->json($message)->setStatusCode($code);
    }

Как грамотно и правильно выстраивать архитектуру метода? (проверки, коды ответа и т. п.)
Как ни крутил - получается уныло совсем.
  • Вопрос задан
  • 260 просмотров
Решения вопроса 4
gzhegow
@gzhegow
aka "ОбнимиБизнесмена"
Знакомая боль давняя.
Принцип следующий - 1) исходные данные 2) превращение в обьект, 3) проверка (валидация) обьекта, 4) передача обьекта в какое-то действие, 5) проверка результата (если нужна) и 6) генерация респонса.

Исходные данные. Смотрим что за фрейм. Лара. Умеет в инжекцию в метод контроллера и биндинг обьектов, если написать правильно. Можно сразу на вход писать MyActionRequest (сделать как в документации), в итоге первой строкой будет $myActionRequest->validate(), которая еще и выкинет 422 если что-то не так.

В чем минус ларавельного реквеста? Он представляет собой как бы ВЕСЬ реквест. Это тоже самое что передавать $_GET по программе, что немного странно.

Рекомендуется сделать свой обьект ДТО. Это по сути твой же реквест, только в нем поля все заранее описаны, голый класс без методов - одни свойства. После валидации ты свои данные из GET/POST/LaravelRequest ручками втыкаешь в поля ДТО и этот заполненный ДТО можешь дальше кидать как "точно проверенная пачка данных" и не боятся каждый раз "а что если что-то не так" - оно точно так, потому что ты уже проверял.

Попадать в какой-то другой класс ДТО может целиком если это кусок бизнес-части программы или по одному полю / массиву полей если это какой-то кусок ядра или глобалки. Пример ядро - класс Money, который переводит там валюты знач и позволяет их переключать. Туда ваш MyActionDTO будет странно кидать, а вот string $money или PHPMoney/Money- пжалста. А вот в обьект например MyCatalog кинуть MyActionDto уже не так уже и плохо.

Вот это ваше действие может быть просто SQL запросом. Category::query()->find() это тоже действие, а почему нет. Рекомендуют конечно запросы выбрасывать в "тип классов" (папка, такая же как "модели") репозиторий чтоб потом когда поломается чего не везде править а в одном месте, но тут тоже, пока помните, что где можно не париться, и потом - увлечетесь обобщением поймаете прикол когда не понятно какую половину запроса в контроллер, а какую - в репозиторий. Стоит помнить что мы пишем задачу под задание, задание представляет собой контроллер, а остальной код ему помогает. Поэтому частности в экшон, похожести - в отдельный класс.

Ну так вот, это "действие" что-то вам вернет. Может это готовый к выводу контент, а может еще какие-то данные которые для вашего экшена может кривые. Вот проверяете "оно? или нет". Частный случай - ничего в базе не нашлось, это критично, и 404 или просто выплюнуть "нет записей" и 200? Проверили ифами, сделали респонсы - где 404ый, где двухсотый, где джсоновский, где иксмэльный, файл-довнлоадный или стримовый.

Потом пошли в Handler.php написали отлов исключений в целом по программе и их запись куда-то, в базу, в кеш или на удаленный логгер-анализатор, чтобы везде не писать try catch а просто писать throw и знать что оно "хоть как-то но отрисуется" и куда надо запишется.

public function destroy(Request $request, $id) // или MyDestroyRequest если юзер что-то еще передает
    {
        $request->validate(); // оно под капотом ещё и авторизацию проверит и 422 само выбросит

        // ... верно тут можно проверять политику, но в принципе если реквест наследовать то в ларе помоему их можно и в реквест код перенести и первый метод еще и это сделает
      
        $dto = new MyDestroyDto($request->valid()); // там внутри пишем что куда, хотя можно и здесь

        $myDomainService = new MyDomainService(); // или new MyCoreService();
        $status = $myDomainService->doSomething($dto); // или $result = $myCoreService->convert($dto->money);

        if ($status === 'ok') return $this->jsonDone('@api.msg.ok_message', 200); // вот метод jsonDone в базовом (абстрактном*) контроллере или, например, в трейте, напиши
        return $this->jsonFail('@api.err.error_message', 500); // собаки в тексте для наглядности, трехуровневый ключ перевода из файла lang, по собаке просто можно определить текст уже проходил через переводчик или нет. три уровня нужны, чтобы когда много переводов скапливается их можно было подключать частями. Первый уровень - где подключать (api, catalog, social), второй - что (msg, err, label, placeholder, title, h1)
    }
Ответ написан
@jazzus
Не нужно ничего крутить. См Policy, Model Binding и Localization в документации Ларавел.
Ответ написан
Комментировать
iMedved2009
@iMedved2009
Не люблю людей
1.public function destroy($id)
Используйте model binding - удобно же.

2. ProjectPolicy::requestDelete($id)
есть прекрасный вариант расставлять policy на уровне роутера - can:
Route::delete('/project/{project}', [Controller::class, 'method'])->can('delete', 'project');


3. $project = Project::find($id);
FindOrFail - в случае отсутствия модели выбросит ровно 404;

4. $this->deleteImageProject($project->logo);
Если вы всегда удаляете лого после удаления модели - то повесьте событие delete и всегда удаляйте лого как только где то и в каком то месте пытаются удалить модель Project. Для случаев mass delete - гляньте в трейт softDeletes, по аналогии можно навешать обработчик.
Лень - через события? Ну переопределите у модели метод delete.
public function delete()
{
    $result = parent::delete();
    if($result){
        $this->deleteImageProject($this->logo);
    }
    return $result;
}

У вас логика удаления модели (если нам надо удалить модель - надо удалить лого) - не должна валяться по всему проекту - она должна лежать в одном месте. А судя повсему вы ее вообще в контроллер запихали - ни в какие ворота

При использовании всего этого у вас метод контроллера приобретет простой и лаконичный вид.

public function destroy(Project $project)
{
    $project->delete();
    return response('', 204);
}


204 - ибо можем

If a DELETE method is successfully applied, the origin server SHOULD
send a 202 (Accepted) status code if the action will likely succeed
but has not yet been enacted, a 204 (No Content) status code if the
action has been enacted and no further information is to be supplied,
or a 200 (OK) status code if the action has been enacted and the
response message includes a representation describing the status.

RFC
Ответ написан
@vism
Да всё верно описал Григорий Васильков , согласен.

lexstile И у вас впринципе то тоже верно

Насчёт ДТО - согласен, но слишком муторно выходит везде ДТО пихать :(
Для ДТО - Spatie DataTransferObject

Я думаю в ПХП 8 простые данные позволительно именованными параметрами передавать
А если данные требуют доп. преобразования, то ДТО

ответ обратно либо стандартным
response()->json()
либо свой обработчик, как Григорий Васильков предложил

вот вариант близкий к вашему написанию. В принципе я так и делаю
public function destroy($id)
    {        
        if (!ProjectPolicy::requestDelete($id)) {
            return response()->json(Response::getErrorMessage('Ошибка доступа'), 403);
        }

        $project = Project::find($id);
        if (!$project) {        
            return response()->json(Response::getErrorMessage('Проект не найден'), 404);
        }
// это вот в сервис класс.
        $project->delete();
        $this->deleteImageProject($project->logo);

        return response()->json(Response::getSuccessMessage('Вы успешно удалили проект'), 200);
    }
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы