https://laraveldaily.com/storage/113/controller-service-injection.png
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()
:
class UserController extends Controller
{
public function store(StoreUserRequest $request)
{
$user = User::create($request->validated());
$user->roles()->sync($request->input('roles', []));
// More actions with that user: let's say, 5+ more lines of code
// - Upload avatar
// - Email to the user
// - Notify admins about new user
// - Create some data for that user
// - and more...
return redirect()->route('users.index');
}
public function update(UpdateUserRequest $request, User $user)
{
$user->update($request->validated());
$user->roles()->sync($request->input('roles', []));
// Also, more actions with that user
return redirect()->route('users.index');
}
}
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:
namespace App\Services;
class UserService {
public function store(array $userData): User
{
$user = User::create($userData);
$user->roles()->sync($userData['roles']);
// More actions with that user: let's say, 5+ more lines of code
// - Upload avatar
// - Email to the user
// - Notify admins about new user
// - Create some data for that user
// - and more...
return $user;
}
public function update(array $userData, User $user): User
{
$user->update($userData);
$user->roles()->sync($userData['roles']);
// Also, more actions with that user
}
}
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:
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');
}
}
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:
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');
}
}
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.
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');
}
}
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:
use App\Services\UserService;
class UserController extends Controller
{
public function __construct(private UserService $userService)
{
}
// We can still use $this->userService anywhere in the Controller
}
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.
Laravel News Links