Laravel Controller into Service Class with Injection

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