Refactoring Business Logic to Actions in Laravel

Search for a command to run...

No comments yet. Be the first to comment.
A few months ago, my product manager dropped something in the team channel. "We are adding an AI agent to the platform next quarter. It will handle follow-ups automatically." Just like that. One messa

f you’re diving into React, you’ve likely come across the useRef hook. But what exactly is useRef(), and how can it make your life easier? In this post we’ll cover what useRef is, walk through five

A bug that only happens for some users, that you can never reproduce, that isn't in your code. Welcome to the React + browser-translation crash — what causes it, why it's so sneaky, and the battle-tested one-file fix.

Upgrading a core production database is usually the stuff of dev nightmares. It often involves maintenance windows, scheduled downtime, and the looming fear of a botched migration. But what if you cou

We are moving into products that adapt, predict and react with real context. Designing only interfaces is not enough anymore. We are designing experiences that understand the user. Designers and digital agencies are now challenged to move beyond trad...

Engineering at JoBins
167 posts
Welcome to Engineering at JoBins — where we share the stories, insights, and lessons from building a world-class hiring platform. From system design and scalability to developer tools and team culture, our engineers write about the real challenges we solve every day. Whether you're a curious developer or a fellow builder, we hope our experiences inspire and inform your own engineering journey.
Laravel is a flexible PHP framework, where we can structure our architecture as per our needs in the project. Based on the project size, we define core components such as Controllers and Models following the MVC architecture. But, for a medium to a large-scale project, considering scalability and maintainability in mind, it is not good to write our business logic, a critical part of our application, in controllers or models. Writing such business logic in a single file like controllers or models, making them a God Object, results in a spaghetti code which makes it harder to cope with the future changes. There are so many ways to resolve this issue by extracting business logic into different single responsible classes in OOP, and the Actions class is the one among them.
Action classes are simple classes without any abstractions (inheritance) or interfaces. It is a class that takes input, does something, and may/may not gives output. That's why an action class usually has only one public method, and sometimes a constructor (for dependency injection). For the convention, we will name this class with the suffix "Action", eg, CreateUserAction. Here are some points to be considered while working on Action classes, though these points are recommended not bind to follow strictly.
CreateUserAction.execute() or handle().Let's see an example of implementing an action class. Consider we have a module, which is supposed to save a blog post in the database and then tweet the post's title on Twitter.
The controller would look like this.
class PostController
{
public function create(Request $request, TwitterService $twitterService)
{
$post = new Post();
$post->title = $request->input('title');
$post->content = $request->input('content');
$post->is_published = true;
$post->save();
$twitterService->tweet($post->title);
return back()->with(['message' => 'Post has been submitted and published.']);
}
}
Here, the business logic is defined in the controller's method. As mentioned above, it is not a good idea to write such logic in the controller's method because the same logic can be used in another place (let's say a CLI command) in the future or might need to change this with some other logic.
Let's extract that logic into an Action class.
class CreatePostAction
{
public function __construct(TwitterService $twitterService)
{
$this->twitterService = $twittterService
}
public function execute(array $data):Post
{
$post = $this->savePost($data);
$this->tweet($post->title);
return $post;
}
public function savePost(array $data): Post
{
$post = new Post();
$post->title = $data['title'];
$post->content = $data['content'];
$post->is_published = true;
$post->save();
return $post;
}
public function tweet(string $text): void
{
$this->twitterService($text);
}
}
and refactor controller method as follows.
class PostController
{
public function create(Request $request, CreatePostAction $action)
{
$post = $action->execute($request->validated());
return back()->with(['message' => 'Post has been submitted and published.']);
}
}
The complete code of implementing the Action class is in the Github repo.
Hope you like the article. We appreciate your comments on this design pattern in Laravel.