Restructuring a Laravel controller using Services & Action Classes

Laravel Refactoring — Laravel creates an admin panel from scratch — Part 11

In the previous part, we moved the UserController store method validation to Form Request. In this part, we going to explore and use the new trending Actions and Services Classes.

We going to cover the below topic in the blog

  • Laravel project structure
  • Controller Refactoring
  • Service Class
    • What is Service Class
    • Implement Service Class
  • Action Class
    • Implement Action Class
  • Advantages of Services & Action Classes
  • Disadvantages of Services & Action Classes
  • Conclusion

Laravel project structure

Laravel does not restrict your project structure also they do not suggest any project structure. So, you have the freedom to choose your project structure.

Laravel gives you the flexibility to choose the structure yourself

We will explore both Services & Action Classes and we use these classes in our Laravel basic admin panel.

Controller Refactoring

The UserController the store function does the below 3 actions.

public function store(StoreUserRequest  $request)
{
    // 1.Create a user
    $user = User::create([
        'name' => $request->name,
        'email' => $request->email,
        'password' => Hash::make($request->password)
    ]);
    // 2.Assign role to user
    if(! empty($request->roles)) {
        $user->assignRole($request->roles);
    }
    // 3.Redirect with message
    return redirect()->route('user.index')
                    ->with('message','User created successfully.');
}

To further refactor, we can move the logic to another class method. This new class is called Services & Action Classes. We will see them one by one.


Services Class

We decided to move the logic to another class. The Laravel best practices are suggested to move business logic from controllers to service classes due to the Single-responsibility principle (SRP). The Service class is just a common PHP class to put all our logic.

What is Service Class

A service is a very simple class and it is not extended with any class. So, it is just a standalone PHP class.

We going to create a new app/Services/Admin/UserService.php service class with the createUser method. This is a custom PHP class in Laravel, so no artisan command. We need to create it manually.

Implement Service Class

app/Services/Admin/UserService.php

<?php
namespace App\Services\Admin;
 
use App\Models\User;
use Illuminate\Support\Facades\Hash;
 
class UserService
{
    public function createUser($data): User
    {
        $user = User::create([
            'name' => $data->name,
            'email' => $data->email,
            'password' => Hash::make($data->password),
        ]);

        if(! empty($data->roles)) {
            $user->assignRole($data->roles);
        }

        return $user;
    }
}

Then, in the UserController call this method. For the Automatic Injection, you may type-hint the dependency in the controller.

Blog Updated: Earlier I passed the $request (function createUser(Request $request)) directly to the service class. The service can use by other methods. So $request is converted to an object and passed as params.

app/Http/Controllers/Admin/UserController.php

use App\Services\Admin\UserService;
public function store(StoreUserRequest $request, UserService $userService)
{
    $userService->createUser((object) $request->all());
    return redirect()->route('user.index')
                    ->with('message','User created successfully.');
}

We can do some more refactoring on UserService Class by moving the user role saving to the new method.

app/Services/Admin/UserService.php

class UserService
{
    public function createUser($data): User
    {
        $user = User::create([
            'name' => $data->name,
            'email' => $data->email,
            'password' => Hash::make($data->password),
        ]);
        return $user;
    }
    public function assignRole($data, User $user): void
    {
        $roles = $data->roles ?? [];
        $user->assignRole($roles);
    }
}

app/Http/Controllers/Admin/UserController.php

public function store(StoreUserRequest $request, UserService $userService)
{
    $data = (object) $request->all();
    $user = $userService->createUser($data);
    $userService->assignRole($data, $user);
    return redirect()->route('user.index')
                    ->with('message','User created successfully.');
}

Now we implemented the Service class. We will discuss the benefit at the end of the blog.

Click here to view examples of service classes used on Laravel


Action Class

In the Laravel community, the concept of Action classes got very popular in recent years. An action is a very simple PHP class similar to the Service class. But Action class only has one public method execute or handle Else you could name that method whatever you want.

Implement Action Class

We going to create a new app/Actions/Admin/User/CreateUser.php Action class with the single handle method.

app/Actions/Admin/User/CreateUser.php

<?php

namespace App\Actions\Admin\User;

use App\Models\User;
use Illuminate\Support\Facades\Hash;

class CreateUser
{
    public function handle($data): User
    {
        $user = User::create([
            'name' => $data->name,
            'email' => $data->email,
            'password' => Hash::make($data->password),
        ]);

        $roles = $data->roles ?? [];
        $user->assignRole($roles);

        return $user;
    }
}

Now call this handle method on UserController. The method injection to resolve CreateUser.

app/Http/Controllers/Admin/UserController.php

public function store(StoreUserRequest $request, CreateUser $createUser)
{
    $createUser->handle((object) $request->all());
    return redirect()->route('user.index')
                    ->with('message','User created successfully.');
}

The biggest advantage of this Action class we don’t worry about the function name. Because it should always single function like handle


Advantages of Services & Action Classes

  • Code reusability: We can call the method on the Artisan command and also easy to call other controllers.
  • Single-responsibility principle (SRP): Achieved SRP by using Services & Action Classes
  • Avoid Conflict: Easy to manage code for larger applications with a large development team.

Disadvantages of Services & Action Classes

  • Too many classes: We need to create too many classes for single functionality
  • Small Application: Not recommended for smaller applications

Conclusion

As said earlier, Laravel gives you the flexibility to choose the structure yourself. The Services and Action classes are one of the structure methods. It should be recommended for large-scale applications to avoid conflict and do faster releases.

For the Laravel Basic Admin Panel, I am going with the Actions classes.

The Laravel admin panel is available at https://github.com/balajidharma/basic-laravel-admin-panel. Install the admin panel and share your feedback.

Thank you for reading.

Stay tuned for more!

Follow me at balajidharma.medium.com.


References

https://freek.dev/1371-refactoring-to-actions
https://laravel-news.com/controller-refactor
https://farhan.dev/tutorial/laravel-service-classes-explained/
Tags: No tags

Comments are closed.