DEV Community

Balaji Dharma
Balaji Dharma

Posted on • Originally published at blog.devgenius.io on

Restructuring a Laravel controller using Services & Action Classes

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

Restructuring a Laravel controller using Services & Action Classes
Photo by GR Stocks on Unsplash

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

The 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 serController 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.');
}
Enter fullscreen mode Exit fullscreen mode

To further refactoring, 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.phpservice class with the createUser method. This is a custom PHP class in Laravel, so no artisan command. We need to create manually.

Implement Service Class

app/Services/Admin/UserService.php

<?php
namespace App\Services\Admin;

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

class UserService
{
    public function createUser(Request $request): User
    {
        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);

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

        return $user;
    }
}
Enter fullscreen mode Exit fullscreen mode

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

app/Http/Controllers/Admin/UserController.php

use App\Services\Admin\UserService;

public function store(StoreUserRequest $request, UserService $userService)
{
    $userService->createUser($request);

    return redirect()->route('user.index')
                    ->with('message','User created successfully.');
}
Enter fullscreen mode Exit fullscreen mode

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(Request $request): User
    {
        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);

        return $user;
    }

    public function assignRole(Request $request, User $user): void
    {
        $roles = $request->roles ?? [];
        $user->assignRole($roles);
    }
}
Enter fullscreen mode Exit fullscreen mode

app/Http/Controllers/Admin/UserController.php

public function store(StoreUserRequest $request, UserService $userService)
{
    $user = $userService->createUser($request);

    $userService->assignRole($request, $user);

    return redirect()->route('user.index')
                    ->with('message','User created successfully.');
}
Enter fullscreen mode Exit fullscreen mode

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

Click here to view example for 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.phpAction class with the singlehandle method.

app/Actions/Admin/User/CreateUser.php

<?php
namespace App\Actions\Admin\User;

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

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

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

        return $user;
    }
}
Enter fullscreen mode Exit fullscreen mode

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($request);

    return redirect()->route('user.index')
                    ->with('message','User created successfully.');
}
Enter fullscreen mode Exit fullscreen mode

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 the 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, The 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 Actions classes.

The Laravel admin panel is available on 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.

Previous part — Part 10: Restructuring a Laravel controller using Form Request Validation

Next part — Part 12: Create Reusable Blade Components in Laravel


Discussion (4)

Collapse
davorminchorov profile image
Davor Minchorov

You are using the Request object as a parameter to the execute method which limits the artisan command from reusing it because you do not have access to the Request object there.

A better alternative is to use a Data Transfer Object which would transfer the data between the classes regardless if it’s a web or APi controller, artisan command etc.

Collapse
balajidharma profile image
Balaji Dharma Author • Edited on

You can create Request object on the fly and call the action function

use Illuminate\Http\Request;

$request = new Request([
        'name'   => 'User',
        'email' => 'newuser@example.com',
    ]);

$createUser->handle($request);
Enter fullscreen mode Exit fullscreen mode
Collapse
davorminchorov profile image
Davor Minchorov

Sure, you can but theoretically this does not make much sense in the real world because the CLI commands do not do HTTP requests.

There are some MVC (or any architecture) rules about what part of the application the HTTP Request object has access to, which is usually the middleware and controller classes.

Another issue with the service method having the Request object as a parameter is that you are dragging more things with you that you do not actually need to. The only things you need is the request data but you are dragging methods and properties related to headers, session, cookies, validation etc which you do not actually need.

Thread Thread
balajidharma profile image
Balaji Dharma Author • Edited on

For Laravel CLI, we going to call the actions handle function in command controller (not going to use the actions or service controller directly). So, that time we can create request objects on the fly.

I am agree using plain object or array will improve the performance, but really we don’t know how much it improves. It based on application complexity.