Laravel: переносим Контроллер в Сервисный Класс с внедрением
Во-первых, ситуация до
— есть Контроллер с двумя методами: store()
и update()
:
class UserController extends Controller
{
public function store(StoreUserRequest $request)
{
$user = User::create($request->validated());
$user->roles()->sync($request->input('roles', []));
// Больше действий с этим пользователем: скажем, ещё 5+ строк кода
// - Загрузка аватара
// - Отправка e-mail пользователю
// - Уведомление админа о новом пользователе
// - Создание неких данных для пользователя
// - и ещё ...
return redirect()->route('users.index');
}
public function update(UpdateUserRequest $request, User $user)
{
$user->update($request->validated());
$user->roles()->sync($request->input('roles', []));
// Кроме того, дополнительные действия с этим пользователем
return redirect()->route('users.index');
}
}
Этот Контроллер слишком длинный — логика должна быть в другом месте.
Рефакторинг — Шаг 1: Сервисный Класс
Один из способов рефакторинга — создать специальный Сервисный класс для всего, что связано с пользователем User
, с такими методами, как store()
и update()
.
Обратите внимание, что в Laravel нет команды php artisan make:service
, вам нужно создать этот класс вручную, как обычный PHP класс.
А затем мы перемещаем этот код из контроллера в Сервис.
app/Services/UserService.php
:
namespace App\Services;
class UserService {
public function store(array $userData): User
{
$user = User::create($userData);
$user->roles()->sync($userData['roles']);
// Больше действий с этим пользователем: скажем, ещё 5+ строк кода
// - Загрузка аватара
// - Отправка e-mail пользователю
// - Уведомление админа о новом пользователе
// - Создание неких данных для пользователя
// - и ещё ...
return $user;
}
public function update(array $userData, User $user): User
{
$user->update($userData);
$user->roles()->sync($userData['roles']);
// Кроме того, дополнительные действия с этим пользователем
}
}
Теперь наш Контроллер намного короче — мы просто вызываем методы Сервисного класса.
Есть несколько способов сделать это. Самый простой — создавать экземпляр Сервисного класса всякий раз, когда нам это нужно, например:
use App\Services\UserService;
class UserController extends Controller
{
public function store(StoreUserRequest $request)
{
(new UserService())->store($request->validated());
return redirect()->route('users.index');
}
public function update(UpdateUserRequest $request, User $user)
{
(new UserService())->update($request->validated(), $user);
return redirect()->route('users.index');
}
}
Рефакторинг — Шаг 2: Внедрение Сервисного класса
Вместо того чтобы делать new UserService()
каждый раз, мы можем просто вставить его как зависимость в методы, где он нам нужен.
Laravel автоматически инициализирует его, если мы предоставим подсказку типа внутри методов Контроллера:
use App\Services\UserService;
class UserController extends Controller
{
public function store(StoreUserRequest $request, UserService $userService)
{
$userService->store($request->validated());
return redirect()->route('users.index');
}
public function update(UpdateUserRequest $request, User $user, UserService $userService)
{
$userService->update($request->validated(), $user);
return redirect()->route('users.index');
}
}
Мы можем пойти ещё дальше и внедрить Сервисный класс в конструктор Контроллера. После этого у нас будет доступ к сервису в любых необходимых нам методах Контроллера.
use App\Services\UserService;
class UserController extends Controller
{
private UserService $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function store(StoreUserRequest $request)
{
$this->userService->store($request->validated());
return redirect()->route('users.index');
}
public function update(UpdateUserRequest $request, User $user)
{
$this->userService->update($request->validated(), $user);
return redirect()->route('users.index');
}
}
Наконец, мы можем использовать относительно новый синтаксис PHP 8, называемый объявление свойств в конструкторе, поэтому нам даже не нужно объявлять приватную переменную или присваивать что-то в конструкторе:
use App\Services\UserService;
class UserController extends Controller
{
public function __construct(private UserService $userService)
{
}
// Мы по-прежнему можем использовать $this->userService в любом месте Контроллера
}
У меня есть отдельное видео, специально посвящённое этой функции PHP 8:
Вот и всё, что я хотел сказать в этой статье. Конечно, есть и другие способы отделить код от Контроллера — Action классы, Репозитории и другие шаблоны — так что выбирайте, что вам нравится, логика внедрения или инициализации этого класса будет такой же.