Laravel: Ваши контроллеры должны выглядеть так
Сценарий
Не так редко, когда начинаешь работать над проектом приходится иметь дело с божественными классами контроллеров. Знаете, что я имею в виду? Кто никогда не сталкивался с контроллером хранящим метод который проверяет, сохраняет, создаёт отношения, отправляет электронные письма, … и возвращает ответ?
Я вижу, что это очень распространённый подход в Laravel-сообществе, в то время как Rails-разработчики склонны делать почти то же самое в классах моделей. Не думаю, что это так уж плохо иметь что-то ещё (помимо обработки запросов и ответов) внутри контроллеров в некоторых конкретных ситуациях, но в целом я бы предпочёл использовать первый принцип SOLID: Принцип единой ответственности (SRP) сделать немного больше сейчас, чтобы упростить задачу в будущем.
Преимущества переноса бизнес-логики из контроллера в новые классы бесчисленны, но напомню три из них:
- Вы можете повторно использовать свой код
- Ваш код должен легко пониматься и поддерживаться
- Вы будете ответственны за часть счастья следующего разработчика
Давайте посмотрим как применить эту концепцию при рефакторинге контроллера
Для простоты давайте рассмотрим, в котором UserController
просто проверяет данные, создаёт нового пользователя, его роли и перенаправляет с сообщением о состоянии:
public function store(Request $request)
{
# Проверяем данные
$request->validate([
'name' => 'required',
'email' => 'required|unique:users',
'phone_number' => 'required',
]);
# Создаём пользователя
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'phone_number' => $request->phone_number,
]);
# Создаём роли пользователя
if(!empty($request->roles)) {
$user->roles()->sync($request->roles);
}
# Перенаправляем с сообщением о состоянии
return redirect()
->route('user.index')
->with('message','Пользователь успешно создан.');
}
Выглядит нормально, правда? Но мы можем сделать его лучше. Давайте переместим проверку в Form Request
, выполнив эту команду:
php artisan make:request StoreUserRequest
Она создаст новый файл в каталоге App\Http\Requests
. Я просто разрешаю использование и переношу правила валидации из контроллера в метод rules
следующим образом:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'email' => 'required|unique:users',
'phone_number' => 'required',
];
}
}
Как использовать этот класс:
Всё, что вам нужно сделать — указать запрос в методе вашего контроллера. Входящий запрос формы проверяется до вызова метода контроллера, значит вам не нужно загромождать контроллер какой-либо логикой проверки.
use App\Http\Requests\StoreUserRequest;
public function store(StoreUserRequest $request)
{
# Создаём пользователя
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'phone_number' => $request->phone_number,
]);
# Создаём роли пользователя
if(!empty($request->roles)) {
$user->roles()->sync($request->roles);
}
# Перенаправляем с сообщением о состоянии
return redirect()
->route('user.index')
->with('message','Пользователь успешно создан.');
}
Немного лучше. Теперь мы создадим новый сервисный класс. Он будет отвечать за создание пользователей.
Поскольку у нас для этого нет специальных команд, нужно будет создать его вручную. Внутри каталога app
создадим каталог Service
с новы классом: app/Services/UserService.php
и перенесём логику из контроллера в этот класс:
<?php
namespace App\Services;
use App\Models\User;
use Illuminate\Http\Request;
class UserService
{
public function createUser(Request $request): User
{
# Создаём пользователя
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'phone_number' => $request->phone_number,
]);
# Создаём роли пользователя
if(!empty($request->roles)) {
$user->roles()->sync($request->roles);
}
return $user;
}
}
Импортируем в контроллер зависимость, которую только что создали:
use App\Services\UserService;
public function store(StoreUserRequest $request, UserService $userService)
{
$userService->createUser($request);
# Перенаправляем с сообщением о состоянии
return redirect()
->route('user.index')
->with('message','Пользователь успешно создан.');
}
Вы заметили разницу?
Да, теперь у нас больше классов и файлов, но с другой стороны, у каждого из них есть свои обязанности, их можно повторно использовать в других частях приложения и индивидуально тестировать.
Не говоря уж о контроллере, который теперь отвечает только за получение запроса, вызов нужного сервиса и возврат ответа.
Красивее, не так ли?
Должен ли я использовать сервисные классы?
Это зависит только от вас, решить и оценить, что будет работать для вашего проекта. Я лично предпочитаю их, но я уже работал над проектами, где был совершенно другой подход.
Это не общее правило, но при создании нового приложения вы должны позаботиться о том, чтобы каждый класс был как можно более чистым, включая ваши контроллеры.