Laravel: переносим Контроллер в Сервисный Класс с внедрением

Источник: «Laravel Controller into Service Class with Injection»
В этой статье я покажу, как сократить Контроллеры с помощью Сервисных классов, а также различные способы инициализации и внедрения этого Сервиса.

Во-первых, ситуация до — есть Контроллер с двумя методами: 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 классы, Репозитории и другие шаблоны — так что выбирайте, что вам нравится, логика внедрения или инициализации этого класса будет такой же.

Дополнительные материалы

Предыдущая Статья

Laravel: Сервис Контейнер — что нужно знать новичкам

Следующая Статья

PHP 8.3: Добавлена функция json_validate