Keep It Simple: Mastering the Single Responsibility Principle in Laravel with Real-World Examples
Table of contents
Single Responsibility Principle in Laravel
Have you ever walked into a kitchen where one person is simultaneously cooking, washing dishes, taking phone orders, and managing inventory? Chaos, right? This is exactly what happens in our code when we ignore the Single Responsibility Principle (SRP). Let's break down this fundamental principle using Laravel and real-world examples that you'll encounter in your daily development journey.
What is the Single Responsibility Principle?
The Single Responsibility Principle, part of the SOLID principles, states that a class should have only one reason to change. In simpler terms: A class should do one thing, and do it well. Think of it like specialized workers in a restaurant โ you have dedicated chefs, waiters, and dishwashers, each focusing on their specific tasks.
The Wrong Way: A Kitchen Nightmare
Let's look at a common mistake developers make when creating a User registration system:
class UserController extends Controller
{
public function register(Request $request)
{
// Validate input
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8'
]);
// Create user
$user = User::create([
'name' => $validatedData['name'],
'email' => $validatedData['email'],
'password' => bcrypt($validatedData['password'])
]);
// Send welcome email
Mail::to($user->email)->send(new WelcomeEmail($user));
// Create user profile
Profile::create([
'user_id' => $user->id,
'avatar' => 'default.jpg',
'bio' => ''
]);
// Log user activity
Activity::create([
'user_id' => $user->id,
'type' => 'registration',
'description' => 'User registered successfully'
]);
return response()->json(['message' => 'Registration successful']);
}
}
This code is like a chef who's doing everything in the kitchen. It's handling validation, user creation, email sending, profile creation, and activity logging. This violates SRP because the class has multiple reasons to change.
The Right Way: A Well-Organized Kitchen
Let's refactor this code following SRP:
- First, create a dedicated UserRegistrationService:
class UserRegistrationService
{
protected $userRepository;
protected $profileService;
protected $activityLogger;
protected $mailService;
public function __construct(
UserRepository $userRepository,
ProfileService $profileService,
ActivityLogger $activityLogger,
MailService $mailService
) {
$this->userRepository = $userRepository;
$this->profileService = $profileService;
$this->activityLogger = $activityLogger;
$this->mailService = $mailService;
}
public function register(array $userData): User
{
$user = $this->userRepository->create($userData);
$this->profileService->createForUser($user);
$this->mailService->sendWelcomeEmail($user);
$this->activityLogger->logRegistration($user);
return $user;
}
}
- Create a UserRepository for database operations:
class UserRepository
{
public function create(array $userData): User
{
return User::create([
'name' => $userData['name'],
'email' => $userData['email'],
'password' => bcrypt($userData['password'])
]);
}
}
- Create a ProfileService:
class ProfileService
{
public function createForUser(User $user): Profile
{
return Profile::create([
'user_id' => $user->id,
'avatar' => 'default.jpg',
'bio' => ''
]);
}
}
- Create an ActivityLogger:
class ActivityLogger
{
public function logRegistration(User $user): void
{
Activity::create([
'user_id' => $user->id,
'type' => 'registration',
'description' => 'User registered successfully'
]);
}
}
- Create a MailService:
class MailService
{
public function sendWelcomeEmail(User $user): void
{
Mail::to($user->email)->send(new WelcomeEmail($user));
}
}
- Finally, a clean UserController:
class UserController extends Controller
{
protected $registrationService;
public function __construct(UserRegistrationService $registrationService)
{
$this->registrationService = $registrationService;
}
public function register(Request $request)
{
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8'
]);
$user = $this->registrationService->register($validatedData);
return response()->json(['message' => 'Registration successful']);
}
}
Benefits of Following SRP
Maintainability: Like having specialized chefs for different cuisines, each class has a specific responsibility, making it easier to maintain and modify.
Testability: You can test each component independently, just like you can evaluate each kitchen staff member's performance separately.
Reusability: Services can be reused across different parts of your application, like how a sauce chef's preparations can be used in multiple dishes.
Scalability: Easy to add new features or modify existing ones without affecting other parts of the code.
Better Organization: Code is more organized and easier to understand, like a well-organized kitchen where everything has its place.
Potential Drawbacks and How to Handle Them
More Files: You'll have more classes and files to manage. Solution: Use proper folder structure and naming conventions.
Initial Setup Time: It takes longer to set up initially. Solution: The time invested pays off in long-term maintenance.
Complexity in Service Container: More classes mean more bindings in Laravel's service container. Solution: Use service providers effectively.
Real-World Analogy: The Restaurant Kitchen
Think of your application like a restaurant kitchen:
UserController: The head chef who oversees operations but doesn't do all the cooking
UserRegistrationService: The kitchen manager coordinating different stations
UserRepository: The pantry chef handling ingredients (data)
ProfileService: The garde manger handling user profiles
ActivityLogger: The kitchen record keeper
MailService: The communication system between kitchen and customers
When to Apply SRP
Apply SRP when your class:
Has methods that serve different purposes
Becomes difficult to test
Changes frequently for different reasons
Has too many dependencies
Exceeds 200-300 lines of code
When Not to Over-Apply SRP
Don't create separate classes for every tiny operation. For example, if you have a simple validation rule, it doesn't need its own class. Use common sense and consider maintainability as your guide.
Conclusion
The Single Responsibility Principle might seem like extra work initially, but it's like organizing a professional kitchen โ everything has its place and purpose. When implemented correctly, it makes your code more maintainable, testable, and scalable. Start applying SRP in your next Laravel project, and you'll see the benefits as your application grows.
Remember: Like a well-organized kitchen produces better food more efficiently, well-organized code following SRP produces better applications more maintainably.
Happy coding! ๐
Subscribe to my newsletter
Read articles from Sohag Hasan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sohag Hasan
Sohag Hasan
WhoAmI => notes.sohag.pro/author