Laravel Refactoring — Laravel creates an admin panel from scratch — Part 11
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.');
}
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;
}
}
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.');
}
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);
}
}
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.');
}
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;
}
}
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.');
}
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
Top comments (4)
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.
You can create Request object on the fly and call the action function
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.
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.