In this article, I will show you how to shorten Controllers by using Service classes, and different ways to initialize or inject that Service.
First, the "before" situation - you have a Controller with two methods: store()
and update()
:
1class UserController extends Controller 2{ 3 public function store(StoreUserRequest $request) 4 { 5 $user = User::create($request->validated()); 6 7 $user->roles()->sync($request->input('roles', [])); 8 9 // More actions with that user: let's say, 5+ more lines of code10 // - Upload avatar11 // - Email to the user12 // - Notify admins about new user13 // - Create some data for that user14 // - and more...15 16 return redirect()->route('users.index');17 }18 19 public function update(UpdateUserRequest $request, User $user)20 {21 $user->update($request->validated());22 $user->roles()->sync($request->input('roles', []));23 24 // Also, more actions with that user25 26 return redirect()->route('users.index');27 }28}
This Controller is too long - the logic should be somewhere else.
Refactoring - Step 1: Service Class
One of the ways to refactor it is to create a specific Service class for everything related to the User, with methods like store()
and update()
.
Note that Laravel doesn't have php artisan make:service
command, you need to create that class manually, as a regular PHP class.
And then, we move that code from Controller, into a Service:
app/Services/UserService.php:
1namespace App\Services; 2 3class UserService { 4 5 public function store(array $userData): User 6 { 7 $user = User::create($userData); 8 9 $user->roles()->sync($userData['roles']);10 11 // More actions with that user: let's say, 5+ more lines of code12 // - Upload avatar13 // - Email to the user14 // - Notify admins about new user15 // - Create some data for that user16 // - and more...17 18 return $user;19 }20 21 public function update(array $userData, User $user): User22 {23 $user->update($userData);24 $user->roles()->sync($userData['roles']);25 26 // Also, more actions with that user27 }28}
Then, our Controller becomes much shorter - we're just calling the Service methods.
There are a few ways to do this. The most straightforward one is to create a Service class instance whenever you need it, like this:
1use App\Services\UserService; 2 3class UserController extends Controller 4{ 5 public function store(StoreUserRequest $request) 6 { 7 (new UserService())->store($request->validated()); 8 9 return redirect()->route('users.index');10 }11 12 public function update(UpdateUserRequest $request, User $user)13 {14 (new UserService())->update($request->validated(), $user);15 16 return redirect()->route('users.index');17 }18}
Refactoring - Step 2: Inject Service
Instead of doing new UserService()
every time we need it, we can just insert it as a dependency in the methods where we need it.
Laravel will auto-initialize it, if we provide the type-hint inside the Controller methods:
1use App\Services\UserService; 2 3class UserController extends Controller 4{ 5 public function store(StoreUserRequest $request, UserService $userService) 6 { 7 $userService->store($request->validated()); 8 9 return redirect()->route('users.index');10 }11 12 public function update(UpdateUserRequest $request, User $user, UserService $userService)13 {14 $userService->update($request->validated(), $user);15 16 return redirect()->route('users.index');17 }18}
We can go even further and inject the Service class into a constructor of the Controller. Then, we have access to the service in whatever Controller methods we need.
1use App\Services\UserService; 2 3class UserController extends Controller 4{ 5 private UserService $userService; 6 7 public function __construct(UserService $userService) 8 { 9 $this->userService = $userService;10 }11 12 public function store(StoreUserRequest $request)13 {14 $this->userService->store($request->validated());15 16 return redirect()->route('users.index');17 }18 19 public function update(UpdateUserRequest $request, User $user)20 {21 $this->userService->update($request->validated(), $user);22 23 return redirect()->route('users.index');24 }25}
Finally, we can use the PHP 8 relatively new syntax called constructor property promotion, so we don't even need to declare a private variable, or assign something in the constructor:
1use App\Services\UserService; 2 3class UserController extends Controller 4{ 5 public function __construct(private UserService $userService) 6 { 7 } 8 9 // We can still use $this->userService anywhere in the Controller10}
I have a separate video, specifically on that PHP 8 feature:
That's it for this article. Of course, there are other ways to separate the code from the Controller - Action classes, Repositories and other patterns - so choose whichever you like, the logic of injecting or initializing that class would be the same.
No comments or questions yet...