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.

avatar

Nice step, but i think we must use a service if each method of the controller more than 20-30 lines maybe..

cause, it will make u move from a file to another just to look at one method.

isn't it?

avatar

There's no strict rule for how many lines of code. It's whenever the method becomes harder to read and understand immediately what it does. I would go lower to 10+ lines of code, not even 20-30, but that's a personal preference.

avatar

Heya,

Building on this idea, how would you feel about invokable service classes dedicated to just the one thing, something like :

public function store(StoreThingyService $storeService, StoreThingyRequest $request)
{
	$thingy = $storeService($request->validated());			
	
	return to_route('thingy.show', compact('thingy'));
}

I've written something of the sort today at work and I'm not really sure what to think about it. Thanks !

avatar

For services with just one method they came up with a separate term called Action classes.

see examples here

avatar

Great, thanks ! I'll give that a look :)

Like our articles?

Become a Premium Member for $129/year or $29/month

Written by

You might also like