Laravel Controller into Service Class with Injection

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 code
10 // - Upload avatar
11 // - Email to the user
12 // - Notify admins about new user
13 // - Create some data for that user
14 // - 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 user
25 
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 code
12 // - Upload avatar
13 // - Email to the user
14 // - Notify admins about new user
15 // - Create some data for that user
16 // - and more...
17 
18 return $user;
19 }
20 
21 public function update(array $userData, User $user): User
22 {
23 $user->update($userData);
24 $user->roles()->sync($userData['roles']);
25 
26 // Also, more actions with that user
27 }
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 Controller
10}

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...

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 22 courses (477 lessons, total 38 h 20 min)
  • 2 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord